Merge branch 'rework/change_from_pendulum_to_strftime' into develop

This commit is contained in:
2021-10-24 01:22:03 +01:00
6 changed files with 78 additions and 56 deletions

View File

@@ -2,6 +2,7 @@ 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.
☐ `TEMBO_CONFIG` should follow same pattern as other env vars and be a python string when read in
VSCode: VSCode:
☐ Look at <https://github.com/CodeWithSwastik/vscode-ext> ☐ Look at <https://github.com/CodeWithSwastik/vscode-ext>
@@ -19,4 +20,12 @@ Documentation:
☐ 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.
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
☐ ~/tembo needs creating
☐ ~/tembo/.config
☐ ~/tembo/.templates
☐ Document how to overwrite these with ENV vars

37
poetry.lock generated
View File

@@ -101,14 +101,14 @@ flake8 = "*"
[[package]] [[package]]
name = "gitdb" name = "gitdb"
version = "4.0.7" version = "4.0.8"
description = "Git Object Database" description = "Git Object Database"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.4" python-versions = ">=3.6"
[package.dependencies] [package.dependencies]
smmap = ">=3.0.1,<5" smmap = ">=3.0.1,<6"
[[package]] [[package]]
name = "gitpython" name = "gitpython"
@@ -220,7 +220,7 @@ pyparsing = ">=2.0.2"
[[package]] [[package]]
name = "panaetius" 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." 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" category = "main"
optional = false optional = false
@@ -436,11 +436,14 @@ pylint = ">=1.7"
[[package]] [[package]]
name = "pyparsing" name = "pyparsing"
version = "2.4.7" version = "3.0.0"
description = "Python parsing module" description = "Python parsing module"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=3.6"
[package.extras]
diagrams = ["jinja2", "railroad-diagrams"]
[[package]] [[package]]
name = "pytest" name = "pytest"
@@ -522,11 +525,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]] [[package]]
name = "smmap" name = "smmap"
version = "4.0.0" version = "5.0.0"
description = "A pure Python implementation of a sliding window memory map manager" description = "A pure Python implementation of a sliding window memory map manager"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.6"
[[package]] [[package]]
name = "snowballstemmer" name = "snowballstemmer"
@@ -574,7 +577,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "3811535320f246d17fe1c47b4a749724381fb56d5baea0e507008712f955c1b3" content-hash = "cf725126812e7aa05eb881de12b273d64ba32c24f0d75ac0e1eb8943f7ad496c"
[metadata.files] [metadata.files]
astroid = [ astroid = [
@@ -614,8 +617,8 @@ flake8-polyfill = [
{file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"},
] ]
gitdb = [ gitdb = [
{file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"}, {file = "gitdb-4.0.8-py3-none-any.whl", hash = "sha256:6875cbaed01f1b750394f372607803768fc7dad7c58c7ceb5f5917e980d779b2"},
{file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"}, {file = "gitdb-4.0.8.tar.gz", hash = "sha256:858966a9310649cb24a387c101429bb5a1110068a312517722b0281077e78bc6"},
] ]
gitpython = [ gitpython = [
{file = "GitPython-3.1.24-py3-none-any.whl", hash = "sha256:dc0a7f2f697657acc8d7f89033e8b1ea94dd90356b2983bca89dc8d2ab3cc647"}, {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"}, {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
] ]
panaetius = [ panaetius = [
{file = "panaetius-2.2.0-py3-none-any.whl", hash = "sha256:55baae01ce7c1fbbea7a9d667585907e5881c859b64bbbcd747db208bcc777d8"}, {file = "panaetius-2.2.2-py3-none-any.whl", hash = "sha256:f353c9893f0de38d90658713d968fec095beaf6834b9f5f086dab13bd292cdf1"},
{file = "panaetius-2.2.0.tar.gz", hash = "sha256:a144debf21a13b23213f138782fd27267cb8b335a2243f86790b1bd5dcbc6da3"}, {file = "panaetius-2.2.2.tar.gz", hash = "sha256:f0a5b9cba12d91a3e21799a5f3bf1ce19b9c40e7cadad0ce2bacc654160e8694"},
] ]
pbr = [ pbr = [
{file = "pbr-5.6.0-py2.py3-none-any.whl", hash = "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"}, {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"}, {file = "pylint_plugin_utils-0.6-py3-none-any.whl", hash = "sha256:2f30510e1c46edf268d3a195b2849bd98a1b9433229bb2ba63b8d776e1fc4d0a"},
] ]
pyparsing = [ pyparsing = [
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-3.0.0-py3-none-any.whl", hash = "sha256:d487599e9fb0dc36bee6b5c183c6fc5bd372ce667736f3d430ab7d842a54a35a"},
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, {file = "pyparsing-3.0.0.tar.gz", hash = "sha256:001cad8d467e7a9248ef9fd513f5c0d39afcbcb9a43684101853bd0ab962e479"},
] ]
pytest = [ pytest = [
{file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, {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"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
] ]
smmap = [ smmap = [
{file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"}, {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"},
{file = "smmap-4.0.0.tar.gz", hash = "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"},
] ]
snowballstemmer = [ snowballstemmer = [
{file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"},

View File

@@ -6,14 +6,15 @@ authors = ["dtomlinson <dtomlinson@panaetius.co.uk>"]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.8" python = "^3.8"
panaetius = "^2.0"
click = "^8.0.3" click = "^8.0.3"
pendulum = "^2.1.2" pendulum = "^2.1.2"
Jinja2 = "^3.0.2" Jinja2 = "^3.0.2"
# panaetius = { path = "../panaetius", develop = true }
panaetius = "^2.2.2"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^6.2.5" 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] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]

View File

@@ -2,14 +2,14 @@ import os
import panaetius 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) CONFIG = panaetius.Config("tembo", config_path)
else: 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, "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, "scopes", {})
panaetius.set_config(CONFIG, "logging.level", "DEBUG") panaetius.set_config(CONFIG, "logging.level", "DEBUG")
panaetius.set_config(CONFIG, "logging.path") panaetius.set_config(CONFIG, "logging.path")

View File

@@ -12,8 +12,6 @@ def run():
""" """
Tembo - an organiser for work notes. Tembo - an organiser for work notes.
""" """
print(tembo.CONFIG.base_path)
# print(tembo.CONFIG.scopes)
@click.command(options_metavar="<options>") @click.command(options_metavar="<options>")
@@ -23,7 +21,8 @@ def run():
nargs=-1, nargs=-1,
metavar="<inputs>", metavar="<inputs>",
) )
def new(scope, inputs): @click.option("--dry-run", is_flag=True, default=False)
def new(scope, inputs, dry_run):
""" """
Create a new note. Create a new note.
@@ -33,7 +32,6 @@ def new(scope, inputs):
Example: tembo new meeting my_presentation Example: tembo new meeting my_presentation
""" """
for user_scope in tembo.CONFIG.scopes: for user_scope in tembo.CONFIG.scopes:
if user_scope["name"] == scope: if user_scope["name"] == scope:
scoped_page = pages.ScopedPageCreator().create_page( scoped_page = pages.ScopedPageCreator().create_page(
@@ -45,8 +43,17 @@ def new(scope, inputs):
user_input=inputs, user_input=inputs,
template_filename=str(user_scope["template_filename"]), template_filename=str(user_scope["template_filename"]),
) )
scoped_page.save_to_disk() scoped_page.save_to_disk(dry_run=dry_run)
tembo.logger.info("Saved %s to disk", scoped_page) 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
)
raise SystemExit(1)
run.add_command(new) run.add_command(new)
if __name__ == "__main__":
new(["scratchpad"], ())

View File

@@ -19,10 +19,11 @@ class PageCreator:
filename: str, filename: str,
extension: str, extension: str,
name: str, name: str,
user_input: tuple[str, ...], user_input: tuple[str, ...] | None,
template_filename: str | None = None, template_filename: str | None = None,
dry_run: bool = False
) -> Page: ) -> Page:
pass raise NotImplementedError
@staticmethod @staticmethod
def _convert_to_path( def _convert_to_path(
@@ -30,7 +31,7 @@ class PageCreator:
) -> pathlib.Path: ) -> pathlib.Path:
# check if Tembo base path exists # check if Tembo base path exists
if not pathlib.Path(base_path).expanduser().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) raise SystemExit(1)
path_to_file = ( path_to_file = (
pathlib.Path(base_path).expanduser() pathlib.Path(base_path).expanduser()
@@ -52,8 +53,7 @@ class PageCreator:
template_path = self._convert_to_path("", CONFIG.template_path, "", "") template_path = self._convert_to_path("", CONFIG.template_path, "", "")
else: else:
# default template_path is base_path / templates # 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", "", "")
print(template_path, template_filename)
# load the template folder # load the template folder
file_loader = jinja2.FileSystemLoader(template_path) file_loader = jinja2.FileSystemLoader(template_path)
env = jinja2.Environment(loader=file_loader, autoescape=True) env = jinja2.Environment(loader=file_loader, autoescape=True)
@@ -96,7 +96,6 @@ class ScopedPageCreator(PageCreator):
template_contents = self._substitute_tokens( template_contents = self._substitute_tokens(
template_contents, user_input, name template_contents, user_input, name
) )
# print(template_contents)
else: else:
template_contents = "" template_contents = ""
return ScopedPage(path, template_contents) return ScopedPage(path, template_contents)
@@ -115,7 +114,7 @@ class ScopedPageCreator(PageCreator):
@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
name_extraction = re.findall(r"(\{name\d*\})", tokenified_string) name_extraction = re.findall(r"(\{name\})", tokenified_string)
for extracted_input in name_extraction: for extracted_input in name_extraction:
tokenified_string = tokenified_string.replace(extracted_input, name) tokenified_string = tokenified_string.replace(extracted_input, name)
return tokenified_string return tokenified_string
@@ -137,68 +136,71 @@ class ScopedPageCreator(PageCreator):
raise SystemExit(1) raise SystemExit(1)
# if there aren't any tokens in the string, return the string # if there aren't any tokens in the string, return the string
return tokenified_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) != 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 # if there are input matches and they don't equal the number of input
# tokens, raise error # tokens, raise error
logger.critical( 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(input_extraction),
len(user_input), len(user_input),
) )
raise SystemExit(1) raise SystemExit(1)
# if the length of user input matches and number of tokens match, or there are # if the length of both the input matches and the number of tokens match then
# no input matches, then substitute each token with the user's input # substitute each token with the user's input
for extracted_input, input_value in zip(input_extraction, user_input): for extracted_input, input_value in zip(input_extraction, user_input):
tokenified_string = tokenified_string.replace(extracted_input, input_value) tokenified_string = tokenified_string.replace(extracted_input, input_value)
return tokenified_string return tokenified_string
@staticmethod @staticmethod
def __substitute_date_tokens(tokenified_string: str) -> str: 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) 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 DD-MM-YYYY only # extract the inner %d-%M-%Y only
strftime_value = re.match(r"\{d\:([^\}]*)\}", extracted_token) strftime_value = re.match(r"\{d\:([^\}]*)\}", extracted_token)
if strftime_value: if strftime_value is not None:
strftime_value = strftime_value.group(1) strftime_value = strftime_value.group(1)
# replace {d:DD-MM-YYYY} with todays date formatted as DD-MM-YYYY if isinstance(strftime_value, str):
tokenified_string = tokenified_string.replace( tokenified_string = tokenified_string.replace(
extracted_token, pendulum.now().format(strftime_value) extracted_token, pendulum.now().strftime(strftime_value)
) )
return tokenified_string return tokenified_string
class Page(metaclass=ABCMeta): class Page(metaclass=ABCMeta):
@abstractmethod @abstractmethod
def __init__(self, path: pathlib.Path, page_content: str) -> None: def __init__(self, path: pathlib.Path, page_content: str, dry_run: bool) -> None:
pass raise NotImplementedError
@abstractmethod @abstractmethod
def save_to_disk(self) -> None: def save_to_disk(self, dry_run: bool) -> None:
pass raise NotImplementedError
class ScopedPage(Page): class ScopedPage(Page):
"""A Page that uses substitute tokens.""" """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.path = path
self.page_content = page_content self.page_content = page_content
def __str__(self): def __str__(self) -> str:
return f"ScopedPage({self.path})" 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) scoped_note_file = pathlib.Path(self.path)
# create the parent directories # create the parent directories
scoped_note_file.parents[0].mkdir(parents=True, exist_ok=True) 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(): 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)
else: else:
logger.info("The file %s already exists - skipping.", str(scoped_note_file)) logger.info("%s already exists - skipping.", self.path)
raise SystemExit(0)
if __name__ == "__main__": if __name__ == "__main__":