-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathcli.py
More file actions
217 lines (182 loc) · 7.66 KB
/
cli.py
File metadata and controls
217 lines (182 loc) · 7.66 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
"""
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='', 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()
config = AnnotationConfig(config_file, report_path, verbosity)
searcher = DjangoSearch(config)
# 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)
for key in (
'report_template_dir',
'rendered_report_dir',
'rendered_report_file_extension',
'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))