Merge branch 'refactor/create_exceptions' into develop

This commit is contained in:
2021-11-01 14:11:24 +00:00
13 changed files with 512 additions and 191 deletions

View File

@@ -13,22 +13,47 @@ Documentation:
☐ Document using `__main__.py` and `cli.py` ☐ Document using `__main__.py` and `cli.py`
Use Duty as an example Use Duty as an example
☐ Document regex usage
☐ Write documentation using `mkdocs` ☐ Write documentation using `mkdocs`
☐ Create a boilerplate `duties.py` for common tasks for future projects. Put in a gist.
☐ Look at how to use github actions ☐ Look at how to use github actions
Use <https://github.com/pdm-project/pdm/tree/main/.github/workflows> for an example Use <https://github.com/pdm-project/pdm/tree/main/.github/workflows> for an example
☐ Build the docs using a github action. ☐ Build the docs using a github action.
☐ Document how to use pytest to read a logging message ☐ Document how to use pytest to read a logging message
<https://stackoverflow.com/questions/53125305/testing-logging-output-with-pytest> <https://stackoverflow.com/questions/53125305/testing-logging-output-with-pytest>
- caplog as fixture
- reading `caplog.records[0].message`
see `_old_test_pages.py`
☐ Document testing value of an exception raised
When you use `with pytest.raises` you can use `.value` to access the attributes
reading `.value.code`
reading `str(.value)`
☐ Document working with exceptions
☐ General pattern - raise exceptions in codebase, catch them in the CLI.
Allows people to use via an API and handle the exceptions themselves.
You can use python builtins but custom exceptions are better for internal control
☐ Capturing exceptions in the CLI.
Access the message of the exception with `.args[0]`.
use `raise SystemExit(1) from exception` in order to gracefully exit
☐ Adding custom args to an exception
Overwrite `__init__`, access them in pytest with `.value.$args`
Access them in a try,except with `raise $excpetion as $name; $name.$arg`
☐ Document using datadir with a module rather than a shared one. Link to tembo as an example. ☐ 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 ☐ Can prospector ignore tests dir? document this in the gist if so
☐ Redo the documentation on a CLI, reorganise and inocropoate all the new tembo layouts
Functionality: Functionality:
☐ Replace loggers with `click.echo` for command outputs. Keep logging messages for actual logging messages? ☐ Replace loggers with `click.echo` for command outputs. Keep logging messages for actual logging messages?
Define a format: [TEMBO:$datetime] $message 🐘 - document this in general python for CLI
☐ Refactor the tembo new command so the cli is split out into manageable methods ☐ 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 ☐ 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. ☐ Replace all logger errors with exceptions, move logger messages to the cli.
☐ How to pass a successful save notification back to the CLI? Return a bool? Or is there some other way?
☐ Replace pendulum with datetime
✔ Make options a property on the class, add to abstract @done(21-10-30 19:31) ✔ Make options a property on the class, add to abstract @done(21-10-30 19:31)
☐ Use the python runner Duty ☐ Use the python runner Duty
<https://github.com/pawamoy/duty> <https://github.com/pawamoy/duty>
@@ -37,6 +62,10 @@ Functionality:
☐ Build docs ☐ Build docs
☐ Document using Duty ☐ Document using Duty
Logging:
☐ Make all internal tembo logs be debug
☐ User can enable them with the config
VSCode: VSCode:
PyInstaller: PyInstaller:
☐ Document build error: <https://github.com/pyenv/pyenv/issues/1095> ☐ Document build error: <https://github.com/pyenv/pyenv/issues/1095>

View File

