Coverage for navigaattori/cli.py: 95.29%
63 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-04 20:46:10 +00:00
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-04 20:46:10 +00:00
1"""Command line interface for navigator (Finnish: navigaattori) guided by conventions."""
3import datetime as dti
4import logging
5import pathlib
6import sys
8import typer
10import navigaattori.api as api
11import navigaattori.eject as eje
12from navigaattori import (
13 APP_NAME,
14 DEFAULT_STRUCTURE_NAME,
15 QUIET,
16 TS_FORMAT_PAYLOADS,
17 __version__ as APP_VERSION,
18 log,
19)
21app = typer.Typer(
22 add_completion=False,
23 context_settings={'help_option_names': ['-h', '--help']},
24 no_args_is_help=True,
25)
27DocumentRoot = typer.Option(
28 '',
29 '-d',
30 '--document-root',
31 help='Root of the document tree to visit. Optional\n(default: positional tree root value)',
32)
33StructureName = typer.Option(
34 DEFAULT_STRUCTURE_NAME,
35 '-s',
36 '--structure',
37 help='structure mapping file (default: {gat.DEFAULT_STRUCTURE_NAME})',
38)
39TargetName = typer.Option(
40 '',
41 '-t',
42 '--target',
43 help='target document key',
44)
45FacetName = typer.Option(
46 '',
47 '-f',
48 '--facet',
49 help='facet key of target document',
50)
51Verbosity = typer.Option(
52 False,
53 '-v',
54 '--verbose',
55 help='Verbose output (default is False)',
56)
57Strictness = typer.Option(
58 False,
59 '-s',
60 '--strict',
61 help='Output noisy warnings on console (default is False)',
62)
63Guess = typer.Option(
64 False,
65 '-g',
66 '--guess',
67 help='Guess and derive structures from folder tree structure.yml files if possible (default is False)',
68)
69OutputPath = typer.Option(
70 '',
71 '-o',
72 '--output-path',
73 help='Path to output unambiguous content to - like when ejecting a template',
74)
75Excludes = typer.Option(
76 '.git/,render/pdf/',
77 '-x',
78 '--excludes',
79 help='comma separated list of values to exclude paths\ncontaining the substring (default: .git/,render/pdf/)',
80)
83@app.callback(invoke_without_command=True)
84def callback(
85 version: bool = typer.Option(
86 False,
87 '-V',
88 '--version',
89 help='Display the application version and exit',
90 is_eager=True,
91 )
92) -> None:
93 """
94 Navigator (Finnish: navigaattori) guided by conventions.
95 """
96 if version:
97 typer.echo(f'{APP_NAME} version {APP_VERSION}')
98 raise typer.Exit()
101def _verify_call_vector(
102 doc_root: str,
103 doc_root_pos: str,
104 verbose: bool,
105 strict: bool,
106 guess: bool,
107 excludes: str,
108) -> tuple[int, str, str, dict[str, object]]:
109 """DRY"""
110 doc = doc_root.strip()
111 if not doc and doc_root_pos:
112 doc = doc_root_pos
113 if not doc:
114 print('Document tree root required', file=sys.stderr)
115 return 2, 'Document tree root required', '', {}
117 doc_root_path = pathlib.Path(doc)
118 if doc_root_path.exists():
119 if not doc_root_path.is_dir(): 119 ↛ 120line 119 didn't jump to line 120, because the condition on line 119 was never true
120 print(f'requested tree root at ({doc}) is not a folder', file=sys.stderr)
121 return 2, f'requested tree root at ({doc}) is not a folder', '', {}
122 else:
123 print(f'requested tree root at ({doc}) does not exist', file=sys.stderr)
124 return 2, f'requested tree root at ({doc}) does not exist', '', {}
126 options = {
127 'quiet': QUIET and not verbose and not strict,
128 'strict': strict,
129 'verbose': verbose,
130 'guess': guess,
131 'excludes': excludes,
132 }
133 if verbose:
134 logging.getLogger().setLevel(logging.DEBUG)
135 return 0, '', doc, options
138@app.command('explore')
139def explore( # noqa
140 doc_root_pos: str = typer.Argument(''),
141 doc_root: str = DocumentRoot,
142 verbose: bool = Verbosity,
143 strict: bool = Strictness,
144 guess: bool = Guess,
145 excludes: str = Excludes,
146) -> int:
147 """
148 Explore the structures definition tree in the file system.
149 """
150 code, message, doc, options = _verify_call_vector(doc_root, doc_root_pos, verbose, strict, guess, excludes)
151 if code:
152 log.error(message)
153 return sys.exit(code)
155 start_time = dti.datetime.now(tz=dti.timezone.utc)
156 start_ts = start_time.strftime(TS_FORMAT_PAYLOADS)
157 log.info(f'start timestamp ({start_ts})')
159 code, _ = api.explore(doc_root=doc, options=options)
161 end_time = dti.datetime.now(tz=dti.timezone.utc)
162 end_ts = end_time.strftime(TS_FORMAT_PAYLOADS)
163 duration_secs = (end_time - start_time).total_seconds()
164 log.info(f'end timestamp ({end_ts})')
165 log.info(f'explored structures at {doc} in {duration_secs} secs')
166 return sys.exit(code)
169@app.command('eject')
170def eject( # noqa
171 that: str = typer.Argument(''),
172 out: str = OutputPath,
173) -> int:
174 """
175 Eject a template. Enter unique part to retrieve, any unknown word to obtain the list of known templates.
176 """
177 return sys.exit(eje.this(thing=that, out=out))
180@app.command('version')
181def app_version() -> None:
182 """
183 Display the application version and exit.
184 """
185 callback(True)