Skip to content

Commit d12976a

Browse files
committed
Fixed #139 - Custom key name for the inventory output
* Added a `--mapping-output` option Signed-off-by: Chin Yeung Li <[email protected]>
1 parent 0eb8825 commit d12976a

File tree

7 files changed

+113
-8
lines changed

7 files changed

+113
-8
lines changed

USAGE.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ inventory
267267
--mapping Use file mapping.config to collect the defined not supported fields in ABOUT files.
268268
--mapping-file Use a custom mapping file with mapping between input
269269
keys and ABOUT field names.
270+
--mapping-output FILE Use a custom mapping file with mapping between
271+
ABOUT field names and output keys
270272
--verbose Show all the errors and warning.
271273
-q, --quiet Do not print any error/warning.
272274
-h, --help Show this message and exit.
@@ -304,6 +306,18 @@ Options
304306

305307
$ about inventory --mapping-file CUSTOM_MAPPING_FILE_PATH LOCATION OUTPUT
306308

309+
--mapping-output
310+
311+
Same behavior as `--mapping-file` but with custom mapping file
312+
In the custom mapping file, the left side is the custom key name where
313+
the right side is the ABOUT field name. For instance,
314+
Component: name
315+
316+
The "Component" is a custom field name for the output
317+
The "name" is one of the defaul ABOUT field name that user want to convert
318+
319+
$ about inventory --mapping-output CUSTOM_MAPPING_FILE_PATH LOCATION OUTPUT
320+
307321
--verbose
308322

309323
This option tells the tool to show all errors found.

docs/CHANGELOG.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* `check` command will not counted INFO message as error when `--verbose` is set
88
* Update `track_change` to `track_changes`
99
* Add support for `author_file`
10-
* New `--filter` option for `inventory`
10+
* New `--filter` and `--mapping-output` options for `inventory`
1111

1212
2018-6-25
1313

src/attributecode/cmd.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ def cli():
123123
type=click.Path(exists=True, dir_okay=True, readable=True, resolve_path=True),
124124
help='Use a custom mapping file with mapping between input keys and ABOUT field names.')
125125

