Skip to content

Commit 6e16251

Browse files
committed
Stream line attribution generation code
* Update attrib.generate function to return an Error and the generated text to simplify error handling in the cmd module * Rename attrib.generate function args template_string to template and vartest to variables. Use variables everywhere too. * Move variables parsing and validatio to the cmd module * Remove any reference to mapping_output: Mapping can now only be used when using a CSV input. * JSON outputs can no longer use mappings * Remove use_mapping everywhere and use only mapping_file instead. mapping_file is either a builtin or custom mapping file. * add validation to every commands to avoid the combo of --mapping and --mapping-file that are mutually exclusive. * use click.UsageError for cmd validation * DEFAULT_MAPPING is now top level constant for the default mapping config file location * Improve docstrings * validate template early in cmd.attrib Signed-off-by: Philippe Ombredanne <[email protected]>
1 parent 2757387 commit 6e16251

File tree

10 files changed

+343
-302
lines changed

10 files changed

+343
-302
lines changed

src/attributecode/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from collections import namedtuple
2222
import logging
23+
import os
2324

2425
try:
2526
unicode # Python 2
@@ -110,3 +111,7 @@ def clean_string(s):
110111
DEBUG : 'DEBUG',
111112
NOTSET : 'NOTSET'
112113
}
114+
115+
116+
DEFAULT_MAPPING = os.path.join(os.path.abspath(
117+
os.path.dirname(__file__)), 'mapping.config')

src/attributecode/attrib.py

Lines changed: 78 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import jinja2
2727

28+
from attributecode import CRITICAL
2829
from attributecode import ERROR
2930
from attributecode import Error
3031
from attributecode.licenses import COMMON_LICENSES
@@ -34,20 +35,31 @@
3435

3536

