diff --git a/src/attributecode/model.py b/src/attributecode/model.py index 86bf4542..a9fa25ba 100644 --- a/src/attributecode/model.py +++ b/src/attributecode/model.py @@ -59,9 +59,11 @@ from attributecode import saneyaml from attributecode import util from attributecode.util import add_unc +from attributecode.util import boolean_fields from attributecode.util import copy_license_notice_files from attributecode.util import check_duplicate_keys_about_file from attributecode.util import on_windows +from attributecode.util import wrap_boolean_value from attributecode.util import UNC_PREFIX from attributecode.util import UNC_PREFIX_POSIX @@ -114,7 +116,8 @@ def validate(self, *args, **kwargs): pass""" else: # present fields should have content ... - if not self.has_content: + # Note that 'False' can be a value for boolean field + if not name in boolean_fields and not self.has_content: # ... especially if required if self.required: msg = u'Field %(name)s is required and empty' @@ -563,9 +566,8 @@ def _validate(self, *args, **kwargs): self.value = None elif flag is None: name = self.name - msg = (u'Field %(name)s: field is empty. ' - u'Defaulting flag to no.' % locals()) - errors.append(Error(INFO, msg)) + msg = (u'Field %(name)s: field is present but empty. ' % locals()) + errors.append(Error(WARNING, msg)) self.value = None else: self.value = self.flags.get(flag) @@ -790,6 +792,7 @@ def all_fields(self, with_absent=True, with_empty=True): If with_empty, include empty fields. """ all_fields = [] + for field in list(self.fields.values()) + list(self.custom_fields.values()): if field.required: all_fields.append(field) @@ -800,8 +803,11 @@ def all_fields(self, with_absent=True, with_empty=True): elif field.present: if with_empty: all_fields.append(field) - elif field.present and field.value: - all_fields.append(field) + elif field.present: + if field.value: + all_fields.append(field) + elif field.name in boolean_fields and not field.value == None: + all_fields.append(field) else: if field.present: @@ -985,6 +991,10 @@ def load(self, location, use_mapping=False, mapping_file=None): with codecs.open(loc, encoding='utf-8') as txt: input_text = txt.read() dup_keys = check_duplicate_keys_about_file(input_text) + # The 'Yes' and 'No' will be converted to 'True' and 'False' in the yaml.load() + # Therefore, we need to wrap the original value in quote to prevent + # the conversion + input = wrap_boolean_value(input_text) if dup_keys: msg = ('Duplicated key name(s): %(dup_keys)s' % locals()) errors.append(Error(ERROR, msg % locals())) @@ -999,7 +1009,7 @@ def load(self, location, use_mapping=False, mapping_file=None): and then join with the 'about_resource_path' """ running_inventory = True - errs = self.load_dict(saneyaml.load(input_text), base_dir, running_inventory, use_mapping, mapping_file) + errs = self.load_dict(saneyaml.load(input), base_dir, running_inventory, use_mapping, mapping_file) errors.extend(errs) except Exception as e: msg = 'Cannot load invalid ABOUT file: %(location)r: %(e)r\n' + str(e) diff --git a/src/attributecode/util.py b/src/attributecode/util.py index c06ce2c7..99e7aecc 100644 --- a/src/attributecode/util.py +++ b/src/attributecode/util.py @@ -49,9 +49,11 @@ from attributecode import CRITICAL, INFO from attributecode import Error - on_windows = 'win32' in sys.platform +# boolean field name +boolean_fields = ['redistribute', 'attribute', 'track_change', 'modified'] + def to_posix(path): """ @@ -123,6 +125,20 @@ def check_file_names(paths): seen[path] = orig_path return errors +def wrap_boolean_value(context): + updated_context = '' + for line in context.splitlines(): + """ + wrap the boolean value in quote + """ + key = line.partition(':')[0] + value = line.partition(':')[2].strip() + if key in boolean_fields and value: + value = '"' + value + '"' + updated_context += key + ': ' + value + '\n' + else: + updated_context += line + '\n' + return updated_context def check_duplicate_keys_about_file(context): keys = [] diff --git a/tests/test_gen.py b/tests/test_gen.py index 077a4fb7..a13d2ea3 100644 --- a/tests/test_gen.py +++ b/tests/test_gen.py @@ -181,3 +181,20 @@ def test_deduplicate(self): expected = ['a', 'b', 'd', 'c'] results = gen.deduplicate(items) assert expected == results + + def test_boolean_value_not_lost(self): + location = get_test_loc('gen/inv6.csv') + base_dir = get_temp_dir() + + errors, abouts = gen.generate(location, base_dir) + + in_mem_result = [a.dumps(with_absent=False, with_empty=False) + for a in abouts][0] + expected = (u'about_resource: .\n' + u'name: AboutCode\n' + u'about_resource_path: .\n' + u'version: 0.11.0\n' + u'redistribute: yes\n' + u'attribute: yes\n' + u'modified: no\n') + assert expected == in_mem_result diff --git a/tests/test_model.py b/tests/test_model.py index 5b38276f..3608549d 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -606,7 +606,7 @@ def test_About_dumps_does_not_transform_strings_in_lists(self): notice_file: NOTICE notice_url: redistribute: -attribute: +attribute: yes track_change: modified: changelog_file: diff --git a/tests/testdata/gen/inv6.csv b/tests/testdata/gen/inv6.csv new file mode 100644 index 00000000..25bb717a --- /dev/null +++ b/tests/testdata/gen/inv6.csv @@ -0,0 +1,2 @@ +about_file_path,about_resource,name,version,download_url,description,homepage_url,notes,license_name,license_file,license_url,copyright,notice_file,notice_url,redistribute,attribute,track_change,modified,changelog_file,owner,owner_url,contact,author,vcs_tool,vcs_repository,vcs_path,vcs_tag,vcs_branch,vcs_revision,checksum_md5,checksum_sha1,spec_version +inv/this.ABOUT,.,AboutCode,0.11.0,,,,,,,,,,,Yes,Y,,N,,,,,,,,,,,,,,