Skip to content

Commit fda0a9e

Browse files
committed
Enhance code for supporting license expression #275
* Updated copyright date * Create a new function 'parse_license_expression' * Pass a sorted dictionary for rendering instead of having it to do in the jinja2 * The code did not copy 'notice_file' previously - Fixed. * Introduce a new 'license_expression' field. (I was using 'license' field to put the license expression which may not be suitable) Signed-off-by: Chin Yeung Li <[email protected]>
1 parent a840b9a commit fda0a9e

File tree

5 files changed

+49
-33
lines changed

5 files changed

+49
-33
lines changed

src/attributecode/attrib.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# -*- coding: utf8 -*-
33

44
# ============================================================================
5-
# Copyright (c) 2013-2016 nexB Inc. http://www.nexb.com/ - All rights reserved.
5+
# Copyright (c) 2013-2017 nexB Inc. http://www.nexb.com/ - All rights reserved.
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
88
# You may obtain a copy of the License at
@@ -18,6 +18,7 @@
1818
from __future__ import print_function
1919

2020
import codecs
21+
import collections
2122
import jinja2
2223
import os
2324
from posixpath import basename
@@ -29,6 +30,7 @@
2930
from attributecode import ERROR
3031
from attributecode import Error
3132
from attributecode.licenses import COMMON_LICENSES
33+
from attributecode.model import parse_license_expression
3234
from attributecode.util import add_unc
3335

3436

@@ -61,9 +63,10 @@ def generate(abouts, template_string=None):
6163
else:
6264
license_key = license_text_name
6365
license_key_and_context[license_key] = about.license_file.value[license_text_name]
66+
sorted_license_key_and_context = collections.OrderedDict(sorted(license_key_and_context.items()))
6467
license_text_name_and_key[license_text_name] = license_key
6568

