Repository navigation
Expand file tree
/
Copy pathcli.py
More file actions
271 lines (233 loc) · 8.4 KB
/
Copy pathcli.py
File metadata and controls
271 lines (233 loc) · 8.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
"""
Command line interface for code annotation tools.
"""
import datetime
import sys
import traceback
import click
from code_annotations.base import AnnotationConfig, ConfigurationException
from code_annotations.find_django import DjangoSearch
from code_annotations.find_static import StaticSearch
from code_annotations.generate_docs import ReportRenderer
from code_annotations.helpers import fail
@click.group()
def entry_point():
"""
Top level click command for the code annotation tools.
"""
@entry_point.command("django_find_annotations")
@click.option(
"--config_file",
default=".annotations",
help="Path to the configuration file",
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
)
@click.option(
"--seed_safelist/--no_safelist",
default=False,
show_default=True,
help="Generate an initial safelist file based on the current Django environment.",
)
@click.option(
"--list_local_models/--no_list_models",
default=False,
show_default=True,
help="List all locally defined models (in the current repo) that require annotations.",
)
@click.option(
"--app_name",
default=None,
help="(Optional) App name for which coverage is generated.",
)
@click.option("--report_path", default=None, help="Location to write the report")
@click.option("-v", "--verbosity", count=True, help="Verbosity level (-v through -vvv)")
@click.option(
"--lint/--no_lint",
help="Enable or disable linting checks",
default=False,
show_default=True,
)
@click.option(
"--report/--no_report",
help="Enable or disable writing the report",
default=False,
show_default=True,
)
@click.option(
"--coverage/--no_coverage",
help="Enable or disable coverage checks",
default=False,
show_default=True,
)
def django_find_annotations(
config_file,
seed_safelist,
list_local_models,
app_name,
report_path,
verbosity,
lint,
report,
coverage,
):
"""
Subcommand for dealing with annotations in Django models.
"""
try:
start_time = datetime.datetime.utcnow()
if (
not coverage
and not seed_safelist
and not list_local_models
and not lint
and not report
):
click.echo(
"No actions specified. Please specify one or more of --seed_safelist, --list_local_models, "
"--lint, --report, or --coverage"
)
sys.exit(1)
config = AnnotationConfig(config_file, report_path, verbosity)
searcher = DjangoSearch(config, app_name)
# Early out if we're trying to do coverage, but a coverage target is not configured
if coverage and not config.coverage_target:
raise ConfigurationException(
"Please add 'coverage_target' to your configuration before running --coverage"
)
if seed_safelist:
searcher.seed_safelist()
if list_local_models:
searcher.list_local_models()
if lint or report or coverage:
annotated_models = searcher.search()
if lint:
click.echo("Performing linting checks...")
# Check grouping and choices
if not searcher.check_results(annotated_models):
click.secho("\nSearch failed due to linting errors!", fg="red")
click.secho("{} errors:".format(len(searcher.errors)), fg="red")
click.secho("---------------------------------", fg="red")
click.echo("\n".join(searcher.errors))
# If there are any errors, do not continue
sys.exit(1)
click.echo("Linting passed without errors.")
if coverage:
if not searcher.check_coverage():
# If there are any errors, do not continue
sys.exit(1)
click.echo("Coverage passed without errors.")
if report:
searcher.report(annotated_models, app_name)
annotation_count = 0
for filename in annotated_models:
annotation_count += len(annotated_models[filename])
elapsed = datetime.datetime.utcnow() - start_time
click.echo(
"Search found {} annotations in {} seconds.".format(
annotation_count, elapsed.total_seconds()
)
)
except Exception as exc:
click.echo(traceback.print_exc())
fail(str(exc))
@entry_point.command("static_find_annotations")
@click.option(
"--config_file",
default=".annotations",
help="Path to the configuration file",
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
)
@click.option(
"--source_path",
help="Location of the source code to search",
type=click.Path(exists=True, dir_okay=True, resolve_path=True),
)
@click.option("--report_path", default=None, help="Location to write the report")
@click.option("-v", "--verbosity", count=True, help="Verbosity level (-v through -vvv)")
@click.option(
"--lint/--no_lint",
help="Enable or disable linting checks",
default=True,
show_default=True,
)
@click.option(
"--report/--no_report",
help="Enable or disable writing the report file",
default=True,
show_default=True,
)
def static_find_annotations(
config_file, source_path, report_path, verbosity, lint, report
):
"""
Subcommand to find annotations via static file analysis.
"""
try:
start_time = datetime.datetime.utcnow()
config = AnnotationConfig(config_file, report_path, verbosity, source_path)
searcher = StaticSearch(config)
all_results = searcher.search()
if lint:
click.echo("Performing linting checks...")
# Check grouping and choices
searcher.check_results(all_results)
# If there are any errors, do not generate the report
if searcher.errors:
click.secho("\nSearch failed due to linting errors!", fg="red")
click.secho("{} errors:".format(len(searcher.errors)), fg="red")
click.secho("---------------------------------", fg="red")
click.echo("\n".join(searcher.errors))
sys.exit(1)
click.echo("Linting passed without errors.")
if report:
click.echo("Writing report...")
report_filename = searcher.report(all_results)
click.echo(f"Report written to {report_filename}.")
elapsed = datetime.datetime.utcnow() - start_time
annotation_count = 0
for filename in all_results:
annotation_count += len(all_results[filename])
click.echo(f"Search found {annotation_count} annotations in {elapsed}.")
except Exception as exc:
click.echo(traceback.print_exc())
fail(str(exc))
@entry_point.command("generate_docs")
@click.option(
"--config_file",
default=".annotations",
help="Path to the configuration file",
type=click.Path(exists=True, dir_okay=False),
)
@click.option("-v", "--verbosity", count=True, help="Verbosity level (-v through -vvv)")
@click.argument("report_files", type=click.File("r"), nargs=-1)
def generate_docs(config_file, verbosity, report_files):
"""
Generate documentation from a code annotations report.
"""
start_time = datetime.datetime.utcnow()
try:
config = AnnotationConfig(config_file, verbosity)
if not report_files:
raise ConfigurationException(
"No report files provided. Please provide one or more report files to generate docs from."
)
for key in (
"report_template_dir",
"rendered_report_dir",
"rendered_report_format",
"rendered_report_source_link_prefix",
):
if not getattr(config, key):
raise ConfigurationException(f"No {key} key in {config_file}")
config.echo(
"Rendering the following reports: \n{}".format(
"\n".join([r.name for r in report_files])
)
)
renderer = ReportRenderer(config, report_files)
renderer.render()
elapsed = datetime.datetime.utcnow() - start_time
click.echo(f"Report rendered in {elapsed.total_seconds()} seconds.")
except Exception as exc:
click.echo(traceback.print_exc())
fail(str(exc))