@@ -92,7 +92,7 @@ def main(self, args=None, prog_name=None, complete_var=None,
9292@click .group (name = 'about' )
9393@click .version_option (version = __version__ , prog_name = prog_name , message = intro )
9494@click .help_option ('-h' , '--help' )
95- def cli ():
95+ def about ():
9696 """
9797Generate licensing attribution and credit notices from .ABOUT files and inventories.
9898
@@ -122,16 +122,32 @@ def validate_filter(ctx, param, value):
122122 return kvals
123123
124124
125- @cli .command (cls = AboutCommand ,
126- short_help = 'Collect .ABOUT files and write an inventory as CSV or JSON.' )
125+ def validate_mapping (mapping , mapping_file ):
126+ """
127+ Return a mapping_file or None.
128+ Raise a UsageError on errors.
129+ """
130+ if mapping and mapping_file :
131+ raise click .UsageError (
132+ 'Invalid options combination: '
133+ '--mapping and --mapping-file are ultually exclusive.' )
134+ if mapping :
135+ return DEFAULT_MAPPING
136+ return mapping_file or None
137+
138+
139+ @about .command (cls = AboutCommand ,
140+ short_help = 'Collect the inventory of .ABOUT files to a CSV or JSON file.' )
127141
128142@click .argument ('location' ,
129143 required = True ,
144+ metavar = 'LOCATION' ,
130145 type = click .Path (
131146 exists = True , file_okay = True , dir_okay = True , readable = True , resolve_path = True ))
132147
133148@click .argument ('output' ,
134149 required = True ,
150+ metavar = 'OUTPUT' ,
135151 type = click .Path (exists = False , dir_okay = False , writable = True , resolve_path = True ))
136152
137153# fIXME: this is too complex and should be removed
@@ -150,13 +166,16 @@ def validate_filter(ctx, param, value):
150166
151167@click .option ('--mapping' ,
152168 is_flag = True ,
153- help = 'Use the default file mapping.config (./attributecode/mapping.config) '
154- 'with mapping between input keys and ABOUT field names.' )
169+ help = 'Use the default built-in "mapping.config" file '
170+ 'with mapping between input keys and .ABOUT field names.'
171+ 'Cannot be combined with the --mapping-file option.' )
155172
156173@click .option ('--mapping-file' ,
157174 metavar = 'FILE' ,
158- type = click .Path (exists = True , dir_okay = True , readable = True , resolve_path = True ),
159- help = 'Use a custom mapping file with mapping between input keys and ABOUT field names.' )
175+ type = click .Path (exists = True , dir_okay = False , readable = True , resolve_path = True ),
176+ help = 'Path to an optional custom mapping FILE '
177+ 'with mapping between input keys and .ABOUT field names. '
178+ 'Cannot be combined with the --mapping option.' )
160179
161180@click .option ('-q' , '--quiet' ,
162181 is_flag = True ,
@@ -171,7 +190,7 @@ def validate_filter(ctx, param, value):
171190def inventory (location , output , mapping , mapping_file ,
172191 format , filter , quiet , verbose ): # NOQA
173192 """
174- Collect a JSON or CSV inventory of packages from .ABOUT files .
193+ Collect the inventory of .ABOUT file data as CSV or JSON .
175194
176195LOCATION: Path to an .ABOUT file or a directory with .ABOUT files.
177196
@@ -181,20 +200,12 @@ def inventory(location, output, mapping, mapping_file,
181200 print_version ()
182201 click .echo ('Collecting inventory from ABOUT files...' )
183202
184- if not os .path .exists (os .path .dirname (output )):
185- # FIXME: there is likely a better way to return an error
186- raise click .UsageError ('ERROR: <OUTPUT> path does not exists.' )
187-
188203 # FIXME: do we really want to continue support zip as an input?
189204 if location .lower ().endswith ('.zip' ):
190205 # accept zipped ABOUT files as input
191206 location = extract_zip (location )
192207
193- if mapping and mapping_file :
194- raise click .UsageError ('Invalid combination of options: --mapping and --mapping-file' )
195-
196- if mapping :
197- mapping_file = DEFAULT_MAPPING
208+ mapping_file = validate_mapping (mapping , mapping_file )
198209
199210 errors , abouts = collect_inventory (location , mapping_file = mapping_file )
200211
@@ -203,7 +214,7 @@ def inventory(location, output, mapping, mapping_file,
203214 abouts = inventory_filter (abouts , filter )
204215
205216 # Do not write the output if one of the ABOUT files has duplicated keys
206- # TODO: why stop only for this message??????
217+ # TODO: why do this check here?? Also if this is the place, we should list what the errors are.
207218 dup_error_msg = u'Duplicated keys'
208219 halt_output = False
209220 for err in errors :
@@ -231,16 +242,28 @@ def inventory(location, output, mapping, mapping_file,
231242# gen subcommand
232243######################################################################
233244
234- @cli .command (cls = AboutCommand ,
245+ def validate_location_extension (ctx , param , value ):
246+ if not value :
247+ return
248+ if not value .endswith (('.csv' , '.json' ,)):
249+ raise click .UsageError (
250+ 'Invalid input file extension: must be one .csv or .json.' )
251+ return value
252+
253+
254+ @about .command (cls = AboutCommand ,
235255 short_help = 'Generate .ABOUT files from an inventory as CSV or JSON.' )
236256
237257@click .argument ('location' ,
238258 required = True ,
239- type = click .Path (exists = True , file_okay = True , dir_okay = False , readable = True , resolve_path = True ))
259+ metavar = 'LOCATION' ,
260+ type = click .Path (
261+ exists = True , file_okay = True , dir_okay = True , readable = True , resolve_path = True ))
240262
241263@click .argument ('output' ,
242264 required = True ,
243- type = click .Path (exists = True , writable = True , file_okay = False , dir_okay = True , resolve_path = True ))
265+ metavar = 'OUTPUT' ,
266+ type = click .Path (exists = True , file_okay = False , writable = True , resolve_path = True ))
244267
245268# FIXME: the CLI UX should be improved with two separate options for API key and URL
246269@click .option ('--fetch-license' ,
@@ -257,13 +280,16 @@ def inventory(location, output, mapping, mapping_file,
257280
258281@click .option ('--mapping' ,
259282 is_flag = True ,
260- help = 'Use the default file mapping.config (./attributecode/mapping.config) '
261- 'with mapping between input keys and ABOUT field names.' )
283+ help = 'Use the default built-in "mapping.config" file '
284+ 'with mapping between input keys and .ABOUT field names.'
285+ 'Cannot be combined with the --mapping-file option.' )
262286
263287@click .option ('--mapping-file' ,
264288 metavar = 'FILE' ,
265- type = click .Path (exists = True , dir_okay = True , readable = True , resolve_path = True ),
266- help = 'Use a custom mapping file with mapping between input keys and ABOUT field names.' )
289+ type = click .Path (exists = True , dir_okay = False , readable = True , resolve_path = True ),
290+ help = 'Path to an optional custom mapping FILE '
291+ 'with mapping between input keys and .ABOUT field names. '
292+ 'Cannot be combined with the --mapping option.' )
267293
268294@click .option ('-q' , '--quiet' ,
269295 is_flag = True ,
@@ -281,7 +307,7 @@ def gen(location, output,
281307 mapping , mapping_file ,
282308 quiet , verbose ):
283309 """
284- Generate .ABOUT files in OUTPUT directory from a JSON or CSV inventory of .ABOUT files at LOCATION.
310+ Generate .ABOUT files in OUTPUT from an inventory of .ABOUT files at LOCATION.
285311
286312LOCATION: Path to a JSON or CSV inventory file.
287313
@@ -291,10 +317,7 @@ def gen(location, output,
291317 print_version ()
292318 click .echo ('Generating .ABOUT files...' )
293319
294- if mapping and mapping_file :
295- raise click .UsageError ('Invalid combination of options: --mapping and --mapping-file' )
296- if mapping :
297- mapping_file = DEFAULT_MAPPING
320+ mapping_file = validate_mapping (mapping , mapping_file )
298321
299322 if not location .endswith (('.csv' , '.json' ,)):
300323 raise click .UsageError ('ERROR: Invalid input file extension: must be one .csv or .json.' )
@@ -335,21 +358,41 @@ def validate_variables(ctx, param, value):
335358 return kvals
336359
337360
338- @cli .command (cls = AboutCommand ,
361+ def validate_template (ctx , param , value ):
362+ if not value :
363+ return DEFAULT_TEMPLATE_FILE
364+
365+ with io .open (value , encoding = 'utf-8' ) as templatef :
366+ template_error = check_template (templatef .read ())
367+
368+ if template_error :
369+ lineno , message = template_error
370+ raise click .UsageError (
371+ 'Template syntax error at line: '
372+ '{lineno}: "{message}"' .format (** locals ()))
373+ return value
374+
375+
376+ @about .command (cls = AboutCommand ,
339377 short_help = 'Generate an attribution document from .ABOUT files.' )
340378
341379@click .argument ('location' ,
342380 required = True ,
343- type = click .Path (exists = True , readable = True , resolve_path = True ))
381+ metavar = 'LOCATION' ,
382+ type = click .Path (
383+ exists = True , file_okay = True , dir_okay = True , readable = True , resolve_path = True ))
344384
345385@click .argument ('output' ,
346386 required = True ,
347- type = click .Path (exists = False , writable = True , dir_okay = False , resolve_path = True ))
387+ metavar = 'OUTPUT' ,
388+ type = click .Path (exists = False , dir_okay = False , writable = True , resolve_path = True ))
348389
349390@click .option ('--template' ,
350- metavar = 'TEMPLATE_FILE_PATH' ,
391+ metavar = 'FILE' ,
392+ callback = validate_template ,
351393 type = click .Path (exists = True , dir_okay = False , readable = True , resolve_path = True ),
352- help = 'Path to an optional custom attribution template to generate the attribution document.' )
394+ help = 'Path to an optional custom attribution template to generate the '
395+ 'attribution document. If not provided the default built-in template is used.' )
353396
354397@click .option ('--variable' ,
355398 multiple = True ,
@@ -358,19 +401,23 @@ def validate_variables(ctx, param, value):
358401 help = 'Add variable(s) as key=value for use in a custom attribution template.' )
359402
360403@click .option ('--inventory' ,
404+ metavar = 'FILE' ,
361405 type = click .Path (exists = True , dir_okay = False , resolve_path = True ),
362- help = 'Path to an optional JSON or CSV inventory file listing the '
406+ help = 'Path to an optional JSON or CSV inventory FILE listing the '
363407 'subset of .ABOUT files paths to consider when generating the attribution document.' )
364408
365409@click .option ('--mapping' ,
366410 is_flag = True ,
367- help = 'Use the default file mapping.config (./attributecode/mapping.config) '
368- 'with mapping between input keys and ABOUT field names.' )
411+ help = 'Use the default built-in "mapping.config" file '
412+ 'with mapping between input keys and .ABOUT field names.'
413+ 'Cannot be combined with the --mapping-file option.' )
369414
370415@click .option ('--mapping-file' ,
371416 metavar = 'FILE' ,
372- type = click .Path (exists = True , dir_okay = True , readable = True , resolve_path = True ),
373- help = 'Use a custom mapping file with mapping between input keys and ABOUT field names.' )
417+ type = click .Path (exists = True , dir_okay = False , readable = True , resolve_path = True ),
418+ help = 'Path to an optional custom mapping FILE '
419+ 'with mapping between input keys and .ABOUT field names. '
420+ 'Cannot be combined with the --mapping option.' )
374421
375422@click .option ('-q' , '--quiet' ,
376423 is_flag = True ,
@@ -396,21 +443,7 @@ def attrib(location, output, template, variable,
396443 print_version ()
397444 click .echo ('Generating attribution...' )
398445
399- if mapping and mapping_file :
400- raise click .UsageError ('Invalid combination of options: --mapping and --mapping-file' )
401- if mapping :
402- mapping_file = DEFAULT_MAPPING
403-
404- # Check template syntax early
405- if template :
406- with io .open (template , encoding = 'utf-8' ) as templatef :
407- template_error = check_template (templatef .read ())
408- if template_error :
409- lineno , message = template_error
410- raise click .UsageError (
411- 'Template validation error at line: {lineno}: "{message}"' .format (** locals ()))
412- else :
413- template = DEFAULT_TEMPLATE_FILE
446+ mapping_file = validate_mapping (mapping , mapping_file )
414447
415448 # accept zipped ABOUT files as input
416449 if location .lower ().endswith ('.zip' ):
@@ -440,13 +473,17 @@ def attrib(location, output, template, variable,
440473# check subcommand
441474######################################################################
442475
443- @cli .command (cls = AboutCommand ,
476+ # FIXME: This is really only a dupe of the Inventory command
477+
478+ @about .command (cls = AboutCommand ,
444479 short_help = 'Validate that the format of .ABOUT files is correct and report '
445480 'errors and warnings.' )
446481
447482@click .argument ('location' ,
448483 required = True ,
449- type = click .Path (exists = True , readable = True , resolve_path = True ))
484+ metavar = 'LOCATION' ,
485+ type = click .Path (
486+ exists = True , file_okay = True , dir_okay = True , readable = True , resolve_path = True ))
450487
451488@click .option ('--verbose' ,
452489 is_flag = True ,
@@ -563,4 +600,4 @@ def parse_key_values(key_values):
563600
564601
565602if __name__ == '__main__' :
566- cli ()
603+ about ()
0 commit comments