126+
@click.option('--mapping-output', metavar='FILE', nargs=1,
127+
type=click.Path(exists=True, dir_okay=True, readable=True, resolve_path=True),
128+
help='Use a custom mapping file with mapping between ABOUT field names and output keys')
129+
126130
@click.option('--verbose', is_flag=True, default=False,
127131
help='Show all errors and warnings. '
128132
'By default, the tool only prints these '
@@ -136,7 +140,7 @@ def cli():
136140

137141
@click.help_option('-h', '--help')
138142

139-
def inventory(location, output, mapping, mapping_file, filter, quiet, format, verbose):
143+
def inventory(location, output, mapping, mapping_file, mapping_output, filter, quiet, format, verbose):
140144
"""
141145
Collect a JSON or CSV inventory of components from .ABOUT files.
142146
@@ -186,7 +190,7 @@ def inventory(location, output, mapping, mapping_file, filter, quiet, format, ve
186190
break
187191

188192
if not halt_output:
189-
write_errors = model.write_output(updated_abouts, output, format)
193+
write_errors = model.write_output(updated_abouts, output, format, mapping_output)
190194
for err in write_errors:
191195
errors.append(err)
192196
else:

src/attributecode/model.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,27 +1300,35 @@ def about_object_to_list_of_dictionary(abouts, with_absent=False, with_empty=Tru
13001300
return abouts_dictionary_list
13011301

13021302

1303-
def write_output(abouts, location, format, with_absent=False, with_empty=True):
1303+
def write_output(abouts, location, format, mapping_output=None, with_absent=False, with_empty=True):
13041304
"""
13051305
Write a CSV/JSON file at location given a list of About objects
13061306
"""
13071307
errors = []
13081308
about_dictionary_list = about_object_to_list_of_dictionary(abouts, with_absent, with_empty)
1309+
if mapping_output:
1310+
updated_dictionary_list = util.update_about_dictionary_keys(about_dictionary_list, mapping_output)
1311+
else:
1312+
updated_dictionary_list = about_dictionary_list
13091313
location = add_unc(location)
13101314
with codecs.open(location, mode='wb', encoding='utf-8') as output_file:
13111315
if format == 'csv':
13121316
fieldnames = field_names(abouts)
1313-
writer = csv.DictWriter(output_file, fieldnames)
1317+
if mapping_output:
1318+
updated_fieldnames = util.update_fieldnames(fieldnames, mapping_output)
1319+
else:
1320+
updated_fieldnames = fieldnames
1321+
writer = csv.DictWriter(output_file, updated_fieldnames)
13141322
writer.writeheader()
1315-
for row in about_dictionary_list:
1323+
for row in updated_dictionary_list:
13161324
# See https://github.com/dejacode/about-code-tool/issues/167
13171325
try:
13181326
writer.writerow(row)
13191327
except Exception as e:
13201328
msg = u'Generation skipped for ' + row['about_file_path'] + u' : ' + str(e)
13211329
errors.append(Error(CRITICAL, msg))
13221330
else:
1323-
output_file.write(json.dumps(about_dictionary_list, indent=2))
1331+
output_file.write(json.dumps(updated_dictionary_list, indent=2))
13241332
return errors
13251333

13261334

src/attributecode/util.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,35 @@ def get_mapping(location=None):
321321
return mapping
322322

323323

324+
def get_output_mapping(location):
325+
"""
326+
Return a mapping of About key names to user key names by reading the
327+
user's input file from location. The format of the user key names will
328+
NOT be formatted (i.e. keys will NOT be forced to convert to lower case)
329+
"""
330+
if not os.path.exists(location):
331+
return {}
332+
333+
mapping = {}
334+
try:
335+
with open(location) as mapping_file:
336+
for line in mapping_file:
337+
if not line or not line.strip() or line.strip().startswith('#'):
338+
continue
339+
340+
if ':' in line:
341+
key, sep, value = line.partition(':')
342+
user_key = key.strip()
343+
about_key = value.strip()
344+
mapping[about_key] = user_key
345+
346+
except Exception as e:
347+
print(repr(e))
348+
print('Cannot open or process file at %(location)r.' % locals())
349+
# FIXME: this is rather brutal
350+
sys.exit(errno.EACCES)
351+
return mapping
352+
324353
def apply_mapping(abouts, alternate_mapping=None):
325354
"""
326355
Given a list of About data dictionaries and a dictionary of
@@ -578,3 +607,32 @@ def inventory_filter(abouts, filter_dict):
578607
# The current about object does not have the defined attribute
579608
continue
580609
return updated_abouts
610+
611+
612+
def update_fieldnames(fieldnames, mapping_output):
613+
map = get_output_mapping(mapping_output)
614+
updated_header = []
615+
for name in fieldnames:
616+
try:
617+
updated_header.append(map[name])
618+
except:
619+
updated_header.append(name)
620+
return updated_header
621+
622+
623+
def update_about_dictionary_keys(about_dictionary_list, mapping_output):
624+
output_map = get_output_mapping(mapping_output)
625+
updated_dict_list = []
626+
for element in about_dictionary_list:
627+
updated_ordered_dict = OrderedDict()
628+
for about_key, value in element.items():
629+
update_key = False
630+
for custom_key in output_map:
631+
if about_key == custom_key:
632+
update_key = True
633+
updated_ordered_dict[output_map[custom_key]] = value
634+
break
635+
if not update_key:
636+
updated_ordered_dict[about_key] = value
637+
updated_dict_list.append(updated_ordered_dict)
638+
return updated_dict_list

tests/test_util.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,4 +481,24 @@ def test_inventory_filter(self):
481481
# the filtering
482482
updated_abouts = util.inventory_filter(abouts, filter_dict)
483483
for about in updated_abouts:
484-
assert about.name.value == 'simple'
484+
assert about.name.value == 'simple'
485+
486+
def test_update_fieldnames(self):
487+
mapping_output = get_test_loc('util/mapping_output')
488+
fieldnames = ['about_file_path', 'name', 'version']
489+
expexted_fieldnames = ['about_file_path', 'Component', 'version']
490+
result = util.update_fieldnames(fieldnames, mapping_output)
491+
assert expexted_fieldnames == result
492+
493+
def test_update_about_dictionary_keys(self):
494+
mapping_output = get_test_loc('util/mapping_output')
495+
about_ordered_dict = OrderedDict()
496+
about_ordered_dict['about_resource_path'] = '.'
497+
about_ordered_dict['name'] = 'test.c'
498+
about_dict_list = [about_ordered_dict]
499+
expected_output_dict = OrderedDict()
500+
expected_output_dict['about_resource_path'] = '.'
501+
expected_output_dict['Component'] = 'test.c'
502+
expected_dict_list = [expected_output_dict]
503+
result = util.update_about_dictionary_keys(about_dict_list, mapping_output)
504+
assert expected_dict_list == result

tests/testdata/util/mapping_output

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Component: name

0 commit comments

Comments
 (0)