Coverage for roter/cli.py: 25.26%
69 statements
« prev ^ index » next coverage.py v7.5.4, created at 2024-06-24 19:21:34 +00:00
« prev ^ index » next coverage.py v7.5.4, created at 2024-06-24 19:21:34 +00:00
1"""CLI operations for rotate and combine tables (Danish: Roter og kombiner borde)."""
3import argparse
4import pathlib
5import sys
6from typing import Union
8import roter.api as api
9from roter import (
10 APP_ALIAS,
11 APP_NAME,
12 APP_VERSION,
13 CHILD_ATTRIBUTES,
14 COMMA,
15 MARKERS,
16 PARENT_ATTRIBUTES,
17 parse_csl_preserve_case
18)
21def parse_request(argv: list[str]) -> Union[int, argparse.Namespace]:
22 """DRY."""
23 parser = argparse.ArgumentParser(
24 prog=APP_ALIAS, description=APP_NAME, formatter_class=argparse.RawTextHelpFormatter
25 )
26 parser.add_argument(
27 '--table-files',
28 '-t',
29 dest='table_files',
30 default='',
31 help='Markdown files with tables to parse. Optional\n(default: positional table files value)',
32 required=False,
33 )
34 parser.add_argument(
35 'table_files_pos', nargs='*', default='', help='markdown files with tables to parse. Optional'
36 )
37 parser.add_argument(
38 '--excludes',
39 '-x',
40 dest='excludes',
41 default='',
42 help='comma separated list of values to exclude paths\ncontaining the substring (default: empty string)',
43 )
44 parser.add_argument(
45 '--out-path',
46 '-o',
47 dest='out_path',
48 default=sys.stdout,
49 help='output file path (stem) to inject combined and inverted markdown tables in between markers',
50 )
51 parser.add_argument(
52 '--markers',
53 '-m',
54 dest='markers',
55 type=str,
56 default=MARKERS,
57 help=f'comma separated begin/end markers in output file path (default: MARKERS)',
58 )
59 parser.add_argument(
60 '--child-attributes',
61 '-c',
62 dest='child_attributes',
63 type=str,
64 default=CHILD_ATTRIBUTES,
65 help=f'heading position and labels (singular and plural) for children related columns (default:{CHILD_ATTRIBUTES})',
66 )
67 parser.add_argument(
68 '--parent-attributes',
69 '-p',
70 dest='parent_attributes',
71 type=str,
72 default=PARENT_ATTRIBUTES,
73 help=f'heading position and labels (singular and plural) for parents related columns (default: {PARENT_ATTRIBUTES})',
74 )
75 parser.add_argument(
76 '--invert-only',
77 dest='invert_only',
78 default=False,
79 action='store_true',
80 help='Only inject the inverted table',
81 required=False,
82 )
83 parser.add_argument(
84 '--concat-only',
85 dest='concat_only',
86 default=False,
87 action='store_true',
88 help='Only inject the combined table',
89 required=False,
90 )
91 parser.add_argument(
92 '--version',
93 '-V',
94 dest='version_request',
95 default=False,
96 action='store_true',
97 help='show version of the app and exit',
98 required=False,
99 )
101 if not argv: 101 ↛ 106line 101 didn't jump to line 106 because the condition on line 101 was always true
102 print(f'{APP_NAME} version {APP_VERSION}')
103 parser.print_help()
104 return 0
106 options = parser.parse_args(argv)
108 if options.version_request:
109 print(f'{APP_NAME} version {APP_VERSION}')
110 return 0
112 if not options.table_files:
113 if options.table_files_pos:
114 options.table_files = options.table_files_pos
115 else:
116 parser.error('missing any paths to parse tables from')
117 else:
118 options.table_files = [p.strip() for p in options.table_files.split() if p.strip()]
119 if options.table_files_pos:
120 options.table_files.extend(options.table_files_pos)
122 options.excludes_parsed = parse_csl(options.excludes) if options and options.excludes else []
123 options.paths = (pathlib.Path(p) for p in options.table_files if not any(x in p for x in options.excludes_parsed ))
124 if not options.paths:
125 parser.error('missing non-excluded paths to parse tables from')
127 if options.out_path is sys.stdout:
128 parser.error('missing output template to inject combined and inverted tables into')
130 if options.markers.count(COMMA) != 4 - 1:
131 parser.error('4 markers separated by comma are required to inject two tables')
133 markers_seq = parse_csl_preserve_case(options.markers)
134 if len(markers_seq) != 4:
135 parser.error('4 non-empty markers are required to inject two tables')
137 options.markers_combined = (markers_seq[0], markers_seq[1])
138 options.markers_inverted = (markers_seq[2], markers_seq[3])
140 child_seq = parse_csl_preserve_case(options.child_attributes)
141 if len(child_seq) != 3:
142 parser.error('3 non-empty child attributes are required to process the tables')
143 try:
144 options.child_column_pos = int(child_seq[0])
145 except: # noqa
146 parser.error('child column position as integer in [1, N] is required to process the tables')
147 options.child, options.children = child_seq[1], child_seq[2]
149 parent_seq = parse_csl_preserve_case(options.parent_attributes)
150 if len(parent_seq) != 3:
151 parser.error('3 non-empty parent attributes are required to process the tables')
152 try:
153 options.parents_column_pos = int(parent_seq[0])
154 except: # noqa
155 parser.error('parent column position as integer in [1, N] is required to process the tables')
156 options.parent, options.parents = parent_seq[1], parent_seq[2]
158 return options
161def main(argv: Union[list[str], None] = None) -> int:
162 """Delegate processing to functional module."""
163 argv = sys.argv[1:] if argv is None else argv
164 options = parse_request(argv)
165 if isinstance(options, int):
166 return 0
167 return api.main(options)