From 903fbf6b69a1bd73608b4b5b6a9977ca476d3c20 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Sat, 30 Oct 2021 03:07:44 +0100 Subject: [PATCH 1/3] adding latest --- TODO.todo | 5 ++- dev/notes/test.md | 20 ++++------ tembo/journal/pages.py | 85 ++++++++++++++++++++++++++++-------------- 3 files changed, 69 insertions(+), 41 deletions(-) diff --git a/TODO.todo b/TODO.todo index da7030b..8b543aa 100644 --- a/TODO.todo +++ b/TODO.todo @@ -25,6 +25,7 @@ Documentation: ☐ Document using datadir with a module rather than a shared one. Link to tembo as an example. + ☐ Can prospector ignore tests dir? document this in the gist if so Functionality: ✔ Move any `tembo.CONFIG` calls out of `pages.py` and ensure these are passed in from the cli. @done(21-10-28 19:44) @@ -32,9 +33,9 @@ Functionality: ✔ Make example optional @done(21-10-29 00:15) ✔ Add the `--example` output to the miscounted token message so the user knows the correct command to use. @done(21-10-29 00:15) ✔ Page options dataclass @done(21-10-28 20:09) + ☐ Make user_input optional @important ☐ Replace loggers with `click.echo` for command outputs. Keep logging messages for actual logging messages? - ☐ Look at `_convert_to_path()` and see if it can be rewritten to make it clearer when there isn't a base path. - Currently checks to see if base_path is not None but this is never the case as a string must be passed in and if there isn't a base_path we pass in an empty string. + ✔ Look at `_convert_to_path()` and see if it can be rewritten to make it clearer when there isn't a base path. @done(21-10-30 02:14) ☐ Replace scoped page creator inputs so that the whole class uses the options dict rather than the variables passed around. ☐ Use the python runner Duty diff --git a/dev/notes/test.md b/dev/notes/test.md index 1c065d0..607a988 100644 --- a/dev/notes/test.md +++ b/dev/notes/test.md @@ -4,6 +4,7 @@ optional: - template_path - example - template_filename +- user_input required: - base_path @@ -11,20 +12,15 @@ required: - filename - extension - name -- user_input - should be optional - - - -- page with a template -- page without a template -- page using date tokens -- page using input tokens -- page using name tokens +- page using/not using input tokens +- page with/without a template - user input does not match number of input tokens +- user input is None +- the given base path does not exist +- page using/not using date tokens +- page using/not using name tokens - - -- user input does match number of input tokensE +- path/page filenames can contain spaces and they are converted diff --git a/tembo/journal/pages.py b/tembo/journal/pages.py index d9f3ea6..8562d9d 100644 --- a/tembo/journal/pages.py +++ b/tembo/journal/pages.py @@ -13,14 +13,29 @@ from jinja2.exceptions import TemplateNotFound import tembo +# TODO: flesh this out with details for the optional args @dataclass class PageCreatorOptions: + """Options dataclass to create a Page. + + Attributes: + base_path (str): + page_path (str): + filename (str): + extension (str): + name (str): + user_input (Collection[str] | None, optional): + example (str | None, optional): + template_filename (str | None, optional): + template_path (str | None, optional): + """ + base_path: str page_path: str filename: str extension: str name: str - user_input: Collection[str] + user_input: Collection[str] | None = None example: str | None = None template_filename: str | None = None template_path: str | None = None @@ -92,51 +107,48 @@ class ScopedPageCreator(PageCreator): """ def __init__(self) -> None: - self.base_path = "" - self.page_path = "" - self.filename = "" - self.extension = "" + # self.base_path = "" + # self.page_path = "" + # self.filename = "" + # self.extension = "" self._all_input_tokens: list[str] = [] + self.options: PageCreatorOptions def create_page(self, options: PageCreatorOptions) -> Page: - self.base_path = options.base_path - self.page_path = options.page_path - self.filename = options.filename - self.extension = options.extension + self.options = options - # verify the user input length matches the number of input tokens in the - # tembo config/templates self._all_input_tokens = self._get_input_tokens( options.template_filename, options.template_path ) self._verify_input_tokens(options.user_input, options.example) - # get the path of the scoped page path = self._convert_to_path( - self.base_path, self.page_path, self.filename, self.extension + self.options.base_path, + self.options.page_path, + self.options.filename, + self.options.extension, ) - - # substitute tokens in the filepath path = pathlib.Path( self._substitute_tokens(str(path), options.user_input, options.name) ) - # get the template file template_contents = self._get_template_contents( options.template_filename, options.template_path ) - # substitute tokens in template_contents if options.template_filename is not None: template_contents = self._substitute_tokens( template_contents, options.user_input, options.name ) + return ScopedPage(path, template_contents) def _get_template_contents( self, template_filename: str | None, template_path: str | None ) -> str: return ( - self._load_template(self.base_path, template_filename, template_path) + self._load_template( + self.options.base_path, template_filename, template_path + ) if template_filename is not None else "" ) @@ -146,7 +158,10 @@ class ScopedPageCreator(PageCreator): ) -> list[str]: path = str( pathlib.Path( - self.base_path, self.page_path, self.filename, self.extension + self.options.base_path, + self.options.page_path, + self.options.filename, + self.options.extension, ).expanduser() ) template_contents = self._get_template_contents( @@ -159,8 +174,23 @@ class ScopedPageCreator(PageCreator): return sorted(all_input_tokens) def _verify_input_tokens( - self, user_input: Collection[str], example: str | None + self, user_input: Collection[str] | None, example: str | None ) -> None: + if len(self._all_input_tokens) > 0 and user_input is None: + if example is not None: + tembo.logger.critical( + "Your tembo.config/template specifies %s input tokens, you gave 0. Example command: %s", + len(self._all_input_tokens), + example, + ) + else: + tembo.logger.critical( + "Your tembo.config/template specifies %s input tokens, you gave 0.", + len(self._all_input_tokens), + ) + raise SystemExit(1) + if user_input is None: + return if len(self._all_input_tokens) != len(user_input): if example is not None: tembo.logger.critical( @@ -176,11 +206,12 @@ class ScopedPageCreator(PageCreator): len(user_input), ) raise SystemExit(1) + return def _substitute_tokens( self, tokenified_string: str, - user_input: Collection[str], + user_input: Collection[str] | None, name: str, ) -> str: """For a tokened string, substitute input, name and date tokens.""" @@ -194,13 +225,13 @@ class ScopedPageCreator(PageCreator): def __substitute_input_tokens( self, tokenified_string: str, - user_input: Collection[str], + user_input: Collection[str] | None, ) -> str: - for input_value, extracted_token in zip(user_input, self._all_input_tokens): - # REVIEW: test this for spaces in the filename/input token - tokenified_string = tokenified_string.replace( - extracted_token, input_value.replace(" ", "_") - ) + if user_input is not None: + for input_value, extracted_token in zip(user_input, self._all_input_tokens): + tokenified_string = tokenified_string.replace( + extracted_token, input_value.replace(" ", "_") + ) return tokenified_string @staticmethod From a6ec5a9f35dd07b7e40e826a6deac63649caea64 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Sat, 30 Oct 2021 03:21:21 +0100 Subject: [PATCH 2/3] replacing all calls to self.options --- TODO.todo | 23 ++++---- tembo/journal/pages.py | 118 +++++++++++++++++------------------------ 2 files changed, 60 insertions(+), 81 deletions(-) diff --git a/TODO.todo b/TODO.todo index 8b543aa..efe3316 100644 --- a/TODO.todo +++ b/TODO.todo @@ -1,7 +1,4 @@ Priority: - ✔ Document the python/logging/typing in Trilium @done(21-10-25 14:33) - ✔ Update typing annotations to include generics instead @done(21-10-25 22:38) - https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes ☐ Write the tests ☐ test logs: document this @@ -28,15 +25,8 @@ Documentation: ☐ Can prospector ignore tests dir? document this in the gist if so Functionality: - ✔ Move any `tembo.CONFIG` calls out of `pages.py` and ensure these are passed in from the cli. @done(21-10-28 19:44) - ✔ Make `config scope` a dict in `cli.py`. @done(21-10-28 19:44) - ✔ Make example optional @done(21-10-29 00:15) - ✔ Add the `--example` output to the miscounted token message so the user knows the correct command to use. @done(21-10-29 00:15) - ✔ Page options dataclass @done(21-10-28 20:09) - ☐ Make user_input optional @important ☐ Replace loggers with `click.echo` for command outputs. Keep logging messages for actual logging messages? - ✔ Look at `_convert_to_path()` and see if it can be rewritten to make it clearer when there isn't a base path. @done(21-10-30 02:14) - ☐ Replace scoped page creator inputs so that the whole class uses the options dict rather than the variables passed around. + ☐ Make options a property on the class, add to abstract ☐ Use the python runner Duty ☐ Run tests @@ -75,6 +65,17 @@ Logging: clone the repo, delete .git, git init, configure and add git origin Archive: + ✔ Document the python/logging/typing in Trilium @done(21-10-25 14:33) @project(Priority) + ✔ Update typing annotations to include generics instead @done(21-10-25 22:38) @project(Priority) + https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes + ✔ Move any `tembo.CONFIG` calls out of `pages.py` and ensure these are passed in from the cli. @done(21-10-28 19:44) @project(Functionality) + ✔ Make `config scope` a dict in `cli.py`. @done(21-10-28 19:44) @project(Functionality) + ✔ Make example optional @done(21-10-29 00:15) @project(Functionality) + ✔ Add the `--example` output to the miscounted token message so the user knows the correct command to use. @done(21-10-29 00:15) @project(Functionality) + ✔ Page options dataclass @done(21-10-28 20:09) @project(Functionality) + ✔ Make user_input optional @important @done(21-10-30 03:20) @project(Functionality) + ✔ Look at `_convert_to_path()` and see if it can be rewritten to make it clearer when there isn't a base path. @done(21-10-30 02:14) @project(Functionality) + ✔ Replace scoped page creator inputs so that the whole class uses the options dict rather than the variables passed around. @done(21-10-30 03:20) @project(Functionality) ✔ Go through code TODOs @done(21-10-25 05:52) @project(Priority) ✔ Check code order and make sure things are where they should be @done(21-10-25 13:31) @project(Priority) ✔ Add version option @done(21-10-25 13:40) @project(Functionality) diff --git a/tembo/journal/pages.py b/tembo/journal/pages.py index 8562d9d..a305d36 100644 --- a/tembo/journal/pages.py +++ b/tembo/journal/pages.py @@ -42,28 +42,32 @@ class PageCreatorOptions: class PageCreator: + def __init__(self, options: PageCreatorOptions) -> None: + raise NotImplementedError + @abstractmethod def create_page(self, options: PageCreatorOptions) -> Page: raise NotImplementedError - @staticmethod - def _convert_to_path( - base_path: str, page_path: str, filename: str, extension: str - ) -> pathlib.Path: + def _convert_to_path(self) -> pathlib.Path: # check if Tembo base path exists - if not pathlib.Path(base_path).expanduser().exists(): + if not pathlib.Path(self.options.base_path).expanduser().exists(): tembo.logger.critical( - "Tembo base path of %s does not exist - exiting", base_path + "Tembo base path of %s does not exist - exiting", self.options.base_path ) raise SystemExit(1) path_to_file = ( - pathlib.Path(base_path).expanduser() - / pathlib.Path(page_path.replace(" ", "_")).expanduser() - / filename.replace(" ", "_") + pathlib.Path(self.options.base_path).expanduser() + / pathlib.Path(self.options.page_path.replace(" ", "_")).expanduser() + / self.options.filename.replace(" ", "_") ) try: # check for existing `.` in the extension - extension = extension[1:] if extension[0] == "." else extension + extension = ( + self.options.extension[1:] + if self.options.extension[0] == "." + else self.options.extension + ) except IndexError: # IndexError means the path is not a file, just a path return path_to_file @@ -107,20 +111,14 @@ class ScopedPageCreator(PageCreator): """ def __init__(self) -> None: - # self.base_path = "" - # self.page_path = "" - # self.filename = "" - # self.extension = "" self._all_input_tokens: list[str] = [] self.options: PageCreatorOptions def create_page(self, options: PageCreatorOptions) -> Page: self.options = options - self._all_input_tokens = self._get_input_tokens( - options.template_filename, options.template_path - ) - self._verify_input_tokens(options.user_input, options.example) + self._all_input_tokens = self._get_input_tokens() + self._verify_input_tokens() path = self._convert_to_path( self.options.base_path, @@ -128,34 +126,26 @@ class ScopedPageCreator(PageCreator): self.options.filename, self.options.extension, ) - path = pathlib.Path( - self._substitute_tokens(str(path), options.user_input, options.name) - ) + path = pathlib.Path(self._substitute_tokens(str(path))) - template_contents = self._get_template_contents( - options.template_filename, options.template_path - ) + template_contents = self._get_template_contents() if options.template_filename is not None: - template_contents = self._substitute_tokens( - template_contents, options.user_input, options.name - ) + template_contents = self._substitute_tokens(template_contents) return ScopedPage(path, template_contents) - def _get_template_contents( - self, template_filename: str | None, template_path: str | None - ) -> str: + def _get_template_contents(self) -> str: return ( self._load_template( - self.options.base_path, template_filename, template_path + self.options.base_path, + self.options.template_filename, + self.options.template_path, ) - if template_filename is not None + if self.options.template_filename is not None else "" ) - def _get_input_tokens( - self, template_filename: str | None, template_path: str | None - ) -> list[str]: + def _get_input_tokens(self) -> list[str]: path = str( pathlib.Path( self.options.base_path, @@ -164,24 +154,20 @@ class ScopedPageCreator(PageCreator): self.options.extension, ).expanduser() ) - template_contents = self._get_template_contents( - template_filename, template_path - ) + template_contents = self._get_template_contents() # get the input tokens from both the path and the template all_input_tokens = [] for tokenified_string in (path, template_contents): all_input_tokens.extend(re.findall(r"(\{input\d*\})", tokenified_string)) return sorted(all_input_tokens) - def _verify_input_tokens( - self, user_input: Collection[str] | None, example: str | None - ) -> None: - if len(self._all_input_tokens) > 0 and user_input is None: - if example is not None: + def _verify_input_tokens(self) -> None: + if len(self._all_input_tokens) > 0 and self.options.user_input is None: + if self.options.example is not None: tembo.logger.critical( "Your tembo.config/template specifies %s input tokens, you gave 0. Example command: %s", len(self._all_input_tokens), - example, + self.options.example, ) else: tembo.logger.critical( @@ -189,58 +175,50 @@ class ScopedPageCreator(PageCreator): len(self._all_input_tokens), ) raise SystemExit(1) - if user_input is None: + if self.options.user_input is None: return - if len(self._all_input_tokens) != len(user_input): - if example is not None: + if len(self._all_input_tokens) != len(self.options.user_input): + if self.options.example is not None: tembo.logger.critical( "Your tembo.config/template specifies %s input tokens, you gave %s. Example command: %s", len(self._all_input_tokens), - len(user_input), - example, + len(self.options.user_input), + self.options.example, ) else: tembo.logger.critical( "Your tembo.config/template specifies %s input tokens, you gave %s.", len(self._all_input_tokens), - len(user_input), + len(self.options.user_input), ) raise SystemExit(1) return - def _substitute_tokens( - self, - tokenified_string: str, - user_input: Collection[str] | None, - name: str, - ) -> str: + def _substitute_tokens(self, tokenified_string: str) -> str: """For a tokened string, substitute input, name and date tokens.""" - tokenified_string = self.__substitute_input_tokens( - tokenified_string, user_input - ) - tokenified_string = self.__substitute_name_tokens(tokenified_string, name) + tokenified_string = self.__substitute_input_tokens(tokenified_string) + tokenified_string = self.__substitute_name_tokens(tokenified_string) tokenified_string = self.__substitute_date_tokens(tokenified_string) return tokenified_string - def __substitute_input_tokens( - self, - tokenified_string: str, - user_input: Collection[str] | None, - ) -> str: - if user_input is not None: - for input_value, extracted_token in zip(user_input, self._all_input_tokens): + def __substitute_input_tokens(self, tokenified_string: str) -> str: + if self.options.user_input is not None: + for input_value, extracted_token in zip( + self.options.user_input, self._all_input_tokens + ): tokenified_string = tokenified_string.replace( extracted_token, input_value.replace(" ", "_") ) return tokenified_string - @staticmethod - def __substitute_name_tokens(tokenified_string: str, name: str) -> str: + def __substitute_name_tokens(self, tokenified_string: str) -> str: """Find any `{name}` tokens and substitute for the name value.""" name_extraction = re.findall(r"(\{name\})", tokenified_string) for extracted_input in name_extraction: - tokenified_string = tokenified_string.replace(extracted_input, name) + tokenified_string = tokenified_string.replace( + extracted_input, self.options.name + ) return tokenified_string @staticmethod From 86ec115c9f990e88e72fc7367c7166f0c013d757 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Sat, 30 Oct 2021 20:24:56 +0100 Subject: [PATCH 3/3] adding latest tests --- TODO.todo | 5 ++- dev/notes/test.md | 39 +++++++++++++++-- tembo/exceptions.py | 4 ++ tembo/journal/pages.py | 62 ++++++++++++---------------- tests/test_journal/old_test_pages.py | 6 +-- tests/test_journal/test_pages.py | 26 ++++++++++-- 6 files changed, 94 insertions(+), 48 deletions(-) diff --git a/TODO.todo b/TODO.todo index efe3316..f7c5ce0 100644 --- a/TODO.todo +++ b/TODO.todo @@ -26,7 +26,10 @@ Documentation: Functionality: ☐ Replace loggers with `click.echo` for command outputs. Keep logging messages for actual logging messages? - ☐ Make options a property on the class, add to abstract + ☐ Refactor the tembo new command so the cli is split out into manageable methods + ☐ Use the complicated CLI example so the tembo new has its own module to define functions in + ☐ Replace all logger errors with exceptions, move logger messages to the cli. + ✔ Make options a property on the class, add to abstract @done(21-10-30 19:31) ☐ Use the python runner Duty ☐ Run tests diff --git a/dev/notes/test.md b/dev/notes/test.md index 607a988..4a1a293 100644 --- a/dev/notes/test.md +++ b/dev/notes/test.md @@ -1,10 +1,10 @@ # testing notes optional: -- template_path +- user_input - example - template_filename -- user_input +- template_path required: - base_path @@ -14,13 +14,44 @@ required: - name -- page using/not using input tokens - page with/without a template -- user input does not match number of input tokens - user input is None - the given base path does not exist +- page using/not using input tokens +- user input does not match number of input tokens + - no user input + - mismatched user input + - with/without example - page using/not using date tokens - page using/not using name tokens - path/page filenames can contain spaces and they are converted + + + +@dataclass +class PageCreatorOptions: + """Options dataclass to create a Page. + + Attributes: + base_path (str): + page_path (str): + filename (str): + extension (str): + name (str): + user_input (Collection[str] | None, optional): + example (str | None, optional): + template_filename (str | None, optional): + template_path (str | None, optional): + """ + + base_path: str + page_path: str + filename: str + extension: str + name: str + user_input: Collection[str] | None = None + example: str | None = None + template_filename: str | None = None + template_path: str | None = None diff --git a/tembo/exceptions.py b/tembo/exceptions.py index a71ea99..9746128 100644 --- a/tembo/exceptions.py +++ b/tembo/exceptions.py @@ -3,3 +3,7 @@ class MismatchedTokenError(Exception): pass + + +class BasePathDoesNotExistError(Exception): + pass diff --git a/tembo/journal/pages.py b/tembo/journal/pages.py index a305d36..4870b47 100644 --- a/tembo/journal/pages.py +++ b/tembo/journal/pages.py @@ -45,11 +45,16 @@ class PageCreator: def __init__(self, options: PageCreatorOptions) -> None: raise NotImplementedError + @property + @abstractmethod + def options(self) -> PageCreatorOptions: + raise NotImplementedError + @abstractmethod def create_page(self, options: PageCreatorOptions) -> Page: raise NotImplementedError - def _convert_to_path(self) -> pathlib.Path: + def _convert_base_path_to_path(self) -> pathlib.Path: # check if Tembo base path exists if not pathlib.Path(self.options.base_path).expanduser().exists(): tembo.logger.critical( @@ -74,27 +79,24 @@ class PageCreator: # return path with a file return path_to_file.with_suffix(f".{extension}") - def _load_template( - self, base_path: str, template_filename: str, template_path: str | None - ) -> str: - # check for overriden template_path - if template_path is not None: - converted_template_path = pathlib.Path(template_path).expanduser() + def _load_template(self) -> str: + if self.options.template_filename is None: + return "" + if self.options.template_path is not None: + converted_template_path = pathlib.Path( + self.options.template_path + ).expanduser() else: - # default template_path is base_path / .templates - converted_template_path = self._convert_to_path( - base_path, ".templates", "", "" - ) - # load the template folder + converted_template_path = pathlib.Path() + file_loader = jinja2.FileSystemLoader(converted_template_path) env = jinja2.Environment(loader=file_loader, autoescape=True) - # load the template contents try: - loaded_template = env.get_template(template_filename) + loaded_template = env.get_template(self.options.template_filename) except TemplateNotFound as template_not_found: tembo.logger.critical( "Template file %s not found - exiting", - str(template_path) + "/" + str(template_not_found.message), + str(self.options.template_path) + "/" + str(template_not_found.message), ) raise SystemExit(1) from template_not_found return loaded_template.render() @@ -112,39 +114,27 @@ class ScopedPageCreator(PageCreator): def __init__(self) -> None: self._all_input_tokens: list[str] = [] - self.options: PageCreatorOptions + self._options: PageCreatorOptions + + @property + def options(self) -> PageCreatorOptions: + return self._options def create_page(self, options: PageCreatorOptions) -> Page: - self.options = options + self._options = options self._all_input_tokens = self._get_input_tokens() self._verify_input_tokens() - path = self._convert_to_path( - self.options.base_path, - self.options.page_path, - self.options.filename, - self.options.extension, - ) + path = self._convert_base_path_to_path() path = pathlib.Path(self._substitute_tokens(str(path))) - template_contents = self._get_template_contents() + template_contents = self._load_template() if options.template_filename is not None: template_contents = self._substitute_tokens(template_contents) return ScopedPage(path, template_contents) - def _get_template_contents(self) -> str: - return ( - self._load_template( - self.options.base_path, - self.options.template_filename, - self.options.template_path, - ) - if self.options.template_filename is not None - else "" - ) - def _get_input_tokens(self) -> list[str]: path = str( pathlib.Path( @@ -154,7 +144,7 @@ class ScopedPageCreator(PageCreator): self.options.extension, ).expanduser() ) - template_contents = self._get_template_contents() + template_contents = self._load_template() # get the input tokens from both the path and the template all_input_tokens = [] for tokenified_string in (path, template_contents): diff --git a/tests/test_journal/old_test_pages.py b/tests/test_journal/old_test_pages.py index 03c62d6..7805193 100644 --- a/tests/test_journal/old_test_pages.py +++ b/tests/test_journal/old_test_pages.py @@ -15,7 +15,7 @@ def test_page_creator_convert_to_path_missing_base_path(caplog): # act with pytest.raises(SystemExit) as system_exit: - PageCreator._convert_to_path( + PageCreator._convert_base_path_to_path( base_path=base_path, page_path=page_path, filename=filename, @@ -50,7 +50,7 @@ def test_page_creator_convert_to_path_full_path_to_file( base_path = tmpdir # act - converted_path = PageCreator._convert_to_path( + converted_path = PageCreator._convert_base_path_to_path( base_path, page_path, filename, extension ) @@ -67,7 +67,7 @@ def test_page_creator_convert_to_path_full_path_no_file(tmpdir): extension = "" # act - converted_path = PageCreator._convert_to_path( + converted_path = PageCreator._convert_base_path_to_path( base_path, page_path, filename, extension ) diff --git a/tests/test_journal/test_pages.py b/tests/test_journal/test_pages.py index abc14c5..d751287 100644 --- a/tests/test_journal/test_pages.py +++ b/tests/test_journal/test_pages.py @@ -1,13 +1,31 @@ import pytest -from tembo.journal.pages import PageCreatorOptions +from tembo.journal.pages import PageCreatorOptions, ScopedPageCreator -def test_scoped_page_creator_create_page_missing_base_path(): +def test_create_page_base_path_does_not_exist(tmpdir, caplog): # arrange - options = PageCreatorOptions() + base_path = str(tmpdir / "nonexistent" / "path") + options = PageCreatorOptions( + base_path=base_path, + page_path="", + filename="", + extension="", + name="", + user_input=None, + example=None, + template_filename=None, + template_path=None, + ) # act + with pytest.raises(SystemExit) as system_exit: + scoped_page_creator = ScopedPageCreator().create_page(options) # assert - pass + assert system_exit.value.code == 1 + assert ( + caplog.records[0].message + == f"Tembo base path of {base_path} does not exist - exiting" + ) + assert caplog.records[0].levelname == "CRITICAL"