3637
# FIXME: the template dir should be outside the code tree
37-
default_template = os.path.join(
38+
DEFAULT_TEMPLATE_FILE = os.path.join(
3839
os.path.dirname(os.path.realpath(__file__)), 'templates', 'default_html.template')
3940

4041

41-
def generate(abouts, template_string=None, vartext_dict=None):
42+
def generate(abouts, template=None, variables=None):
4243
"""
43-
Generate and return attribution text from a list of About objects and a
44-
template string.
45-
The returned rendered text may contain template processing error messages.
44+
Generate an attribution text from an `abouts` list of About objects, a
45+
`template` template text and a `variables` optional mapping of extra
46+
variables.
47+
48+
Return a tuple of (error, attribution text) where error is an Error object
49+
or None and attribution text is the generated text or None.
4650
"""
47-
syntax_error = check_template(template_string)
48-
if syntax_error:
49-
return 'Template validation error at line: %r: %r' % (syntax_error)
50-
template = jinja2.Template(template_string)
51+
rendered = None
52+
error = None
53+
template_error = check_template(template)
54+
if template_error:
55+
lineno, message = template_error
56+
error = Error(
57+
CRITICAL,
58+
'Template validation error at line: {lineno}: "{message}"'.format(**locals())
59+
)
60+
return error, None
61+
62+
template = jinja2.Template(template)
5163

5264
try:
5365
captured_license = []
@@ -106,18 +118,25 @@ def generate(abouts, template_string=None, vartext_dict=None):
106118

107119
# Get the current UTC time
108120
utcnow = datetime.datetime.utcnow()
109-
rendered = template.render(abouts=abouts, common_licenses=COMMON_LICENSES,
110-
license_key_and_context=sorted_license_key_and_context,
111-
license_file_name_and_key=license_file_name_and_key,
112-
license_key_to_license_name=license_key_to_license_name,
113-
license_name_to_license_key=license_name_to_license_key,
114-
utcnow=utcnow, vartext_dict=vartext_dict)
121+
rendered = template.render(
122+
abouts=abouts, common_licenses=COMMON_LICENSES,
123+
license_key_and_context=sorted_license_key_and_context,
124+
license_file_name_and_key=license_file_name_and_key,
125+
license_key_to_license_name=license_key_to_license_name,
126+
license_name_to_license_key=license_name_to_license_key,
127+
utcnow=utcnow,
128+
variables=variables
129+
)
115130
except Exception as e:
116-
line = getattr(e, 'lineno', None)
117-
ln_msg = ' at line: %r' % line if line else ''
118-
err = getattr(e, 'message', '')
119-
return 'Template processing error%(ln_msg)s: %(err)r' % locals()
120-
return rendered
131+
lineno = getattr(e, 'lineno', '') or ''
132+
if lineno:
133+
lineno = ' at line: {}'.format(lineno)
134+
err = getattr(e, 'message', '') or ''
135+
error = Error(
136+
CRITICAL,
137+
'Template processing error {lineno}: {err}'.format(**locals()),
138+
)
139+
return error, rendered
121140

122141

123142
def check_template(template_string):
@@ -131,43 +150,46 @@ def check_template(template_string):
131150
return e.lineno, e.message
132151

133152

134-
def generate_from_file(abouts, template_loc=default_template, vartext_dict=None):
153+
def generate_from_file(abouts, template_loc=DEFAULT_TEMPLATE_FILE, variables=None):
135154
"""
136-
Generate and return attribution string from a list of About objects and a
137-
template location.
155+
Generate an attribution text from an `abouts` list of About objects, a
156+
`template_loc` template file location and a `variables` optional
157+
mapping of extra variables.
158+
159+
Return a tuple of (error, attribution text) where error is an Error object
160+
or None and attribution text is the generated text or None.
138161
"""
139162
template_loc = add_unc(template_loc)
140163
with codecs.open(template_loc, 'rb', encoding='utf-8') as tplf:
141164
tpls = tplf.read()
142-
return generate(abouts, template_string=tpls, vartext_dict=vartext_dict)
165+
return generate(abouts, template=tpls, variables=variables)
143166

144167

145-
def generate_and_save(abouts, output_location, use_mapping=False, mapping_file=None,
146-
template_loc=None, inventory_location=None, vartext=None):
168+
def generate_and_save(abouts, output_location, template_loc=None, variables=None,
169+
mapping_file=None, inventory_location=None):
147170
"""
148-
Generate attribution file using the `abouts` list of About object
149-
at `output_location`.
150-
Return a list of errors if any.
151-
152-
Optionally use the mapping.config file if `use_mapping` is True.
171+
Generate an attribution text from an `abouts` list of About objects, a
172+
`template_loc` template file location and a `variables` optional
173+
mapping of extra variables. Save the generated attribution text in the
174+
`output_location` file.
175+
Return a list of Error objects if any.
153176
154-
Optionally use the custom mapping file if mapping_file is set.
177+
FIXME: these three argument are too complex:
155178
156-
Use the optional `template_loc` custom temaplte or a default template.
157-
158-
Optionally filter `abouts` object based on the inventory JSON or
159-
CSV at `inventory_location`.
179+
Optionally use the `mapping_file` mapping config if provided.
180+
Optionally filter `abouts` object based on the inventory JSON or CSV at `inventory_location`.
160181
"""
161182
updated_abouts = []
162183
lstrip_afp = []
163184
afp_list = []
164185
not_match_path = []
165186
errors = []
166-
vartext_dict = {}
167187

168188
if not inventory_location:
169189
updated_abouts = abouts
170-
# Do the following if an filter list (inventory_location) is provided
190+
191+
# FIXME: this is too complex
192+
# Do the following if a filter list (inventory_location) is provided
171193
else:
172194
if not os.path.exists(inventory_location):
173195
# FIXME: this message does not make sense
@@ -176,12 +198,11 @@ def generate_and_save(abouts, output_location, use_mapping=False, mapping_file=N
176198
return errors
177199

178200
if inventory_location.endswith('.csv') or inventory_location.endswith('.json'):
179-
# FIXME: we should use the same inventory lodaing that we use everywhere!!!!
201+
# FIXME: we should use the same inventory loading that we use everywhere
180202

181203
try:
182204
# Return a list which contains only the about file path
183-
about_list = get_about_file_path(
184-
inventory_location, use_mapping=use_mapping, mapping_file=mapping_file)
205+
about_list = get_about_file_path(inventory_location, mapping_file=mapping_file)
185206
# FIXME: why catching all exceptions?
186207
except Exception:
187208
# 'about_file_path' key/column doesn't exist
@@ -227,23 +248,24 @@ def generate_and_save(abouts, output_location, use_mapping=False, mapping_file=N
227248

228249
# Parse license_expression and save to the license list
229250
for about in updated_abouts:
230-
if about.license_expression.value:
231-
special_char_in_expression, lic_list = parse_license_expression(about.license_expression.value)
232-
if special_char_in_expression:
233-
msg = (u"The following character(s) cannot be in the licesne_expression: " +
234-
str(special_char_in_expression))
235-
errors.append(Error(ERROR, msg))
236-
else:
237-
about.license_key.value = lic_list
251+
if not about.license_expression.value:
252+
continue
253+
special_char_in_expression, lic_list = parse_license_expression(about.license_expression.value)
254+
if special_char_in_expression:
255+
msg = (u"The following character(s) cannot be in the licesne_expression: " +
256+
str(special_char_in_expression))
257+
errors.append(Error(ERROR, msg))
258+
else:
259+
about.license_key.value = lic_list
238260

239-
# Parse the vartext and save to the vartext dictionary
240-
if vartext:
241-
for var in vartext:
242-
key = var.partition('=')[0]
243-
value = var.partition('=')[2]
244-
vartext_dict[key] = value
261+
rendering_error, rendered = generate_from_file(
262+
updated_abouts,
263+
template_loc=template_loc,
264+
variables=variables
265+
)
245266

246-
rendered = generate_from_file(updated_abouts, template_loc=template_loc, vartext_dict=vartext_dict)
267+
if rendering_error:
268+
errors.append(rendering_error)
247269

248270
if rendered:
249271
output_location = add_unc(output_location)

0 commit comments

Comments
 (0)