diff --git a/tembo/cli/cli.py b/tembo/cli/cli.py index 0014fea..e0df85b 100644 --- a/tembo/cli/cli.py +++ b/tembo/cli/cli.py @@ -32,9 +32,8 @@ def run(): def list_all(): """List all scopes defined in the config.yml.""" _all_scopes = [user_scope["name"] for user_scope in tembo.cli.CONFIG.scopes] - tembo.cli.logger.info( - "%s names found in config.yml: '%s'", len(_all_scopes), "', '".join(_all_scopes) - ) + _all_scopes_joined = "', '".join(_all_scopes) + cli_message(f"{len(_all_scopes)} names found in config.yml: '{_all_scopes_joined}'") raise SystemExit(0) @@ -70,7 +69,7 @@ def new(scope: str, inputs: Collection[str], dry_run: bool, example: bool): exceptions.MissingConfigYML, ) as tembo_exception: cli_message(tembo_exception.args[0]) - raise SystemExit(0) from tembo_exception + raise SystemExit(1) from tembo_exception # get the scope configuration from the config.yml try: @@ -83,7 +82,7 @@ def new(scope: str, inputs: Collection[str], dry_run: bool, example: bool): _new_show_example(example, config_scope) # if the name is in the config.yml, create the scoped page - scoped_page = _new_create_scoped_page(config_scope, inputs) + scoped_page = new_create_scoped_page(config_scope, inputs) if dry_run: cli_message(f"{scoped_page.path} will be created") @@ -99,6 +98,39 @@ def new(scope: str, inputs: Collection[str], dry_run: bool, example: bool): raise SystemExit(0) from scoped_page_already_exists +def new_create_scoped_page(config_scope: dict, inputs: Collection[str]) -> pages.Page: + page_creator_options = pages.PageCreatorOptions( + base_path=tembo.cli.CONFIG.base_path, + template_path=tembo.cli.CONFIG.template_path, + page_path=config_scope["path"], + filename=config_scope["filename"], + extension=config_scope["extension"], + name=config_scope["name"], + example=config_scope["example"], + user_input=inputs, + template_filename=config_scope["template_filename"], + ) + try: + return pages.ScopedPageCreator(page_creator_options).create_page() + except exceptions.BasePathDoesNotExistError as base_path_does_not_exist_error: + cli_message(base_path_does_not_exist_error.args[0]) + raise SystemExit(1) from base_path_does_not_exist_error + except exceptions.TemplateFileNotFoundError as template_file_not_found_error: + cli_message(template_file_not_found_error.args[0]) + raise SystemExit(1) from template_file_not_found_error + except exceptions.MismatchedTokenError as mismatched_token_error: + if config_scope["example"] is not None: + cli_message( + f'Your tembo config.yml/template specifies {mismatched_token_error.expected} input tokens, you gave {mismatched_token_error.given}. Example: {config_scope["example"]}' + ) + raise SystemExit(1) from mismatched_token_error + cli_message( + f"Your tembo config.yml/template specifies {mismatched_token_error.expected} input tokens, you gave {mismatched_token_error.given}" + ) + + raise SystemExit(1) from mismatched_token_error + + def _new_verify_name_exists(scope: str) -> None: _name_found = scope in [ user_scope["name"] for user_scope in tembo.cli.CONFIG.scopes @@ -107,7 +139,7 @@ def _new_verify_name_exists(scope: str) -> None: return if len(tembo.cli.CONFIG.scopes) > 0: # if the name is missing in the config.yml, raise error - raise exceptions.ScopeNotFound(f"Command {scope} not found in config.yml") + raise exceptions.ScopeNotFound(f"Scope {scope} not found in config.yml") # raise error if no config.yml found if pathlib.Path(tembo.cli.CONFIG.config_path).exists(): raise exceptions.EmptyConfigYML( @@ -158,51 +190,9 @@ def _new_show_example(example: bool, config_scope: dict) -> None: raise SystemExit(0) -def _new_create_scoped_page(config_scope: dict, inputs: Collection[str]) -> pages.Page: - page_creator_options = pages.PageCreatorOptions( - base_path=tembo.cli.CONFIG.base_path, - template_path=tembo.cli.CONFIG.template_path, - page_path=config_scope["path"], - filename=config_scope["filename"], - extension=config_scope["extension"], - name=config_scope["name"], - example=config_scope["example"], - user_input=inputs, - template_filename=config_scope["template_filename"], - ) - try: - return pages.ScopedPageCreator(page_creator_options).create_page() - except exceptions.BasePathDoesNotExistError as base_path_does_not_exist_error: - cli_message(base_path_does_not_exist_error.args[0]) - raise SystemExit(1) from base_path_does_not_exist_error - except exceptions.TemplateFileNotFoundError as template_file_not_found_error: - cli_message(template_file_not_found_error.args[0]) - raise SystemExit(1) from template_file_not_found_error - except exceptions.MismatchedTokenError as mismatched_token_error: - if config_scope["example"] is not None: - cli_message( - f'Your tembo config.yml/template specifies {mismatched_token_error.expected} input tokens, you gave {mismatched_token_error.given}. Example: {config_scope["example"]}' - ) - raise SystemExit(1) from mismatched_token_error - cli_message( - f'Your tembo config.yml/template specifies {mismatched_token_error.expected} input tokens, you gave {mismatched_token_error.given}' - ) - - raise SystemExit(1) from mismatched_token_error - - def cli_message(message: str) -> None: click.echo(f"[TEMBO] {message} 🐘") run.add_command(new) run.add_command(list_all) - - -if __name__ == "__main__": - new(["meeting", "a", "b", "c", "d"]) # noqa - - # pyinstaller - # if getattr(sys, "frozen", False): - # run(sys.argv[1:]) - # run(sys.argv[1:]) diff --git a/tests/test_cli/data/config/success/config.yml b/tests/test_cli/data/config/success/config.yml index 8830957..0550bbe 100644 --- a/tests/test_cli/data/config/success/config.yml +++ b/tests/test_cli/data/config/success/config.yml @@ -5,3 +5,8 @@ path: "some_scope" filename: "{name}" extension: md + - name: another_some_scope + example: tembo new another_some_scope + path: "another_some_scope" + filename: "{name}" + extension: md diff --git a/tests/test_cli/data/some_scope/some_scope.md b/tests/test_cli/data/some_scope/some_scope.md new file mode 100644 index 0000000..ce7e948 --- /dev/null +++ b/tests/test_cli/data/some_scope/some_scope.md @@ -0,0 +1 @@ +already exists diff --git a/tests/test_cli/test_cli.py b/tests/test_cli/test_cli.py index b4f14a9..a89346a 100644 --- a/tests/test_cli/test_cli.py +++ b/tests/test_cli/test_cli.py @@ -10,14 +10,12 @@ from tembo.cli.cli import ( _new_verify_name_exists, _new_get_config_scope, _new_show_example, - _new_create_scoped_page, + new_create_scoped_page, + new, + list_all, ) -def test_cli_page_is_saved_success(): - pass - - def test_new_verify_name_exists_success(shared_datadir): # arrange os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") @@ -45,8 +43,7 @@ def test_new_verify_name_exists_scope_not_found(shared_datadir): # assert assert ( - str(scope_not_found.value) - == "Command some_missing_scope not found in config.yml" + str(scope_not_found.value) == "Scope some_missing_scope not found in config.yml" ) # cleanup @@ -111,6 +108,9 @@ def test_new_get_config_scope_success(shared_datadir): "template_filename": None, } + # cleanup + del os.environ["TEMBO_CONFIG"] + def test_new_get_config_scope_key_not_found(shared_datadir): # arrange @@ -128,6 +128,9 @@ def test_new_get_config_scope_key_not_found(shared_datadir): str(mandatory_key_not_found.value) == "Key 'filename' not found in config.yml" ) + # cleanup + del os.environ["TEMBO_CONFIG"] + @pytest.mark.parametrize( "path,message", @@ -150,6 +153,9 @@ def test_new_show_example(path, message, shared_datadir, capsys): assert capsys.readouterr().out == message assert system_exit.value.code == 0 + # cleanup + del os.environ["TEMBO_CONFIG"] + def test_new_create_scoped_page_success(shared_datadir, tmpdir): # arrange @@ -164,12 +170,16 @@ def test_new_create_scoped_page_success(shared_datadir, tmpdir): ) # act - scoped_page = _new_create_scoped_page(config_scope, inputs) + scoped_page = new_create_scoped_page(config_scope, inputs) # assert assert scoped_page.path == scoped_page_file assert scoped_page.page_content == "" + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + def test_new_create_scoped_page_base_path_does_not_exist( shared_datadir, tmpdir, capsys @@ -184,7 +194,7 @@ def test_new_create_scoped_page_base_path_does_not_exist( # act with pytest.raises(SystemExit) as system_exit: - _new_create_scoped_page(config_scope, inputs) + new_create_scoped_page(config_scope, inputs) # assert assert system_exit.value.code == 1 @@ -193,6 +203,10 @@ def test_new_create_scoped_page_base_path_does_not_exist( == f'[TEMBO] Tembo base path of {os.environ["TEMBO_BASE_PATH"]} does not exist. 🐘\n' ) + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + def test_new_create_scoped_page_template_file_does_not_exist( shared_datadir, tmpdir, capsys @@ -209,7 +223,7 @@ def test_new_create_scoped_page_template_file_does_not_exist( # act with pytest.raises(SystemExit) as system_exit: - _new_create_scoped_page(config_scope, inputs) + new_create_scoped_page(config_scope, inputs) # assert assert system_exit.value.code == 1 @@ -218,6 +232,10 @@ def test_new_create_scoped_page_template_file_does_not_exist( == f'[TEMBO] Template file {os.environ["TEMBO_TEMPLATE_PATH"]}/{config_scope["template_filename"]} does not exist. 🐘\n' ) + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + @pytest.mark.parametrize("example", [(True,), (False,)]) def test_new_create_scoped_page_mismatched_token( @@ -235,17 +253,209 @@ def test_new_create_scoped_page_mismatched_token( # act with pytest.raises(SystemExit) as system_exit: - _new_create_scoped_page(config_scope, inputs) + new_create_scoped_page(config_scope, inputs) # assert assert system_exit.value.code == 1 if not example[0]: assert ( capsys.readouterr().out - == f'[TEMBO] Your tembo config.yml/template specifies 0 input tokens, you gave 1 🐘\n' + == f"[TEMBO] Your tembo config.yml/template specifies 0 input tokens, you gave 1 🐘\n" ) else: assert ( capsys.readouterr().out - == f'[TEMBO] Your tembo config.yml/template specifies 0 input tokens, you gave 1. Example: tembo new some_scope 🐘\n' + == f"[TEMBO] Your tembo config.yml/template specifies 0 input tokens, you gave 1. Example: tembo new some_scope 🐘\n" ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_new_dry_run(shared_datadir, tmpdir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + scope = "some_scope" + dry_run = "--dry-run" + + # act + with pytest.raises(SystemExit) as system_exit: + new([scope, dry_run]) + + # assert + assert system_exit.value.code == 0 + assert ( + capsys.readouterr().out + == f"[TEMBO] {tmpdir}/some_scope/some_scope.md will be created 🐘\n" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_new_success(shared_datadir, tmpdir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + scoped_page_file = pathlib.Path(tmpdir / "some_scope" / "some_scope").with_suffix( + ".md" + ) + + # act + with pytest.raises(SystemExit) as system_exit: + new(["some_scope"]) + + # assert + assert scoped_page_file.exists() + assert system_exit.value.code == 0 + assert capsys.readouterr().out == f"[TEMBO] Saved {scoped_page_file} to disk 🐘\n" + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_new_success_already_exists(shared_datadir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_BASE_PATH"] = str(shared_datadir) + importlib.reload(tembo.cli) + scoped_page_file = pathlib.Path( + shared_datadir / "some_scope" / "some_scope" + ).with_suffix(".md") + + # act + with pytest.raises(SystemExit) as system_exit: + new(["some_scope"]) + + # assert + assert scoped_page_file.exists() + assert system_exit.value.code == 0 + assert ( + capsys.readouterr().out == f"[TEMBO] File {scoped_page_file} already exists 🐘\n" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_new_scope_not_found(shared_datadir, tmpdir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + scoped_page_file = pathlib.Path(tmpdir / "some_scope" / "some_scope").with_suffix( + ".md" + ) + + # act + with pytest.raises(SystemExit) as system_exit: + new(["some_nonexistent_scope"]) + + # assert + assert not scoped_page_file.exists() + assert system_exit.value.code == 1 + assert ( + capsys.readouterr().out + == f"[TEMBO] Scope some_nonexistent_scope not found in config.yml 🐘\n" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_new_empty_config(shared_datadir, tmpdir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "empty") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + + # act + with pytest.raises(SystemExit) as system_exit: + new(["some_nonexistent_scope"]) + + # assert + assert system_exit.value.code == 1 + assert ( + capsys.readouterr().out + == f"[TEMBO] Config.yml found in {shared_datadir}/config/empty is empty 🐘\n" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_new_missing_config(shared_datadir, tmpdir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "missing") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + + # act + with pytest.raises(SystemExit) as system_exit: + new(["some_nonexistent_scope"]) + + # assert + assert system_exit.value.code == 1 + assert ( + capsys.readouterr().out + == f"[TEMBO] No config.yml found in {shared_datadir}/config/missing 🐘\n" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_new_missing_mandatory_key(shared_datadir, tmpdir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "missing_keys") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + + # act + with pytest.raises(SystemExit) as system_exit: + new(["some_scope"]) + + # assert + assert system_exit.value.code == 1 + assert ( + capsys.readouterr().out == f"[TEMBO] Key 'filename' not found in config.yml 🐘\n" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_list_all_success(shared_datadir, tmpdir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + scoped_page_file = pathlib.Path(tmpdir / "some_scope" / "some_scope").with_suffix( + ".md" + ) + + # act + with pytest.raises(SystemExit) as system_exit: + list_all([]) + + # assert + assert system_exit.value.code == 0 + assert ( + capsys.readouterr().out + == f"[TEMBO] 2 names found in config.yml: 'some_scope', 'another_some_scope' 🐘\n" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"]