-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathhelpers.py
More file actions
182 lines (145 loc) · 5.55 KB
/
helpers.py
File metadata and controls
182 lines (145 loc) · 5.55 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
"""
Helpers for code_annotations scripts.
"""
import os
import re
import sys
from io import StringIO
from pprint import pprint
import click
def fail(msg):
"""
Log the message and exit.
Args:
msg: Message to log
"""
click.secho(msg, fg="red")
sys.exit(1)
class VerboseEcho:
"""
Helper to handle verbosity-dependent logging.
"""
verbosity = 1
def __call__(self, output, **kwargs):
"""
Echo the given output regardless of verbosity level.
This is just a convenience method to avoid lots of `self.echo.echo()`.
Args:
output: Text to output
kwargs: Any additional keyword args to pass to click.echo
"""
self.echo(output, **kwargs)
def set_verbosity(self, verbosity):
"""
Override the default verbosity level.
Args:
verbosity: The verbosity level to set to
kwargs: Any additional keyword args to pass to click.echo
"""
self.verbosity = verbosity
self.echo_v(f"Verbosity level set to {verbosity}")
def echo(self, output, verbosity_level=0, **kwargs):
"""
Echo the given output, if over the verbosity threshold.
Args:
output: Text to output
verbosity_level: Only output if our verbosity level is >= this.
kwargs: Any additional keyword args to pass to click.echo
"""
if verbosity_level <= self.verbosity:
click.secho(output, **kwargs)
def echo_v(self, output, **kwargs):
"""
Echo the given output if verbosity level is >= 1.
Args:
output: Text to output
kwargs: Any additional keyword args to pass to click.echo
"""
self.echo(output, 1, **kwargs)
def echo_vv(self, output, **kwargs):
"""
Echo the given output if verbosity level is >= 2.
Args:
output: Text to output
kwargs: Any additional keyword args to pass to click.echo
"""
self.echo(output, 2, **kwargs)
def echo_vvv(self, output, **kwargs):
"""
Echo the given output if verbosity level is >= 3.
Args:
output: Text to output
kwargs: Any additional keyword args to pass to click.echo
"""
self.echo(output, 3, **kwargs)
def pprint(self, data, indent=4, verbosity_level=0):
"""
Pretty-print some data with the given verbosity level.
"""
formatted = StringIO()
pprint(data, indent=indent, stream=formatted)
formatted.seek(0)
self.echo(formatted.read(), verbosity_level=verbosity_level)
def clean_abs_path(filename_to_clean, parent_path):
"""
Safely strips the parent path from the given filename, leaving only the relative path.
Args:
filename_to_clean: Input filename
parent_path: Path to remove from the input
Returns:
Updated path
"""
# If we are operating on only one file we don't know what to strip off here,
# just return the whole thing.
if filename_to_clean == parent_path:
return os.path.basename(filename_to_clean)
return os.path.relpath(filename_to_clean, parent_path)
def get_annotation_regex(annotation_regexes):
"""
Return the full regex to search inside comments for configured annotations.
A successful match against the regex will return two groups of interest: 'token'
and 'data'.
This regular expression supports annotation tokens that span multiple lines. To do
so, prefix each line after the first by at least two leading spaces. E.g:
.. pii: First line
second line
Unfortunately, the indenting spaces will find their way to the content of the "token" group.
Args:
annotation_regexes: List of re.escaped annotation tokens to search for.
Returns:
Regex ready for searching comments for annotations.
"""
annotation_regex = r"""
(?P<space>[\ \t]*) # Leading empty spaces
(?P<token>{tokens}) # Python format string that will be replaced with a
# regex, escaped and then or-joined to make a list
# of the annotation tokens we're looking for
# Ex: (\.\.\ pii\:\:|\.\.\ pii\_types\:\:)
(?P<data> # Captured annotation data
(?: # non-capture mode
. # any non-newline character
| # or new line of multi-line annotation data
(?: # non-capture mode
\n{{1,}} # at least one newline,
(?P=space) # followed by as much space as the prefix,
(?P<indent>\ {{2,}}) # at least two spaces,
(?=[^\ ]) # and a non-space character (look-ahead)
(?!{tokens}) # that does not match any of the token regexes
) #
)* # any number of times
)
"""
annotation_regex = annotation_regex.format(tokens='|'.join(annotation_regexes))
return re.compile(annotation_regex, flags=re.VERBOSE)
def clean_annotation(token, data):
"""
Clean annotation token and data by stripping all trailing/prefix empty spaces.
Args:
token (str)
data (str)
Returns:
(str, str): Tuple of cleaned token, data
"""
token = token.strip()
data = data.strip()
return token, data