Coverage for tembo/cli/cli.py: 100%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Submodule which contains the CLI implementation using Click."""
3from __future__ import annotations
5import pathlib
6from typing import Collection
8import click
10import tembo.cli
11from tembo import exceptions
12from tembo._version import __version__
13from tembo.journal import pages
14from tembo.utils import Success
16CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
19@click.group(context_settings=CONTEXT_SETTINGS, options_metavar="<options>")
20@click.version_option(
21 __version__,
22 "-v",
23 "--version",
24 prog_name="Tembo",
25 message=f"Tembo v{__version__} 🐘",
26)
27def main():
28 """Tembo - an organiser for work notes."""
31@click.command(options_metavar="<options>", name="list")
32def list_all():
33 """List all scopes defined in the config.yml."""
34 _all_scopes = [user_scope["name"] for user_scope in tembo.cli.CONFIG.scopes]
35 _all_scopes_joined = "', '".join(_all_scopes)
36 cli_message(f"{len(_all_scopes)} names found in config.yml: '{_all_scopes_joined}'")
37 raise SystemExit(0)
40@click.command(options_metavar="<options>")
41@click.argument("scope", metavar="<scope>")
42@click.argument(
43 "inputs",
44 nargs=-1,
45 metavar="<inputs>",
46)
47@click.option(
48 "--dry-run",
49 is_flag=True,
50 default=False,
51 help="Show the full path of the page to be created without actually saving the page to disk "
52 "and exit.",
53)
54@click.option(
55 "--example",
56 is_flag=True,
57 default=False,
58 help="Show the example command in the config.yml if it exists and exit.",
59)
60def new(scope: str, inputs: Collection[str], dry_run: bool, example: bool): # noqa
61 """
62 Create a new page.
64 \b
65 `<scope>` The name of the scope in the config.yml.
66 \b
67 `<inputs>` Any input token values that are defined in the config.yml for this scope.
68 Accepts multiple inputs separated by a space.
70 \b
71 Example:
72 `tembo new meeting my_presentation`
73 """
74 # check that the name exists in the config.yml
75 try:
76 _new_verify_name_exists(scope)
77 except (
78 exceptions.ScopeNotFound,
79 exceptions.EmptyConfigYML,
80 exceptions.MissingConfigYML,
81 ) as tembo_exception:
82 cli_message(tembo_exception.args[0])
83 raise SystemExit(1) from tembo_exception
85 # get the scope configuration from the config.yml
86 try:
87 config_scope = _new_get_config_scope(scope)
88 except exceptions.MandatoryKeyNotFound as mandatory_key_not_found:
89 cli_message(mandatory_key_not_found.args[0])
90 raise SystemExit(1) from mandatory_key_not_found
92 # if --example flag, return the example to the user
93 _new_show_example(example, config_scope)
95 # if the name is in the config.yml, create the scoped page
96 scoped_page = _new_create_scoped_page(config_scope, inputs)
98 if dry_run:
99 cli_message(f"{scoped_page.path} will be created")
100 raise SystemExit(0)
102 try:
103 result = scoped_page.save_to_disk()
104 if isinstance(result, Success):
105 cli_message(f"Saved {result.message} to disk")
106 raise SystemExit(0)
107 except exceptions.ScopedPageAlreadyExists as scoped_page_already_exists:
108 cli_message(f"File {scoped_page_already_exists}")
109 raise SystemExit(0) from scoped_page_already_exists
112def _new_create_scoped_page(config_scope: dict, inputs: Collection[str]) -> pages.Page:
113 page_creator_options = pages.PageCreatorOptions(
114 base_path=tembo.cli.CONFIG.base_path,
115 template_path=tembo.cli.CONFIG.template_path,
116 page_path=config_scope["path"],
117 filename=config_scope["filename"],
118 extension=config_scope["extension"],
119 name=config_scope["name"],
120 example=config_scope["example"],
121 user_input=inputs,
122 template_filename=config_scope["template_filename"],
123 )
124 try:
125 return pages.ScopedPageCreator(page_creator_options).create_page()
126 except exceptions.BasePathDoesNotExistError as base_path_does_not_exist_error:
127 cli_message(base_path_does_not_exist_error.args[0])
128 raise SystemExit(1) from base_path_does_not_exist_error
129 except exceptions.TemplateFileNotFoundError as template_file_not_found_error:
130 cli_message(template_file_not_found_error.args[0])
131 raise SystemExit(1) from template_file_not_found_error
132 except exceptions.MismatchedTokenError as mismatched_token_error:
133 if config_scope["example"] is not None:
134 cli_message(
135 f"Your tembo config.yml/template specifies {mismatched_token_error.expected}"
136 + f" input tokens, you gave {mismatched_token_error.given}. "
137 + f'Example: {config_scope["example"]}'
138 )
139 raise SystemExit(1) from mismatched_token_error
140 cli_message(
141 f"Your tembo config.yml/template specifies {mismatched_token_error.expected}"
142 + f" input tokens, you gave {mismatched_token_error.given}"
143 )
145 raise SystemExit(1) from mismatched_token_error
148def _new_verify_name_exists(scope: str) -> None:
149 _name_found = scope in [user_scope["name"] for user_scope in tembo.cli.CONFIG.scopes]
150 if _name_found:
151 return
152 if len(tembo.cli.CONFIG.scopes) > 0:
153 # if the name is missing in the config.yml, raise error
154 raise exceptions.ScopeNotFound(f"Scope {scope} not found in config.yml")
155 # raise error if no config.yml found
156 if pathlib.Path(tembo.cli.CONFIG.config_path).exists():
157 raise exceptions.EmptyConfigYML(
158 f"Config.yml found in {tembo.cli.CONFIG.config_path} is empty"
159 )
160 raise exceptions.MissingConfigYML(f"No config.yml found in {tembo.cli.CONFIG.config_path}")
163def _new_get_config_scope(scope: str) -> dict:
164 config_scope = {}
165 optional_keys = ["example", "template_filename"]
166 for option in [
167 "name",
168 "path",
169 "filename",
170 "extension",
171 "example",
172 "template_filename",
173 ]:
174 try:
175 config_scope.update(
176 {
177 option: str(user_scope[option])
178 for user_scope in tembo.cli.CONFIG.scopes
179 if user_scope["name"] == scope
180 }
181 )
182 except KeyError as key_error:
183 if key_error.args[0] in optional_keys:
184 config_scope.update({key_error.args[0]: None})
185 continue
186 raise exceptions.MandatoryKeyNotFound(f"Key {key_error} not found in config.yml")
187 return config_scope
190def _new_show_example(example: bool, config_scope: dict) -> None:
191 if example:
192 if isinstance(config_scope["example"], str):
193 cli_message(f'Example for {config_scope["name"]}: {config_scope["example"]}')
194 else:
195 cli_message("No example in config.yml")
196 raise SystemExit(0)
199def cli_message(message: str) -> None:
200 """
201 Relay a message to the user using the CLI.
203 Args:
204 message (str): THe message to be displayed.
205 """
206 click.echo(f"[TEMBO] {message} 🐘")
209main.add_command(new)
210main.add_command(list_all)