mirror of
https://github.com/dtomlinson91/tembo.git
synced 2025-12-22 07:55:45 +00:00
Merge branch 'bugfix/token_mismatch' into develop
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -137,4 +137,6 @@ dmypy.json
|
|||||||
# Cython debug symbols
|
# Cython debug symbols
|
||||||
cython_debug/
|
cython_debug/
|
||||||
|
|
||||||
|
# custom
|
||||||
.vscode/
|
.vscode/
|
||||||
|
**/__pycache__
|
||||||
|
|||||||
47
TODO.todo
47
TODO.todo
@@ -2,32 +2,49 @@ 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
|
||||||
Using the logger, initialising with the config path etc
|
Using the logger, initialising with the config path etc
|
||||||
✘ Uses Pendulum tokens: https://pendulum.eustace.io/docs/#tokens @cancelled(21-10-24 05:32)
|
✘ Uses Pendulum tokens: https://pendulum.eustace.io/docs/#tokens @cancelled(21-10-24 05:32)
|
||||||
☐ Uses `strftime` tokens: <https://strftime.org>
|
☐ Uses `strftime` tokens: <https://strftime.org>
|
||||||
☐ Document latest typing.
|
☐ Document latest typing.
|
||||||
☐ Using from `__future__` with `|`
|
☐ Using from `__future__` with `|`
|
||||||
☐ `using Tuple[str, ...]`
|
☐ `using Tuple[str, ...]`
|
||||||
☐ `Sequence` vs `Collection`
|
☐ `Sequence` vs `Collection`
|
||||||
☐ 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/>)
|
||||||
Tembo:
|
|
||||||
☐ Document creating new Tembo config
|
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:
|
||||||
|
☐ Document creating new Tembo config
|
||||||
☐ ~/tembo needs creating
|
☐ ~/tembo needs creating
|
||||||
☐ ~/tembo/.config
|
☐ ~/tembo/.config
|
||||||
☐ ~/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
|
||||||
|
|||||||
@@ -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"])
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
"""Tembo exceptions."""
|
"""Tembo exceptions."""
|
||||||
|
|
||||||
|
|
||||||
|
class MismatchedTokenError(Exception):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -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,9 +220,10 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user