From 560991325c4da2fb110c2e479b84f31d9c380651 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Sat, 23 Oct 2021 21:11:20 +0100 Subject: [PATCH 1/4] updating todo --- TODO.todo | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/TODO.todo b/TODO.todo index f0a4d9d..c1e8a91 100644 --- a/TODO.todo +++ b/TODO.todo @@ -2,6 +2,7 @@ Functionality: ☐ 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` ☐ Should be a `tembo new --list` to list all possible names. + ☐ `TEMBO_CONFIG` should follow same pattern as other env vars and be a python string when read in VSCode: ☐ Look at @@ -19,4 +20,12 @@ Documentation: ☐ Using from `__future__` with `|` ☐ `using Tuple[str, ...]` ☐ `Sequence` vs `Collection` + ☐ 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 ☐ Document using jinja2 briefly and link to Tembo (link to ) + Tembo: + ☐ Document creating new Tembo config + ☐ ~/tembo needs creating + ☐ ~/tembo/.config + ☐ ~/tembo/.templates + ☐ Document how to overwrite these with ENV vars From 4e6efa326ed9d4a77f432a439295843fa49a9059 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Sat, 23 Oct 2021 21:12:04 +0100 Subject: [PATCH 2/4] changing default config location default config is ~/tembo/.config --- tembo/__init__.py | 6 +++--- tembo/cli.py | 12 +++++++++--- tembo/journal/pages.py | 29 ++++++++++++++--------------- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/tembo/__init__.py b/tembo/__init__.py index 5e73856..a9fc698 100644 --- a/tembo/__init__.py +++ b/tembo/__init__.py @@ -2,14 +2,14 @@ import os import panaetius -if config_path := os.environ.get("TEMBO_CONFIG") is not None: +if (config_path := os.environ.get("TEMBO_CONFIG")) is not None: CONFIG = panaetius.Config("tembo", config_path) else: - CONFIG = panaetius.Config("tembo") + CONFIG = panaetius.Config("tembo", "~/tembo/.config", skip_header_init=True) panaetius.set_config(CONFIG, "base_path", "~/tembo") -panaetius.set_config(CONFIG, "template_path", "~/tembo/templates") +panaetius.set_config(CONFIG, "template_path", "~/tembo/.templates") panaetius.set_config(CONFIG, "scopes", {}) panaetius.set_config(CONFIG, "logging.level", "DEBUG") panaetius.set_config(CONFIG, "logging.path") diff --git a/tembo/cli.py b/tembo/cli.py index 88cb88e..4275486 100644 --- a/tembo/cli.py +++ b/tembo/cli.py @@ -12,8 +12,6 @@ def run(): """ Tembo - an organiser for work notes. """ - print(tembo.CONFIG.base_path) - # print(tembo.CONFIG.scopes) @click.command(options_metavar="") @@ -33,7 +31,6 @@ def new(scope, inputs): Example: tembo new meeting my_presentation """ - for user_scope in tembo.CONFIG.scopes: if user_scope["name"] == scope: scoped_page = pages.ScopedPageCreator().create_page( @@ -47,6 +44,15 @@ def new(scope, inputs): ) scoped_page.save_to_disk() tembo.logger.info("Saved %s to disk", scoped_page) + raise SystemExit(0) + tembo.logger.critical( + "No config.yml found in %s - exiting", tembo.CONFIG.config_path + ) + raise SystemExit(1) run.add_command(new) + + +if __name__ == "__main__": + new(["scratchpad"], ()) diff --git a/tembo/journal/pages.py b/tembo/journal/pages.py index 74c85e5..cc346c1 100644 --- a/tembo/journal/pages.py +++ b/tembo/journal/pages.py @@ -19,7 +19,7 @@ class PageCreator: filename: str, extension: str, name: str, - user_input: tuple[str, ...], + user_input: tuple[str, ...] | None, template_filename: str | None = None, ) -> Page: pass @@ -53,7 +53,6 @@ class PageCreator: else: # default template_path is base_path / templates template_path = self._convert_to_path(base_path, "templates", "", "") - print(template_path, template_filename) # load the template folder file_loader = jinja2.FileSystemLoader(template_path) env = jinja2.Environment(loader=file_loader, autoescape=True) @@ -96,7 +95,6 @@ class ScopedPageCreator(PageCreator): template_contents = self._substitute_tokens( template_contents, user_input, name ) - # print(template_contents) else: template_contents = "" return ScopedPage(path, template_contents) @@ -138,34 +136,34 @@ class ScopedPageCreator(PageCreator): # 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 what's passed in - if len(input_extraction) != len(user_input) and len(input_extraction) > 0: + if len(input_extraction) > 0 and len(input_extraction) != len(user_input): # if there are input matches and they don't equal the number of input # tokens, raise error logger.critical( - "Your config specifies %s input tokens, you gave %s - exiting", + "Your config/template specifies %s input tokens, you gave %s - exiting", len(input_extraction), len(user_input), ) raise SystemExit(1) - # if the length of user input matches and number of tokens match, or there are - # no input matches, then substitute each token with the user's input + # if the length of both the input matches and the number of tokens match then + # substitute each token with the user's input for extracted_input, input_value in zip(input_extraction, user_input): tokenified_string = tokenified_string.replace(extracted_input, input_value) return tokenified_string @staticmethod def __substitute_date_tokens(tokenified_string: str) -> str: - # find any {d:DD-MM-YYYY} tokens + # find any {d:%d-%M-%Y} tokens date_extraction_token = re.findall(r"(\{d\:[^}]*\})", tokenified_string) for extracted_token in date_extraction_token: - # extract the inner DD-MM-YYYY only + # extract the inner %d-%M-%Y only strftime_value = re.match(r"\{d\:([^\}]*)\}", extracted_token) - if strftime_value: + if strftime_value is not None: strftime_value = strftime_value.group(1) - # replace {d:DD-MM-YYYY} with todays date formatted as DD-MM-YYYY - tokenified_string = tokenified_string.replace( - extracted_token, pendulum.now().format(strftime_value) - ) + if isinstance(strftime_value, str): + tokenified_string = tokenified_string.replace( + extracted_token, pendulum.now().strftime(strftime_value) + ) return tokenified_string @@ -198,7 +196,8 @@ class ScopedPage(Page): with scoped_note_file.open("w", encoding="utf-8") as scoped_page: scoped_page.write(self.page_content) else: - logger.info("The file %s already exists - skipping.", str(scoped_note_file)) + logger.info("%s already exists - skipping.", str(self)) + raise SystemExit(0) if __name__ == "__main__": From 65f6a7658b29e12646894015d2f9f70cbff6336f Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Sun, 24 Oct 2021 01:21:24 +0100 Subject: [PATCH 3/4] updating panaetius --- poetry.lock | 37 ++++++++++++++++++++----------------- pyproject.toml | 5 +++-- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/poetry.lock b/poetry.lock index 769531a..a72745f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -101,14 +101,14 @@ flake8 = "*" [[package]] name = "gitdb" -version = "4.0.7" +version = "4.0.8" description = "Git Object Database" category = "dev" optional = false -python-versions = ">=3.4" +python-versions = ">=3.6" [package.dependencies] -smmap = ">=3.0.1,<5" +smmap = ">=3.0.1,<6" [[package]] name = "gitpython" @@ -220,7 +220,7 @@ pyparsing = ">=2.0.2" [[package]] name = "panaetius" -version = "2.2.0" +version = "2.2.2" description = "Python module to gracefully handle a .config file/environment variables for scripts, with built in masking for sensitive options. Provides a Splunk friendly formatted logger instance." category = "main" optional = false @@ -436,11 +436,14 @@ pylint = ">=1.7" [[package]] name = "pyparsing" -version = "2.4.7" +version = "3.0.0" description = "Python parsing module" category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" @@ -522,11 +525,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "smmap" -version = "4.0.0" +version = "5.0.0" description = "A pure Python implementation of a sliding window memory map manager" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [[package]] name = "snowballstemmer" @@ -574,7 +577,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "3811535320f246d17fe1c47b4a749724381fb56d5baea0e507008712f955c1b3" +content-hash = "cf725126812e7aa05eb881de12b273d64ba32c24f0d75ac0e1eb8943f7ad496c" [metadata.files] astroid = [ @@ -614,8 +617,8 @@ flake8-polyfill = [ {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, ] gitdb = [ - {file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"}, - {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"}, + {file = "gitdb-4.0.8-py3-none-any.whl", hash = "sha256:6875cbaed01f1b750394f372607803768fc7dad7c58c7ceb5f5917e980d779b2"}, + {file = "gitdb-4.0.8.tar.gz", hash = "sha256:858966a9310649cb24a387c101429bb5a1110068a312517722b0281077e78bc6"}, ] gitpython = [ {file = "GitPython-3.1.24-py3-none-any.whl", hash = "sha256:dc0a7f2f697657acc8d7f89033e8b1ea94dd90356b2983bca89dc8d2ab3cc647"}, @@ -766,8 +769,8 @@ packaging = [ {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, ] panaetius = [ - {file = "panaetius-2.2.0-py3-none-any.whl", hash = "sha256:55baae01ce7c1fbbea7a9d667585907e5881c859b64bbbcd747db208bcc777d8"}, - {file = "panaetius-2.2.0.tar.gz", hash = "sha256:a144debf21a13b23213f138782fd27267cb8b335a2243f86790b1bd5dcbc6da3"}, + {file = "panaetius-2.2.2-py3-none-any.whl", hash = "sha256:f353c9893f0de38d90658713d968fec095beaf6834b9f5f086dab13bd292cdf1"}, + {file = "panaetius-2.2.2.tar.gz", hash = "sha256:f0a5b9cba12d91a3e21799a5f3bf1ce19b9c40e7cadad0ce2bacc654160e8694"}, ] pbr = [ {file = "pbr-5.6.0-py2.py3-none-any.whl", hash = "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"}, @@ -851,8 +854,8 @@ pylint-plugin-utils = [ {file = "pylint_plugin_utils-0.6-py3-none-any.whl", hash = "sha256:2f30510e1c46edf268d3a195b2849bd98a1b9433229bb2ba63b8d776e1fc4d0a"}, ] pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pyparsing-3.0.0-py3-none-any.whl", hash = "sha256:d487599e9fb0dc36bee6b5c183c6fc5bd372ce667736f3d430ab7d842a54a35a"}, + {file = "pyparsing-3.0.0.tar.gz", hash = "sha256:001cad8d467e7a9248ef9fd513f5c0d39afcbcb9a43684101853bd0ab962e479"}, ] pytest = [ {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, @@ -913,8 +916,8 @@ six = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] smmap = [ - {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"}, - {file = "smmap-4.0.0.tar.gz", hash = "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182"}, + {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, + {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] snowballstemmer = [ {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, diff --git a/pyproject.toml b/pyproject.toml index 13d7d30..c66217f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,14 +6,15 @@ authors = ["dtomlinson "] [tool.poetry.dependencies] python = "^3.8" -panaetius = "^2.0" click = "^8.0.3" pendulum = "^2.1.2" Jinja2 = "^3.0.2" +# panaetius = { path = "../panaetius", develop = true } +panaetius = "^2.2.2" [tool.poetry.dev-dependencies] pytest = "^6.2.5" -prospector = {extras = ["with_bandit", "with_mypy"], version = "^1.5.1"} +prospector = { extras = ["with_bandit", "with_mypy"], version = "^1.5.1" } [build-system] requires = ["poetry-core>=1.0.0"] From 118a85e6fccffb33d844b82400623689f2d66317 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Sun, 24 Oct 2021 01:21:54 +0100 Subject: [PATCH 4/4] adding --dry-run --- tembo/cli.py | 7 ++++--- tembo/journal/pages.py | 31 +++++++++++++++++-------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/tembo/cli.py b/tembo/cli.py index 4275486..40529bf 100644 --- a/tembo/cli.py +++ b/tembo/cli.py @@ -21,7 +21,8 @@ def run(): nargs=-1, metavar="", ) -def new(scope, inputs): +@click.option("--dry-run", is_flag=True, default=False) +def new(scope, inputs, dry_run): """ Create a new note. @@ -42,8 +43,8 @@ def new(scope, inputs): user_input=inputs, template_filename=str(user_scope["template_filename"]), ) - scoped_page.save_to_disk() - tembo.logger.info("Saved %s to disk", scoped_page) + scoped_page.save_to_disk(dry_run=dry_run) + tembo.logger.info("Saved %s to disk", scoped_page.path) raise SystemExit(0) tembo.logger.critical( "No config.yml found in %s - exiting", tembo.CONFIG.config_path diff --git a/tembo/journal/pages.py b/tembo/journal/pages.py index cc346c1..b32019b 100644 --- a/tembo/journal/pages.py +++ b/tembo/journal/pages.py @@ -21,8 +21,9 @@ class PageCreator: name: str, user_input: tuple[str, ...] | None, template_filename: str | None = None, + dry_run: bool = False ) -> Page: - pass + raise NotImplementedError @staticmethod def _convert_to_path( @@ -30,7 +31,7 @@ class PageCreator: ) -> pathlib.Path: # check if Tembo base path exists if not pathlib.Path(base_path).expanduser().exists(): - logger.critical("base path of %s does not exist - exiting", base_path) + logger.critical("Tembo base path of %s does not exist - exiting", base_path) raise SystemExit(1) path_to_file = ( pathlib.Path(base_path).expanduser() @@ -52,7 +53,7 @@ class PageCreator: template_path = self._convert_to_path("", CONFIG.template_path, "", "") else: # default template_path is base_path / templates - template_path = self._convert_to_path(base_path, "templates", "", "") + template_path = self._convert_to_path(base_path, ".templates", "", "") # load the template folder file_loader = jinja2.FileSystemLoader(template_path) env = jinja2.Environment(loader=file_loader, autoescape=True) @@ -113,7 +114,7 @@ class ScopedPageCreator(PageCreator): @staticmethod def __substitute_name_tokens(tokenified_string: str, name: str) -> str: # find any {name} tokens and substitute for the name value - name_extraction = re.findall(r"(\{name\d*\})", tokenified_string) + name_extraction = re.findall(r"(\{name\})", tokenified_string) for extracted_input in name_extraction: tokenified_string = tokenified_string.replace(extracted_input, name) return tokenified_string @@ -135,7 +136,7 @@ class ScopedPageCreator(PageCreator): 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 what's passed in + # 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): # if there are input matches and they don't equal the number of input # tokens, raise error @@ -169,34 +170,36 @@ class ScopedPageCreator(PageCreator): class Page(metaclass=ABCMeta): @abstractmethod - def __init__(self, path: pathlib.Path, page_content: str) -> None: - pass + def __init__(self, path: pathlib.Path, page_content: str, dry_run: bool) -> None: + raise NotImplementedError @abstractmethod - def save_to_disk(self) -> None: - pass + def save_to_disk(self, dry_run: bool) -> None: + raise NotImplementedError class ScopedPage(Page): """A Page that uses substitute tokens.""" - def __init__(self, path: pathlib.Path, page_content: str): + def __init__(self, path: pathlib.Path, page_content: str) -> None: self.path = path self.page_content = page_content - def __str__(self): + def __str__(self) -> str: return f"ScopedPage({self.path})" - def save_to_disk(self) -> None: + def save_to_disk(self, dry_run: bool = False) -> None: scoped_note_file = pathlib.Path(self.path) # create the parent directories scoped_note_file.parents[0].mkdir(parents=True, exist_ok=True) - + if dry_run: + logger.info("%s will be created", self.path) + raise SystemExit(0) if not scoped_note_file.exists(): with scoped_note_file.open("w", encoding="utf-8") as scoped_page: scoped_page.write(self.page_content) else: - logger.info("%s already exists - skipping.", str(self)) + logger.info("%s already exists - skipping.", self.path) raise SystemExit(0)