Merge branch 'bugfix/token_mismatch' into develop

This commit is contained in:
2021-10-25 04:24:34 +01:00
8 changed files with 97 additions and 61 deletions

2
.gitignore vendored
View File

@@ -137,4 +137,6 @@ dmypy.json
# Cython debug symbols # Cython debug symbols
cython_debug/ cython_debug/
# custom
.vscode/ .vscode/
**/__pycache__

View File

@@ -2,15 +2,24 @@ Functionality:
☐ Handle case where there are no scopes in the config and command is invoked. ☐ Handle case where there are no scopes in the config and command is invoked.
☐ Have an `--example` flag to `new` that prints an example given in the `config.yml` ☐ Have an `--example` flag to `new` that prints an example given in the `config.yml`
☐ Should be a `tembo new --list` to list all possible names. ☐ Should be a `tembo new --list` to list all possible names.
☐ When template not found, raise a Tembo error
☐ Convert spaces to underscores in filepath
currently raises jinja2.exceptions.TemplateNotFound on line 62, in _load_template
☐ Add update notification?
check pypi for latest version and compare to current
✔ `TEMBO_CONFIG` should follow same pattern as other env vars and be a python string when read in @done(21-10-24 05:31) ✔ `TEMBO_CONFIG` should follow same pattern as other env vars and be a python string when read in @done(21-10-24 05:31)
Bug:
☐ tokens
Say we have input0 and input3 in file path
and we have input1 and input2 in template
it only recognises this as 2 inputs total, not four.
passing in tembo new meeting a b will work and input0=input1, input3=input2
VSCode: VSCode:
☐ Look at <https://github.com/CodeWithSwastik/vscode-ext> ☐ Look at <https://github.com/CodeWithSwastik/vscode-ext>
Logging: Logging:
☐ How to raise + debug an exception?
☐ Document how to raise a logger.critical instead of exception
in a try, except you can just do logger.critical(exec_info=1) to print the stack
Documentation: Documentation:
☐ Document usage of Panaetius in a module ☐ Document usage of Panaetius in a module
@@ -24,6 +33,12 @@ Documentation:
☐ Document how to do docstrings in python. Don't document `__init__` do it in class. ☐ Document how to do docstrings in python. Don't document `__init__` do it in class.
Should update the default gist to hide the `__init__` messages Should update the default gist to hide the `__init__` messages
☐ Document using jinja2 briefly and link to Tembo (link to <https://zetcode.com/python/jinja/>) ☐ Document using jinja2 briefly and link to Tembo (link to <https://zetcode.com/python/jinja/>)
Logging:
☐ How to raise + debug an exception?
☐ Document how to raise a logger.critical instead of exception
in a try, except you can just do logger.critical(exec_info=1) to print the stack
Tembo: Tembo:
☐ Document creating new Tembo config ☐ Document creating new Tembo config
☐ ~/tembo needs creating ☐ ~/tembo needs creating
@@ -31,3 +46,5 @@ Documentation:
☐ ~/tembo/.templates ☐ ~/tembo/.templates
☐ ~/tembo/logs ☐ ~/tembo/logs
☐ Document how to overwrite these with ENV vars ☐ Document how to overwrite these with ENV vars
☐ have a git repo with all the above already configured and walk user through
clone the repo, delete .git, git init, configure and add git origin

View File

@@ -46,7 +46,6 @@ def new(scope, inputs, dry_run):
template_filename=str(user_scope["template_filename"]) template_filename=str(user_scope["template_filename"])
) )
scoped_page.save_to_disk(dry_run=dry_run) scoped_page.save_to_disk(dry_run=dry_run)
tembo.logger.info("Saved %s to disk", scoped_page.path)
raise SystemExit(0) raise SystemExit(0)
if not _name_found and len(tembo.CONFIG.scopes) > 0: if not _name_found and len(tembo.CONFIG.scopes) > 0:
tembo.logger.warning("Command %s not found in config.yml - exiting", scope) tembo.logger.warning("Command %s not found in config.yml - exiting", scope)
@@ -62,5 +61,6 @@ run.add_command(new)
if __name__ == "__main__": if __name__ == "__main__":
# BUG: fix this bug where input tokens are mismatched # BUG: fix this bug where input tokens are mismatched
new(["meeting", "robs presentation", "meeting on gcp"]) # new(["meeting", "robs presentation", "meeting on gcp"])
new(["meeting", "a", "b", "c", "d"])
# new(["meeting", "robs presentation"]) # new(["meeting", "robs presentation"])

View File

@@ -1 +1,5 @@
"""Tembo exceptions.""" """Tembo exceptions."""
class MismatchedTokenError(Exception):
pass

View File

