Skip to content

Commit 4572ffe

Browse files
committed
#404 Fix for multiple files for *_file fields
* Introduce to use ',' to separate multiple files
1 parent a273c26 commit 4572ffe

File tree

4 files changed

+120
-77
lines changed

4 files changed

+120
-77
lines changed

src/attributecode/gen.py

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

44
# ============================================================================
5-
# Copyright (c) 2013-2018 nexB Inc. http://www.nexb.com/ - All rights reserved.
5+
# Copyright (c) 2013-2019 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
@@ -36,6 +36,7 @@
3636
from attributecode import util
3737
from attributecode.util import add_unc
3838
from attributecode.util import csv
39+
from attributecode.util import file_fields
3940
from attributecode.util import to_posix
4041
from attributecode.util import UNC_PREFIX_POSIX
4142
from attributecode.util import unique
@@ -96,6 +97,24 @@ def check_duplicated_about_resource(inventory_dict):
9697
return errors
9798

9899

100+
def check_newline_in_file_field(inventory_dict):
101+
"""
102+
Return a list of errors for newline characters detected in *_file fields.
103+
"""
104+
errors = []
105+
for component in inventory_dict:
106+
for k in component.keys():
107+
if k in file_fields:
108+
try:
109+
if '\n' in component[k]:
110+
msg = ("New line character detected in '%s' for '%s' which is not supported."
111+
"\nPlease use ',' to declare multiple files.") % (k, component['about_resource'])
112+
errors.append(Error(CRITICAL, msg))
113+
except:
114+
pass
115+
return errors
116+
117+
99118
# TODO: this should be either the CSV or the ABOUT files but not both???
100119
def load_inventory(location, base_dir, reference_dir=None):
101120
"""
@@ -126,6 +145,10 @@ def load_inventory(location, base_dir, reference_dir=None):
126145
if dup_about_resource_err:
127146
errors.extend(dup_about_resource_err)
128147
return errors, abouts
148+
newline_in_file = check_newline_in_file_field(inventory)
149+
if newline_in_file:
150+
errors.extend(newline_in_file)
151+
return errors, abouts
129152
except Exception as e:
130153
# TODO: why catch ALL Exception
131154
msg = "The essential field 'about_resource' is not found in the <input>"
@@ -164,7 +187,7 @@ def load_inventory(location, base_dir, reference_dir=None):
164187
else:
165188
updated_resource_value = basename(resource_path)
166189
fields['about_resource'] = updated_resource_value
167-
190+
168191
ld_errors = about.load_dict(
169192
fields,
170193
base_dir,

src/attributecode/model.py

Lines changed: 80 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
from attributecode.util import boolean_fields
6565
from attributecode.util import copy_license_notice_files
6666
from attributecode.util import csv
67+
from attributecode.util import file_fields
6768
from attributecode.util import filter_errors
6869
from attributecode.util import is_valid_name
6970
from attributecode.util import on_windows
@@ -438,66 +439,68 @@ def _validate(self, *args, **kwargs):
438439
# dict of normalized paths to a location or None
439440
paths = OrderedDict()
440441

441-
for path in self.value:
442-
path = path.strip()
443-
path = util.to_posix(path)
444-
445-
# normalize eventual / to .
446-
# and a succession of one or more ////// to . too
447-
if path.strip() and not path.strip(posixpath.sep):
448-
path = '.'
449-
450-
# removing leading and trailing path separator
451-
# path are always relative
452-
path = path.strip(posixpath.sep)
453-
454-
# the license files, if need to be copied, are located under the path
455-
# set from the 'license-text-location' option, so the tool should check
456-
# at the 'license-text-location' instead of the 'base_dir'
457-
if not (self.base_dir or self.reference_dir):
458-
msg = (u'Field %(name)s: Unable to verify path: %(path)s:'
459-
u' No base directory provided' % locals())
460-
errors.append(Error(ERROR, msg))
461-
location = None
462-
paths[path] = location
463-
continue
464-
465-
if self.reference_dir:
466-
location = posixpath.join(self.reference_dir, path)
467-
else:
468-
# The 'about_resource' should be a joined path with
469-
# the 'about_file_path' and the 'base_dir
470-
if not self.running_inventory and self.about_file_path:
471-
# Get the parent directory of the 'about_file_path'
472-
afp_parent = posixpath.dirname(self.about_file_path)
473-
474-
# Create a relative 'about_resource' path by joining the
475-
# parent of the 'about_file_path' with the value of the
476-
# 'about_resource'
477-
arp = posixpath.join(afp_parent, path)
478-
normalized_arp = posixpath.normpath(arp).strip(posixpath.sep)
479-
location = posixpath.join(self.base_dir, normalized_arp)
480-
else:
481-
location = posixpath.join(self.base_dir, path)
482-
483-
location = util.to_native(location)
484-
location = os.path.abspath(os.path.normpath(location))
485-
location = util.to_posix(location)
486-
location = add_unc(location)
487-
488-
if not os.path.exists(location):
489-
# We don't want to show the UNC_PREFIX in the error message
490-
location = util.to_posix(location.strip(UNC_PREFIX))
491-
msg = (u'Field %(name)s: Path %(location)s not found'
492-
% locals())
493-
# We want to show INFO error for 'about_resource'
494-
if name == u'about_resource':
495-
errors.append(Error(INFO, msg))
442+
for path_value in self.value:
443+
p = path_value.split(',')
444+
for path in p:
445+
path = path.strip()
446+
path = util.to_posix(path)
447+
448+
# normalize eventual / to .
449+
# and a succession of one or more ////// to . too
450+
if path.strip() and not path.strip(posixpath.sep):
451+
path = '.'
452+
453+
# removing leading and trailing path separator
454+
# path are always relative
455+
path = path.strip(posixpath.sep)
456+
457+
# the license files, if need to be copied, are located under the path
458+
# set from the 'license-text-location' option, so the tool should check
459+
# at the 'license-text-location' instead of the 'base_dir'
460+
if not (self.base_dir or self.reference_dir):
461+
msg = (u'Field %(name)s: Unable to verify path: %(path)s:'
462+
u' No base directory provided' % locals())
463+
errors.append(Error(ERROR, msg))
464+
location = None
465+
paths[path] = location
466+
continue
467+
468+
if self.reference_dir:
469+
location = posixpath.join(self.reference_dir, path)
496470
else:
497-
errors.append(Error(CRITICAL, msg))
498-
location = None
499-
500-
paths[path] = location
471+
# The 'about_resource' should be a joined path with
472+
# the 'about_file_path' and the 'base_dir
473+
if not self.running_inventory and self.about_file_path:
474+
# Get the parent directory of the 'about_file_path'
475+
afp_parent = posixpath.dirname(self.about_file_path)
476+
477+
# Create a relative 'about_resource' path by joining the
478+
# parent of the 'about_file_path' with the value of the
479+
# 'about_resource'
480+
arp = posixpath.join(afp_parent, path)
481+
normalized_arp = posixpath.normpath(arp).strip(posixpath.sep)
482+
location = posixpath.join(self.base_dir, normalized_arp)
483+
else:
484+
location = posixpath.join(self.base_dir, path)
485+
486+
location = util.to_native(location)
487+
location = os.path.abspath(os.path.normpath(location))
488+
location = util.to_posix(location)
489+
location = add_unc(location)
490+
491+
if not os.path.exists(location):
492+
# We don't want to show the UNC_PREFIX in the error message
493+
location = util.to_posix(location.strip(UNC_PREFIX))
494+
msg = (u'Field %(name)s: Path %(location)s not found'
495+
% locals())
496+
# We want to show INFO error for 'about_resource'
497+
if name == u'about_resource':
498+
errors.append(Error(INFO, msg))
499+
else:
500+
errors.append(Error(CRITICAL, msg))
501+
location = None
502+
503+
paths[path] = location
501504

502505
self.value = paths
503506
return errors
@@ -516,7 +519,6 @@ def _validate(self, *args, **kwargs):
516519
errors = super(AboutResourceField, self)._validate(*args, ** kwargs)
517520
return errors
518521

519-
520522
class FileTextField(PathField):
521523
"""
522524
A path field pointing to one or more text files such as license files.
@@ -529,10 +531,7 @@ def _validate(self, *args, **kwargs):
529531
of errors. base_dir is the directory used to resolve a file location
530532
from a path.
531533
"""
532-
533534
errors = super(FileTextField, self)._validate(*args, ** kwargs)
534-
super(FileTextField, self)._validate(*args, ** kwargs)
535-
536535
# a FileTextField is a PathField
537536
# self.value is a paths to location ordered dict
538537
# we will replace the location with the text content
@@ -560,7 +559,6 @@ def _validate(self, *args, **kwargs):
560559
self.errors = errors
561560
return errors
562561

563-
564562
class BooleanField(SingleLineField):
565563
"""
566564
An flag field with a boolean value. Validated value is False, True or None.
@@ -976,7 +974,6 @@ def load_dict(self, fields_dict, base_dir, running_inventory=False, reference_di
976974
if not value:
977975
# never return empty or absent fieds
978976
continue
979-
980977
if key == u'licenses':
981978
# FIXME: use a license object instead
982979
lic_key, lic_name, lic_file, lic_url = ungroup_licenses(value)
@@ -1023,7 +1020,6 @@ def dumps(self):
10231020
license_name = []
10241021
license_file = []
10251022
license_url = []
1026-
file_fields = ['about_resource', 'notice_file', 'changelog_file', 'author_file']
10271023
bool_fields = ['redistribute', 'attribute', 'track_changes', 'modified', 'internal_use_only']
10281024
for field in self.all_fields():
10291025
if not field.value and not field.name in bool_fields:
@@ -1034,15 +1030,20 @@ def dumps(self):
10341030
elif field.name == 'license_name' and field.value:
10351031
license_name = field.value
10361032
elif field.name == 'license_file' and field.value:
1037-
license_file = field.value.keys()
1033+
# Restore the original_value as it was parsed for
1034+
# validation purpose
1035+
if field.original_value:
1036+
license_file = field.original_value.split('\n')
1037+
else:
1038+
license_file = field.value.keys()
10381039
elif field.name == 'license_url' and field.value:
10391040
license_url = field.value
10401041

10411042
# No multiple 'about_resource' reference supported.
10421043
# Take the first element (should only be one) in the list for the
10431044
# value of 'about_resource'
10441045
elif field.name in file_fields and field.value:
1045-
data[field.name] = list(field.value.keys())[0]
1046+
data[field.name] = field.original_value
10461047
elif field.name in bool_fields and not field.value == None:
10471048
data[field.name] = field.value
10481049
else:
@@ -1193,8 +1194,17 @@ def about_object_to_list_of_dictionary(abouts):
11931194
"""
11941195
serialized = []
11951196
for about in abouts:
1197+
# Restore the *_file value to the original value
1198+
# The *_file's original_value may be parsed (i.e. split(',))
1199+
# for validation purpose.
1200+
about.license_file.value = about.license_file.original_value
1201+
about.notice_file.value = about.notice_file.original_value
1202+
about.changelog_file.value = about.changelog_file.original_value
1203+
about.author_file.value = about.author_file.original_value
1204+
11961205
# TODO: this wholeblock should be under sd_dict()
11971206
ad = about.as_dict()
1207+
11981208
# Update the 'about_resource' field with the relative path
11991209
# from the output location
12001210
try:
@@ -1207,7 +1217,8 @@ def about_object_to_list_of_dictionary(abouts):
12071217
for resource in about_resource:
12081218
updated_about_resource = posixpath.normpath(posixpath.join(afp_parent, resource))
12091219
if resource == u'.':
1210-
updated_about_resource = updated_about_resource + '/'
1220+
if not updated_about_resource == '/':
1221+
updated_about_resource = updated_about_resource + '/'
12111222
ad['about_resource'] = OrderedDict([(updated_about_resource, None)])
12121223
del ad['about_file_path']
12131224
serialized.append(ad)

src/attributecode/util.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353

5454
# boolean field name
5555
boolean_fields = ['redistribute', 'attribute', 'track_change', 'modified', 'internal_use_only']
56+
file_fields = ['about_resource', 'notice_file', 'changelog_file', 'author_file']
5657

5758
def to_posix(path):
5859
"""
@@ -487,14 +488,13 @@ def ungroup_licenses(licenses):
487488
# FIXME: add docstring
488489
def format_about_dict_for_csv_output(about_dictionary_list):
489490
csv_formatted_list = []
490-
file_fields = ['license_file', 'notice_file', 'changelog_file', 'author_file']
491491
for element in about_dictionary_list:
492492
row_list = OrderedDict()
493493
for key in element:
494494
if element[key]:
495495
if isinstance(element[key], list):
496496
row_list[key] = u'\n'.join((element[key]))
497-
elif key == u'about_resource' or key in file_fields:
497+
elif key == u'about_resource':
498498
row_list[key] = u'\n'.join((element[key].keys()))
499499
else:
500500
row_list[key] = element[key]
@@ -505,7 +505,6 @@ def format_about_dict_for_csv_output(about_dictionary_list):
505505
# FIXME: add docstring
506506
def format_about_dict_for_json_output(about_dictionary_list):
507507
licenses = ['license_key', 'license_name', 'license_file', 'license_url']
508-
file_fields = ['notice_file', 'changelog_file', 'author_file']
509508
json_formatted_list = []
510509
for element in about_dictionary_list:
511510
row_list = OrderedDict()
@@ -526,11 +525,9 @@ def format_about_dict_for_json_output(about_dictionary_list):
526525
elif key == 'license_name':
527526
license_name = element[key]
528527
elif key == 'license_file':
529-
license_file = element[key].keys()
528+
license_file = element[key]
530529
elif key == 'license_url':
531530
license_url = element[key]
532-
elif key in file_fields:
533-
row_list[key] = element[key].keys()
534531
else:
535532
row_list[key] = element[key]
536533

tests/test_gen.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ def test_check_duplicated_about_resource(self):
5656
result = gen.check_duplicated_about_resource(test_dict)
5757
assert expected == result
5858

59+
def test_check_newline_in_file_field(self):
60+
test_dict = [
61+
{'about_resource': '/test/test.c', 'name': 'test.c', 'notice_file': 'NOTICE\nNOTICE2'},
62+
{'about_resource': '/test/abc/', 'version': '1.0', 'name': 'abc'},
63+
{'about_resource': '/test/test.c', 'version': '1.04', 'name': 'test1.c'}]
64+
expected = [
65+
Error(CRITICAL,
66+
"New line character detected in 'notice_file' for '/test/test.c' which is not supported."
67+
"\nPlease use ',' to declare multiple files.")]
68+
result = gen.check_newline_in_file_field(test_dict)
69+
assert expected == result
70+
5971
def test_load_inventory(self):
6072
location = get_test_loc('test_gen/inv.csv')
6173
base_dir = get_temp_dir()

0 commit comments

Comments
 (0)