66-
rendered = template.render(abouts=abouts, common_licenses=COMMON_LICENSES, license_key_and_context=license_key_and_context,
69+
rendered = template.render(abouts=abouts, common_licenses=COMMON_LICENSES, license_key_and_context=sorted_license_key_and_context,
6770
license_text_name_and_key=license_text_name_and_key)
6871
except Exception, e:
6972
line = getattr(e, 'lineno', None)
@@ -167,6 +170,17 @@ def generate_and_save(abouts, output_location, mapping, template_loc=None,
167170
if about.about_file_path == fp:
168171
updated_abouts.append(about)
169172

173+
# Parse license_expression and save to the license list
174+
for about in updated_abouts:
175+
if about.license_expression.value:
176+
special_char_in_expression, lic_list = parse_license_expression(about.license_expression.value)
177+
if special_char_in_expression:
178+
msg = (u"The following character(s) cannot be in the licesne_expression: " +
179+
str(special_char_in_expression))
180+
errors.append(Error(ERROR, msg))
181+
else:
182+
about.license.value = lic_list
183+
170184
rendered = generate_from_file(updated_abouts, template_loc=template_loc)
171185

172186
if rendered:

src/attributecode/cmd.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# -*- coding: utf8 -*-
33

44
# ============================================================================
5-
# Copyright (c) 2013-2016 nexB Inc. http://www.nexb.com/ - All rights reserved.
5+
# Copyright (c) 2013-2017 nexB Inc. http://www.nexb.com/ - All rights reserved.
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
88
# You may obtain a copy of the License at
@@ -136,12 +136,12 @@ def inventory(location, output, quiet, format):
136136
'\nExample syntax:\n\n'
137137
"about gen --fetch-license 'api_url' 'api_key'")
138138
)
139-
@click.option('--license-text-location', nargs=1,
139+
@click.option('--license-notice-text-location', nargs=1,
140140
type=click.Path(exists=True, dir_okay=True, readable=True, resolve_path=True),
141141
help="Copy the 'license_file' from the directory to the generated location")
142142
@click.option('--mapping', is_flag=True, help='Use for mapping between the input keys and the ABOUT field names - mapping.config')
143143
@click.option('-q', '--quiet', is_flag=True, help='Do not print any error/warning.')
144-
def gen(location, output, mapping, license_text_location, fetch_license, quiet):
144+
def gen(location, output, mapping, license_notice_text_location, fetch_license, quiet):
145145
"""
146146
Given an inventory of ABOUT files at location, generate ABOUT files in base
147147
directory.
@@ -156,7 +156,7 @@ def gen(location, output, mapping, license_text_location, fetch_license, quiet):
156156
return
157157
click.echo('Generating ABOUT files...')
158158

159-
errors, abouts = attributecode.gen.generate(location, output, mapping, license_text_location, fetch_license)
159+
errors, abouts = attributecode.gen.generate(location, output, mapping, license_notice_text_location, fetch_license)
160160

161161
number_of_about_file = len(abouts)
162162
number_of_error = 0

src/attributecode/gen.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def check_duplicated_about_file_path(inventory_dict):
9797
return errors
9898

9999

100-
def load_inventory(mapping, location, base_dir, license_text_location=None):
100+
def load_inventory(mapping, location, base_dir, license_notice_text_location=None):
101101
"""
102102
Load the inventory file at location. Return a list of errors and a list of About
103103
objects validated against the base_dir.
@@ -150,7 +150,7 @@ def load_inventory(mapping, location, base_dir, license_text_location=None):
150150
loc = posixpath.join(base_dir, afp)
151151
about = model.About(about_file_path=afp)
152152
about.location = loc
153-
ld_errors = about.load_dict(fields, base_dir, license_text_location, with_empty=False)
153+
ld_errors = about.load_dict(fields, base_dir, license_notice_text_location, with_empty=False)
154154
# 'about_resource' field will be generated during the process.
155155
# No error need to be raise for the missing 'about_resource'.
156156
for e in ld_errors:
@@ -163,7 +163,7 @@ def load_inventory(mapping, location, base_dir, license_text_location=None):
163163
return errors, abouts
164164

165165

166-
def generate(location, base_dir, mapping, license_text_location, fetch_license, policy=None, conf_location=None,
166+
def generate(location, base_dir, mapping, license_notice_text_location, fetch_license, policy=None, conf_location=None,
167167
with_empty=False, with_absent=False):
168168
"""
169169
Load ABOUT data from an inventory at csv_location. Write ABOUT files to
@@ -182,7 +182,7 @@ def generate(location, base_dir, mapping, license_text_location, fetch_license,
182182
gen_license = True
183183

184184
bdir = to_posix(base_dir)
185-
errors, abouts = load_inventory(mapping, location, bdir, license_text_location)
185+
errors, abouts = load_inventory(mapping, location, bdir, license_notice_text_location)
186186

187187
if gen_license:
188188
license_dict, err = model.pre_process_and_fetch_license_dict(abouts, api_url, api_key)

src/attributecode/model.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
from attributecode import api
4747
from attributecode import saneyaml
4848
from attributecode import util
49-
from attributecode.util import add_unc, UNC_PREFIX, UNC_PREFIX_POSIX, on_windows, copy_license_files
49+
from attributecode.util import add_unc, UNC_PREFIX, UNC_PREFIX_POSIX, on_windows, copy_license_notice_files
5050
from license_expression import Licensing
5151

5252

@@ -363,7 +363,7 @@ def _validate(self, *args, **kwargs):
363363
"""
364364
errors = super(PathField, self)._validate(*args, ** kwargs)
365365
self.base_dir = kwargs.get('base_dir')
366-
self.license_text_location = kwargs.get('license_text_location')
366+
self.license_notice_text_location = kwargs.get('license_notice_text_location')
367367

368368
if self.base_dir:
369369
self.base_dir = util.to_posix(self.base_dir)
@@ -387,9 +387,9 @@ def _validate(self, *args, **kwargs):
387387
# the license files, if need to be copied, are located under the path
388388
# set from the 'license-text-location' option, so the tool should check
389389
# at the 'license-text-location' instead of the 'base_dir'
390-
if self.base_dir or self.license_text_location:
391-
if self.license_text_location:
392-
location = posixpath.join(self.license_text_location, path)
390+
if self.base_dir or self.license_notice_text_location:
391+
if self.license_notice_text_location:
392+
location = posixpath.join(self.license_notice_text_location, path)
393393
else:
394394
location = posixpath.join(self.base_dir, path)
395395
location = util.to_native(location)
@@ -479,7 +479,7 @@ def _validate(self, *args, **kwargs):
479479
try:
480480
# TODO: we have lots the location by replacing it with a text
481481
location = add_unc(location)
482-
text = codecs.open(location, encoding='utf-8').read()
482+
text = codecs.open(location, encoding='utf-8', errors='ignore').read()
483483
self.value[path] = text
484484
except Exception, e:
485485
# only keep the first 100 char of the exception
@@ -584,14 +584,14 @@ def __eq__(self, other):
584584
and self.value == other.value)
585585

586586

587-
def validate_fields(fields, base_dir, license_text_location=None):
587+
def validate_fields(fields, base_dir, license_notice_text_location=None):
588588
"""
589589
Validate a sequence of Field objects. Return a list of errors.
590590
Validation may update the Field objects as needed as a side effect.
591591
"""
592592
errors = []
593593
for f in fields:
594-
val_err = f.validate(base_dir=base_dir, license_text_location=license_text_location)
594+
val_err = f.validate(base_dir=base_dir, license_notice_text_location=license_notice_text_location)
595595
errors.extend(val_err)
596596
return errors
597597

@@ -629,7 +629,8 @@ def create_fields(self):
629629
('home_url', UrlField()),
630630
('notes', StringField()),
631631

632-
('license', StringField()),
632+
('license', ListField()),
633+
('license_expression', StringField()),
633634
('license_name', StringField()),
634635
('license_file', FileTextField()),
635636
('license_url', UrlField()),
@@ -870,25 +871,25 @@ def hydrate(self, fields):
870871
errors.append(Error(INFO, msg % locals()))
871872
return errors
872873

873-
def process(self, fields, base_dir=None, license_text_location=None):
874+
def process(self, fields, base_dir=None, license_notice_text_location=None):
874875
"""
875876
Hydrate and validate a sequence of field name/value tuples from an
876877
ABOUT file. Return a list of errors.
877878
"""
878879
self.base_dir = base_dir
879-
self.license_text_location = license_text_location
880+
self.license_notice_text_location = license_notice_text_location
880881
afp = self.about_file_path
881882
errors = []
882883
hydratation_errors = self.hydrate(fields)
883884
errors.extend(hydratation_errors)
884885

885886
# We want to copy the license_files before the validation
886-
if license_text_location:
887-
copy_license_files(fields, base_dir, license_text_location, afp)
887+
if license_notice_text_location:
888+
copy_license_notice_files(fields, base_dir, license_notice_text_location, afp)
888889

889890
# we validate all fields, not only these hydrated
890891
all_fields = self.all_fields()
891-
validation_errors = validate_fields(all_fields, self.base_dir, self.license_text_location)
892+
validation_errors = validate_fields(all_fields, self.base_dir, self.license_notice_text_location)
892893
errors.extend(validation_errors)
893894

894895
# do not forget to resolve about resource paths
@@ -937,7 +938,7 @@ def load_lines(self, lines, base_dir):
937938
self.errors = errors
938939
return errors
939940

940-
def load_dict(self, fields_dict, base_dir, license_text_location=None, with_empty=True):
941+
def load_dict(self, fields_dict, base_dir, license_notice_text_location=None, with_empty=True):
941942
"""
942943
Load the ABOUT file from a fields name/value mapping.
943944
If with_empty, create fields with no value for empty fields.
@@ -947,7 +948,7 @@ def load_dict(self, fields_dict, base_dir, license_text_location=None, with_empt
947948
fields = fields_dict.items()
948949
if not with_empty:
949950
fields = [(n, v) for n, v in fields_dict.items() if v]
950-
errors = self.process(fields, base_dir, license_text_location)
951+
errors = self.process(fields, base_dir, license_notice_text_location)
951952
self.errors = errors
952953
return errors
953954

@@ -1009,8 +1010,9 @@ def dump_lic(self, location, license_dict):
10091010
if not posixpath.exists(parent):
10101011
os.makedirs(add_unc(parent))
10111012

1012-
if self.license.present and not self.license_file.present:
1013-
special_char_in_expression, lic_list = parse_license_expression(self.license.value)
1013+
if self.license_expression.present and not self.license_file.present:
1014+
special_char_in_expression, lic_list = parse_license_expression(self.license_expression.value)
1015+
self.license = lic_list
10141016
if not special_char_in_expression:
10151017
for lic_key in lic_list:
10161018
try:
@@ -1347,8 +1349,8 @@ def pre_process_and_fetch_license_dict(abouts, api_url, api_key):
13471349
auth_error = Error(ERROR, u"Authorization denied. Invalid '--api_key'. License generation is skipped.")
13481350
if auth_error in errors:
13491351
break
1350-
if about.license.present:
1351-
special_char_in_expression, lic_list = parse_license_expression(about.license.value)
1352+
if about.license_expression.present:
1353+
special_char_in_expression, lic_list = parse_license_expression(about.license_expression.value)
13521354
if special_char_in_expression:
13531355
msg = (u"The following character(s) cannot be in the licesne_expression: " +
13541356
str(special_char_in_expression))

src/attributecode/util.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,13 +411,13 @@ def add_unc(location):
411411
return location
412412

413413

414-
def copy_license_files(fields, base_dir, license_text_location, afp):
414+
def copy_license_notice_files(fields, base_dir, license_notice_text_location, afp):
415415
lic_name = u''
416416
for key, value in fields:
417-
if key == u'license_file':
417+
if key == u'license_file' or key == u'notice_file':
418418
lic_name = value
419419

420-
from_lic_path = posixpath.join(to_posix(license_text_location), lic_name)
420+
from_lic_path = posixpath.join(to_posix(license_notice_text_location), lic_name)
421421
about_file_dir = dirname(to_posix(afp)).lstrip('/')
422422
to_lic_path = posixpath.join(to_posix(base_dir), about_file_dir)
423423

0 commit comments

Comments
 (0)