@@ -1,57 +1,39 @@
# testing notes # testing notes
## options
optional: optional:
- user_input - user_input
- example - example
- template_filename - template_filename
- template_path - template_path
required: required:
- base_path - base_path
- page_path - page_path
- filename - filename
- extension - extension
- name - name
## tests to write
- page with/without a template
- 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 - user input does not match number of input tokens
- no user input - no user input
- mismatched user input - mismatched user input
- with/without example - with/without example
- page using/not using date tokens
- page using/not using name tokens
- dry run
## tests done
- path/page filenames can contain spaces and they are converted - path/page filenames can contain spaces and they are converted
- user input is None
- page using/not using input tokens
- page using/not using date tokens
@dataclass - page using/not using name tokens
class PageCreatorOptions: - page with/without a template
"""Options dataclass to create a Page. - the given base path does not exist
- the given template file does not exist
Attributes: - page already exists
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

View File

@@ -2,6 +2,7 @@ import click
import tembo import tembo
from tembo.journal import pages from tembo.journal import pages
from tembo import exceptions
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
@@ -112,9 +113,40 @@ def new(scope, inputs, dry_run, example):
template_path=tembo.CONFIG.template_path, template_path=tembo.CONFIG.template_path,
) )
if _name_found: if _name_found:
scoped_page = pages.ScopedPageCreator().create_page(page_creator_options) try:
scoped_page.save_to_disk(dry_run=dry_run) scoped_page = pages.ScopedPageCreator(page_creator_options).create_page()
raise SystemExit(0) except exceptions.BasePathDoesNotExistError as base_path_does_not_exist_error:
tembo.logger.critical(base_path_does_not_exist_error)
raise SystemExit(1) from base_path_does_not_exist_error
except exceptions.TemplateFileNotFoundError as template_file_not_found_error:
tembo.logger.critical(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:
tembo.logger.critical(
"Your tembo config.yml/template specifies %s input tokens, you gave %s. Example: %s",
mismatched_token_error.expected,
mismatched_token_error.given,
config_scope["example"],
)
raise SystemExit(1) from mismatched_token_error
tembo.logger.critical(
"Your tembo config.yml/template specifies %s input tokens, you gave %s",
mismatched_token_error.expected,
mismatched_token_error.given,
)
raise SystemExit(1) from mismatched_token_error
if dry_run:
click.echo(cli_message(f"{scoped_page.path} will be created"))
raise SystemExit(0)
try:
scoped_page.save_to_disk()
raise SystemExit(0)
except exceptions.ScopedPageAlreadyExists as scoped_page_already_exists:
cli_message(f"File {scoped_page_already_exists}")
raise SystemExit(0) from scoped_page_already_exists
if not _name_found and len(tembo.CONFIG.scopes) > 0: if not _name_found and len(tembo.CONFIG.scopes) > 0:
# if the name is missing in the config.yml, raise error # if the name is missing in the config.yml, raise error
tembo.logger.warning("Command %s not found in config.yml - exiting", scope) tembo.logger.warning("Command %s not found in config.yml - exiting", scope)
@@ -127,13 +159,17 @@ def new(scope, inputs, dry_run, example):
raise SystemExit(1) raise SystemExit(1)
def cli_message(message: str) -> None:
click.echo(f"[TEMBO] {message} 🐘")
run.add_command(new) run.add_command(new)
run.add_command(list_all) run.add_command(list_all)
if __name__ == "__main__": if __name__ == "__main__":
# new(["meeting", "robs presentation", "meeting on gcp"]) # new(["meeting", "robs presentation", "meeting on gcp"])
new(["meeting", "a", "b", "c", "d"]) new(["meeting", "a", "b", "c", "d", "e"])
# new(["meeting", "robs presentation"]) # new(["meeting", "robs presentation"])
# pyinstaller # pyinstaller

View File

@@ -2,8 +2,19 @@
class MismatchedTokenError(Exception): class MismatchedTokenError(Exception):
pass def __init__(self, expected: int, given: int) -> None:
self.expected = expected
self.given = given
super().__init__()
class BasePathDoesNotExistError(Exception): class BasePathDoesNotExistError(Exception):
pass """Raised if the base path does not exist."""
class TemplateFileNotFoundError(Exception):
"""Raised if the template file does not exist."""
class ScopedPageAlreadyExists(Exception):
"""Raised if the scoped page file already exists."""

View File

@@ -11,6 +11,7 @@ import jinja2
from jinja2.exceptions import TemplateNotFound from jinja2.exceptions import TemplateNotFound
import tembo import tembo
from tembo import exceptions
# TODO: flesh this out with details for the optional args # TODO: flesh this out with details for the optional args
@@ -42,6 +43,7 @@ class PageCreatorOptions:
class PageCreator: class PageCreator:
@abstractmethod
def __init__(self, options: PageCreatorOptions) -> None: def __init__(self, options: PageCreatorOptions) -> None:
raise NotImplementedError raise NotImplementedError
@@ -51,31 +53,27 @@ class PageCreator:
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def create_page(self, options: PageCreatorOptions) -> Page: def create_page(self) -> Page:
raise NotImplementedError raise NotImplementedError
def _convert_base_path_to_path(self) -> pathlib.Path: def _check_base_path_exists(self) -> None:
# check if Tembo base path exists
if not pathlib.Path(self.options.base_path).expanduser().exists(): if not pathlib.Path(self.options.base_path).expanduser().exists():
tembo.logger.critical( raise exceptions.BasePathDoesNotExistError(
"Tembo base path of %s does not exist - exiting", self.options.base_path f"Tembo base path of {self.options.base_path} does not exist."
) )
raise SystemExit(1)
def _convert_base_path_to_path(self) -> pathlib.Path:
path_to_file = ( path_to_file = (
pathlib.Path(self.options.base_path).expanduser() pathlib.Path(self.options.base_path).expanduser()
/ pathlib.Path(self.options.page_path.replace(" ", "_")).expanduser() / pathlib.Path(self.options.page_path.replace(" ", "_")).expanduser()
/ self.options.filename.replace(" ", "_") / self.options.filename.replace(" ", "_")
) )
try: # check for existing `.` in the extension
# check for existing `.` in the extension extension = (
extension = ( self.options.extension[1:]
self.options.extension[1:] if self.options.extension[0] == "."
if self.options.extension[0] == "." else self.options.extension
else self.options.extension )
)
except IndexError:
# IndexError means the path is not a file, just a path
return path_to_file
# return path with a file # return path with a file
return path_to_file.with_suffix(f".{extension}") return path_to_file.with_suffix(f".{extension}")
@@ -87,18 +85,20 @@ class PageCreator:
self.options.template_path self.options.template_path
).expanduser() ).expanduser()
else: else:
converted_template_path = pathlib.Path() converted_template_path = (
pathlib.Path(self.options.base_path).expanduser() / ".templates"
)
file_loader = jinja2.FileSystemLoader(converted_template_path) file_loader = jinja2.FileSystemLoader(converted_template_path)
env = jinja2.Environment(loader=file_loader, autoescape=True) env = jinja2.Environment(loader=file_loader, autoescape=True)
try: try:
loaded_template = env.get_template(self.options.template_filename) loaded_template = env.get_template(self.options.template_filename)
except TemplateNotFound as template_not_found: except TemplateNotFound as template_not_found:
tembo.logger.critical( _template_file = f"{converted_template_path}/{template_not_found.args[0]}"
"Template file %s not found - exiting", raise exceptions.TemplateFileNotFoundError(
str(self.options.template_path) + "/" + str(template_not_found.message), f"Template file {_template_file} does not exist."
) ) from template_not_found
raise SystemExit(1) from template_not_found
return loaded_template.render() return loaded_template.render()
@@ -112,16 +112,16 @@ class ScopedPageCreator(PageCreator):
extension (str): extension of file. extension (str): extension of file.
""" """
def __init__(self) -> None: def __init__(self, options: PageCreatorOptions) -> None:
self._all_input_tokens: list[str] = [] self._all_input_tokens: list[str] = []
self._options: PageCreatorOptions self._options = options
@property @property
def options(self) -> PageCreatorOptions: def options(self) -> PageCreatorOptions:
return self._options return self._options
def create_page(self, options: PageCreatorOptions) -> Page: def create_page(self) -> Page:
self._options = options self._check_base_path_exists()
self._all_input_tokens = self._get_input_tokens() self._all_input_tokens = self._get_input_tokens()
self._verify_input_tokens() self._verify_input_tokens()
@@ -130,7 +130,7 @@ class ScopedPageCreator(PageCreator):
path = pathlib.Path(self._substitute_tokens(str(path))) path = pathlib.Path(self._substitute_tokens(str(path)))
template_contents = self._load_template() template_contents = self._load_template()
if options.template_filename is not None: if self.options.template_filename is not None:
template_contents = self._substitute_tokens(template_contents) template_contents = self._substitute_tokens(template_contents)
return ScopedPage(path, template_contents) return ScopedPage(path, template_contents)
@@ -141,8 +141,9 @@ class ScopedPageCreator(PageCreator):
self.options.base_path, self.options.base_path,
self.options.page_path, self.options.page_path,
self.options.filename, self.options.filename,
self.options.extension, )
).expanduser() .expanduser()
.with_suffix(f".{self.options.extension}")
) )
template_contents = self._load_template() template_contents = self._load_template()
# get the input tokens from both the path and the template # get the input tokens from both the path and the template
@@ -153,35 +154,16 @@ class ScopedPageCreator(PageCreator):
def _verify_input_tokens(self) -> None: def _verify_input_tokens(self) -> None:
if len(self._all_input_tokens) > 0 and self.options.user_input is None: if len(self._all_input_tokens) > 0 and self.options.user_input is None:
if self.options.example is not None: raise exceptions.MismatchedTokenError(
tembo.logger.critical( expected=len(self._all_input_tokens), given=0
"Your tembo.config/template specifies %s input tokens, you gave 0. Example command: %s", )
len(self._all_input_tokens),
self.options.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 self.options.user_input is None: if self.options.user_input is None:
return return
if len(self._all_input_tokens) != len(self.options.user_input): if len(self._all_input_tokens) != len(self.options.user_input):
if self.options.example is not None: raise exceptions.MismatchedTokenError(
tembo.logger.critical( expected=len(self._all_input_tokens),
"Your tembo.config/template specifies %s input tokens, you gave %s. Example command: %s", given=len(self.options.user_input),
len(self._all_input_tokens), )
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(self.options.user_input),
)
raise SystemExit(1)
return return
def _substitute_tokens(self, tokenified_string: str) -> str: def _substitute_tokens(self, tokenified_string: str) -> str:
@@ -235,7 +217,7 @@ class Page(metaclass=ABCMeta):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def save_to_disk(self, dry_run: bool) -> None: def save_to_disk(self) -> None:
raise NotImplementedError raise NotImplementedError
@@ -260,7 +242,7 @@ class ScopedPage(Page):
def __str__(self) -> str: def __str__(self) -> str:
return f"ScopedPage({self.path})" return f"ScopedPage({self.path})"
def save_to_disk(self, dry_run: bool = False) -> None: def save_to_disk(self) -> None:
"""Save the scoped page to disk and write the `page_content`. """Save the scoped page to disk and write the `page_content`.
If the page already exists a message will be logged to stdout and no file If the page already exists a message will be logged to stdout and no file
@@ -276,91 +258,15 @@ class ScopedPage(Page):
SystemExit: Exit code 0 if dry run is `True`, page is successfully saved SystemExit: Exit code 0 if dry run is `True`, page is successfully saved
or if page already exists. or if page already exists.
""" """
if dry_run: # TODO: move this functionality to the CLI so the page is created and the message
tembo.logger.info("%s will be created", self.path) # returned to the user from the CLI.
raise SystemExit(0)
# create the parent directories # create the parent directories
scoped_note_file = pathlib.Path(self.path) scoped_page_file = pathlib.Path(self.path)
scoped_note_file.parents[0].mkdir(parents=True, exist_ok=True) scoped_page_file.parents[0].mkdir(parents=True, exist_ok=True)
if not scoped_note_file.exists(): if scoped_page_file.exists():
with scoped_note_file.open("w", encoding="utf-8") as scoped_page: raise exceptions.ScopedPageAlreadyExists(f"{self.path} already exists")
scoped_page.write(self.page_content)
tembo.logger.info("Saved %s to disk", self.path)
else:
tembo.logger.info("%s already exists - skipping.", self.path)
raise SystemExit(0)
with scoped_page_file.open("w", encoding="utf-8") as scoped_page:
if __name__ == "__main__": scoped_page.write(self.page_content)
c = ScopedPageCreator() # TODO: pass this back somehow
# # raises error tembo.logger.info("Saved %s to disk", self.path)
# # print(c._substitute_tokens("scratchpad/{input0}-{d:DD-MM-YYYY}-{d:dddd}-{d:A}-file.md", None))
# print(
# c._substitute_tokens(
# "scratchpad/{d:A}/{d:DD-MM-YYYY}-{d:dddd}-{d:A}-file-{input0}.md", ("last",)
# )
# )
# print(
# c.create_page(
# "~/tembo",
# "{name}",
# "{input0}-{input1}-file",
# "md",
# "scratchpad",
# ("first", "second"),
# )
# )
# print(
# c.create_page(
# "~/tembo",
# "{name}/{d:MMMM-YY}",
# "{input0}-{d:DD-MM-YYYY}-{d:dddd}-{d:A}-file",
# "md",
# "scratchpad",
# ("first",),
# )
# )
# print(
# c.create_page(
# "~/tembo",
# "{name}/{d:DD-MM-YYYY}-{d:dddd}-{d:A}",
# "file",
# "md",
# "scratchpad",
# None,
# )
# )
# print(
# c.create_page(
# "~/tembo",
# "{name}/{d:A}/{d:DD-MM-YYYY}-{d:dddd}-{d:A}",
# "file-{input0}-{name}",
# ".md",
# "meeting",
# ("last",),
# "scratchpad.md.tpl",
# )
# )
# test_page_with_template = c.create_page(
# "~/tembo",
# "{name}/{d:A}/{d:DD-MM-YYYY}-{d:dddd}-{d:A}",
# "file-{input0}-{name}",
# ".md",
# "meeting",
# ("last",),
# "scratchpad.md.tpl",
# )
# print(test_page_with_template)
# test_page_with_template.save_to_disk(False)
# print(
# c.create_page(
# "~/tembo",
# "{name}/{d:A}/{d:DD-MM-YYYY}-{d:dddd}-{d:A}",
# "file-{input0}-{name}",
# ".md",
# "meeting",
# ("last",),
# "scratchpad_templates/scratchpad.md.tpl",
# )
# )

View File

@@ -1,9 +1,16 @@
from datetime import date
import pathlib
import pytest import pytest
from tembo.journal.pages import PageCreatorOptions, ScopedPageCreator from tembo.journal.pages import PageCreatorOptions, ScopedPageCreator
from tembo import exceptions
def test_create_page_base_path_does_not_exist(tmpdir, caplog): DATE_TODAY = date.today().strftime("%d-%m-%Y")
def test_create_page_base_path_does_not_exist(tmpdir):
# arrange # arrange
base_path = str(tmpdir / "nonexistent" / "path") base_path = str(tmpdir / "nonexistent" / "path")
options = PageCreatorOptions( options = PageCreatorOptions(
@@ -19,13 +26,356 @@ def test_create_page_base_path_does_not_exist(tmpdir, caplog):
) )
# act # act
with pytest.raises(SystemExit) as system_exit: with pytest.raises(
scoped_page_creator = ScopedPageCreator().create_page(options) exceptions.BasePathDoesNotExistError
) as base_path_does_not_exist_error:
scoped_page = ScopedPageCreator(options).create_page()
# assert # assert
assert system_exit.value.code == 1
assert ( assert (
caplog.records[0].message str(base_path_does_not_exist_error.value)
== f"Tembo base path of {base_path} does not exist - exiting" == f"Tembo base path of {base_path} does not exist."
) )
assert caplog.records[0].levelname == "CRITICAL"
@pytest.mark.parametrize("template_path", [(None), ("/nonexistent/path")])
def test_create_page_template_file_does_not_exist(template_path, tmpdir):
# arrange
options = PageCreatorOptions(
base_path=str(tmpdir),
page_path="some_path",
filename="some_filename",
extension="some_extension",
name="some_name",
user_input=None,
example=None,
template_filename="template.md.tpl",
template_path=template_path,
)
# act
with pytest.raises(
exceptions.TemplateFileNotFoundError
) as template_file_not_found_error:
scoped_page = ScopedPageCreator(options).create_page()
# assert
if template_path is None:
assert str(template_file_not_found_error.value) == (
f"Template file {options.base_path}/.templates/{options.template_filename} does not exist."
)
else:
assert str(template_file_not_found_error.value) == (
f"Template file {template_path}/{options.template_filename} does not exist."
)
def test_create_page_already_exists(datadir):
# arrange
options = PageCreatorOptions(
base_path=str(datadir),
page_path="does_exist",
filename="some_note",
extension="md",
name="some_name",
user_input=None,
example=None,
template_filename=None,
template_path=None,
)
scoped_page_file = (
pathlib.Path(options.base_path) / options.page_path / options.filename
).with_suffix(f".{options.extension}")
# act
scoped_page = ScopedPageCreator(options).create_page()
with pytest.raises(exceptions.ScopedPageAlreadyExists) as page_already_exists:
scoped_page.save_to_disk()
# assert
assert scoped_page_file.exists()
assert str(page_already_exists.value) == f"{scoped_page_file} already exists"
with scoped_page_file.open("r", encoding="utf-8") as scoped_page_contents:
assert scoped_page_contents.readlines() == ["this file already exists\n"]
def test_create_page_without_template(tmpdir, caplog):
# arrange
options = PageCreatorOptions(
base_path=str(tmpdir),
page_path="some_path",
filename="some_filename",
extension="some_extension",
name="some_name",
user_input=None,
example=None,
template_filename=None,
template_path=None,
)
# TODO: copy this pattern creation into the other tests
scoped_page_file = (
pathlib.Path(options.base_path) / options.page_path / options.filename
).with_suffix(f".{options.extension}")
# act
scoped_page = ScopedPageCreator(options).create_page()
scoped_page.save_to_disk()
# assert
assert scoped_page_file.exists()
assert caplog.records[0].message == f"Saved {scoped_page_file} to disk"
with scoped_page_file.open("r", encoding="utf-8") as scoped_page_contents:
assert scoped_page_contents.readlines() == []
def test_create_page_with_template(datadir, caplog):
# arrange
options = PageCreatorOptions(
base_path=str(datadir),
page_path="some_path",
filename="some_note",
extension="md",
name="some_name",
user_input=None,
example=None,
template_filename="some_template_no_tokens.md.tpl",
template_path=None,
)
scoped_page_file = (
pathlib.Path(options.base_path) / options.page_path / options.filename
).with_suffix(f".{options.extension}")
# act
scoped_page = ScopedPageCreator(options).create_page()
scoped_page.save_to_disk()
# assert
assert scoped_page_file.exists()
assert caplog.records[0].message == f"Saved {scoped_page_file} to disk"
with scoped_page_file.open("r", encoding="utf-8") as scoped_page_contents:
assert scoped_page_contents.readlines() == [
"scoped page file\n",
"\n",
"no tokens",
]
@pytest.mark.parametrize(
"user_input,template_filename,page_contents",
[
(None, "some_template_date_tokens.md.tpl", f"some date token: {DATE_TODAY}"),
(
("first_input", "second_input"),
"some_template_input_tokens.md.tpl",
"some input tokens second_input first_input",
),
(None, "some_template_name_tokens.md.tpl", "some name token some_name"),
],
)
def test_create_tokened_page_tokens_in_template(
datadir, caplog, user_input, template_filename, page_contents
):
# arrange
options = PageCreatorOptions(
base_path=str(datadir),
page_path="some_path",
filename="some_note",
extension="md",
name="some_name",
user_input=user_input,
example=None,
template_filename=template_filename,
template_path=None,
)
scoped_page_file = (
pathlib.Path(options.base_path) / options.page_path / options.filename
).with_suffix(f".{options.extension}")
# act
scoped_page = ScopedPageCreator(options).create_page()
scoped_page.save_to_disk()
# assert
assert scoped_page_file.exists()
assert caplog.records[0].message == f"Saved {scoped_page_file} to disk"
with scoped_page_file.open("r", encoding="utf-8") as scoped_page_contents:
assert scoped_page_contents.readline() == page_contents
@pytest.mark.parametrize(
"user_input,filename,tokened_filename",
[
(None, "date_token_{d:%d-%m-%Y}", f"date_token_{DATE_TODAY}"),
(None, "name_token_{name}", "name_token_some_name"),
(
("first_input", "second input"),
"input_token_{input1}_{input0}",
"input_token_second_input_first_input",
),
],
)
def test_create_tokened_page_tokens_in_filename(
datadir, caplog, user_input, filename, tokened_filename
):
# arrange
options = PageCreatorOptions(
base_path=str(datadir),
page_path="some_path",
filename=filename,
extension="md",
name="some_name",
user_input=user_input,
example=None,
template_filename=None,
template_path=None,
)
scoped_page_file = (
pathlib.Path(options.base_path) / options.page_path / tokened_filename
).with_suffix(f".{options.extension}")
# act
scoped_page = ScopedPageCreator(options).create_page()
scoped_page.save_to_disk()
# assert
assert scoped_page_file.exists()
assert caplog.records[0].message == f"Saved {scoped_page_file} to disk"
def test_create_tokened_page_input_tokens_preserve_order(datadir, caplog):
# arrange
tokened_filename = "input_token_fourth_input_first_input"
options = PageCreatorOptions(
base_path=str(datadir),
page_path="some_path",
filename="input_token_{input3}_{input0}",
extension="md",
name="some_name",
user_input=("first_input", "second_input", "third_input", "fourth_input"),
example=None,
template_filename="some_template_input_tokens_preserve_order.md.tpl",
template_path=None,
)
scoped_page_file = (
pathlib.Path(options.base_path) / options.page_path / tokened_filename
).with_suffix(f".{options.extension}")
# act
scoped_page = ScopedPageCreator(options).create_page()
scoped_page.save_to_disk()
# assert
assert scoped_page_file.exists()
assert caplog.records[0].message == f"Saved {scoped_page_file} to disk"
with scoped_page_file.open(mode="r", encoding="utf-8") as scoped_page_contents:
assert scoped_page_contents.readline() == "third_input second_input"
@pytest.mark.parametrize(
"user_input,expected,given",
[
(None, 3, 0),
(("first_input", "second_input"), 3, 2),
(("first_input", "second_input", "third_input", "fourth_input"), 3, 4),
],
)
def test_create_page_mismatched_tokens(tmpdir, user_input, expected, given):
# arrange
options = PageCreatorOptions(
base_path=str(tmpdir),
page_path="some_path",
filename="input_token_{input0}_{input1}_{input2}",
extension="md",
name="some_name",
user_input=user_input,
example=None,
template_filename=None,
template_path=None,
)
# act
with pytest.raises(exceptions.MismatchedTokenError) as mismatched_token_error:
scoped_page = ScopedPageCreator(options).create_page()
# assert
assert mismatched_token_error.value.expected == expected
assert mismatched_token_error.value.given == given
def test_create_page_spaces_in_path(tmpdir, caplog):
# arrange
options = PageCreatorOptions(
base_path=str(tmpdir),
page_path="some path with a space",
filename="some filename with a space",
extension="md",
name="some_name",
user_input=None,
example=None,
template_filename=None,
template_path=None,
)
scoped_page_file = (
pathlib.Path(options.base_path)
/ options.page_path.replace(" ", "_")
/ options.filename.replace(" ", "_")
).with_suffix(f".{options.extension}")
# act
scoped_page = ScopedPageCreator(options).create_page()
scoped_page.save_to_disk()
# assert
assert scoped_page_file.exists()
assert caplog.records[0].message == f"Saved {scoped_page_file} to disk"
def test_create_page_dot_in_extension(tmpdir, caplog):
# arrange
options = PageCreatorOptions(
base_path=str(tmpdir),
page_path="some_path",
filename="some_filename",
extension=".md",
name="some_name",
user_input=None,
example=None,
template_filename=None,
template_path=None,
)
scoped_page_file = (
pathlib.Path(options.base_path) / options.page_path / options.filename
).with_suffix(f".{options.extension[1:]}")
# act
scoped_page = ScopedPageCreator(options).create_page()
scoped_page.save_to_disk()
# assert
assert scoped_page_file.exists()
assert caplog.records[0].message == f"Saved {scoped_page_file} to disk"
def test_create_page_str_representation(tmpdir):
# arrange
options = PageCreatorOptions(
base_path=str(tmpdir),
page_path="some_path",
filename="some_filename",
extension="md",
name="some_name",
user_input=None,
example=None,
template_filename=None,
template_path=None,
)
scoped_page_file = (
pathlib.Path(options.base_path) / options.page_path / options.filename
).with_suffix(f".{options.extension}")
# act
scoped_page = ScopedPageCreator(options).create_page()
# assert
assert str(scoped_page) == f"ScopedPage({scoped_page_file})"

View File

@@ -1 +0,0 @@
template contents

View File

@@ -0,0 +1 @@
some date token: {d:%d-%m-%Y}

View File

@@ -0,0 +1 @@
some input tokens {input1} {input0}

View File

@@ -0,0 +1 @@
some name token {name}

View File

@@ -0,0 +1,3 @@
scoped page file
no tokens

View File

@@ -0,0 +1 @@
this file already exists