@@ -68,6 +68,7 @@ class ScopedPageCreator(PageCreator):
self.page_path = "" self.page_path = ""
self.filename = "" self.filename = ""
self.extension = "" self.extension = ""
self._all_input_tokens: list[str] = []
def create_page( def create_page(
self, self,
@@ -83,12 +84,22 @@ class ScopedPageCreator(PageCreator):
self.page_path = page_path self.page_path = page_path
self.filename = filename self.filename = filename
self.extension = extension self.extension = extension
# verify the user input length matches the number of input tokens in the
# config/templates
self._all_input_tokens = self._get_input_tokens(template_filename)
self._verify_input_tokens(user_input)
# get the path of the scoped page # get the path of the scoped page
path = self._convert_to_path( path = self._convert_to_path(
self.base_path, self.page_path, self.filename, self.extension self.base_path, self.page_path, self.filename, self.extension
) )
# substitute tokens in the filepath # substitute tokens in the filepath
path = pathlib.Path(self._substitute_tokens(str(path), user_input, name)) path = pathlib.Path(
self._substitute_tokens(str(path), user_input, name)
)
# get the template file # get the template file
if template_filename is not None: if template_filename is not None:
# load the template file contents and substitute tokens # load the template file contents and substitute tokens
@@ -98,15 +109,45 @@ class ScopedPageCreator(PageCreator):
) )
else: else:
template_contents = "" template_contents = ""
return ScopedPage(path, template_contents) return ScopedPage(path, template_contents)
def _get_input_tokens(self, template_filename: str | None) -> list[str]:
path = str(
pathlib.Path(
self.base_path, self.page_path, self.filename, self.extension
).expanduser()
)
if template_filename is not None:
template_contents = self._load_template(self.base_path, template_filename)
else:
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: Tuple[str, ...] | Tuple[()]) -> None:
if len(self._all_input_tokens) != len(user_input):
logger.critical(
"Your config/template specifies %s input tokens, you gave %s",
len(self._all_input_tokens),
len(user_input),
)
raise SystemExit(1)
def _substitute_tokens( def _substitute_tokens(
self, self,
tokenified_string: str, tokenified_string: str,
user_input: Tuple[str, ...] | Tuple[()], user_input: Tuple[str, ...] | Tuple[()],
name: str, name: str,
) -> str: ) -> str:
# for a tokened string, substitute input, name and date tokens """For a tokened string, substitute input, name and date tokens."""
# TODO: fn to get tokens from file and template
# tokenified_string = self.__substitute_input_tokens(
# tokenified_string, user_input, input_token_type
# )
tokenified_string = self.__substitute_input_tokens( tokenified_string = self.__substitute_input_tokens(
tokenified_string, user_input tokenified_string, user_input
) )
@@ -114,6 +155,15 @@ class ScopedPageCreator(PageCreator):
tokenified_string = self.__substitute_date_tokens(tokenified_string) tokenified_string = self.__substitute_date_tokens(tokenified_string)
return tokenified_string return tokenified_string
def __substitute_input_tokens(
self,
tokenified_string: str,
user_input: Tuple[str, ...] | Tuple[()],
) -> str:
for input_value, extracted_token in zip(user_input, self._all_input_tokens):
tokenified_string = tokenified_string.replace(extracted_token, input_value)
return tokenified_string
@staticmethod @staticmethod
def __substitute_name_tokens(tokenified_string: str, name: str) -> str: def __substitute_name_tokens(tokenified_string: str, name: str) -> str:
# find any {name} tokens and substitute for the name value # find any {name} tokens and substitute for the name value
@@ -122,49 +172,11 @@ class ScopedPageCreator(PageCreator):
tokenified_string = tokenified_string.replace(extracted_input, name) tokenified_string = tokenified_string.replace(extracted_input, name)
return tokenified_string return tokenified_string
@staticmethod
def __substitute_input_tokens(
tokenified_string: str, user_input: Tuple[str, ...] | Tuple[()]
) -> str:
# find {inputN} tokens in string
input_extraction = re.findall(r"(\{input\d*\})", tokenified_string)
if len(user_input) == 0:
# if there's no user input, but the regex matches, raise error
if len(input_extraction) > 0:
logger.critical(
"Your config/template specifies %s input tokens, you gave 0 "
"- exiting",
len(input_extraction),
)
raise SystemExit(1)
# if there aren't any tokens in the string, return the string
return tokenified_string
# if there is user input, check the number of tokens match the number passed in
if (
len(input_extraction) > 0
and len(input_extraction) != len(user_input)
and len(user_input) > 0
):
# if there are input matches and they don't equal the number of input
# tokens, raise error
logger.critical(
"Your config/template specifies %s input tokens, you gave %s - exiting",
len(input_extraction),
len(user_input),
)
raise SystemExit(1)
# if the length of both the input matches and the number of tokens match then
# substitute each token with the user's input
if len(user_input) > 0:
for extracted_input, input_value in zip(input_extraction, user_input):
tokenified_string = tokenified_string.replace(
extracted_input, input_value
)
return tokenified_string
@staticmethod @staticmethod
def __substitute_date_tokens(tokenified_string: str) -> str: def __substitute_date_tokens(tokenified_string: str) -> str:
# find any {d:%d-%M-%Y} tokens """Find any {d:%d-%M-%Y} tokens."""
# extract the full token string
date_extraction_token = re.findall(r"(\{d\:[^}]*\})", tokenified_string) date_extraction_token = re.findall(r"(\{d\:[^}]*\})", tokenified_string)
for extracted_token in date_extraction_token: for extracted_token in date_extraction_token:
# extract the inner %d-%M-%Y only # extract the inner %d-%M-%Y only
@@ -208,6 +220,7 @@ class ScopedPage(Page):
if not scoped_note_file.exists(): if not scoped_note_file.exists():
with scoped_note_file.open("w", encoding="utf-8") as scoped_page: with scoped_note_file.open("w", encoding="utf-8") as scoped_page:
scoped_page.write(self.page_content) scoped_page.write(self.page_content)
logger.info("Saved %s to disk", self.path)
else: else:
logger.info("%s already exists - skipping.", self.path) logger.info("%s already exists - skipping.", self.path)
raise SystemExit(0) raise SystemExit(0)