2020
2121from collections import defaultdict
2222import errno
23+ import io
2324import logging
2425import os
2526import sys
2627
2728import click
28- from attributecode .attrib import check_template
29- import codecs
3029# silence unicode literals warnings
3130click .disable_unicode_literals_warning = True
3231
3736from attributecode import __version__
3837from attributecode import DEFAULT_MAPPING
3938from attributecode import severities
39+ from attributecode .attrib import check_template
40+ from attributecode .attrib import DEFAULT_TEMPLATE_FILE
4041from attributecode .attrib import generate_and_save as generate_attribution_doc
4142from attributecode .gen import generate as generate_about_files
4243from attributecode .model import collect_inventory
@@ -221,8 +222,8 @@ def inventory(location, output, mapping, mapping_file,
221222
222223 errors_count = report_errors (errors , quiet , verbose , log_file_loc = output + '-error.log' )
223224 if not quiet :
224- msg = 'Inventory collected with {severe_error_count} errors or warnings detected.'
225- click .echo (msg . format ( ** locals ()) )
225+ msg = 'Inventory collected in {output}.' . format ( ** locals ())
226+ click .echo (msg )
226227 sys .exit (errors_count )
227228
228229
@@ -235,11 +236,11 @@ def inventory(location, output, mapping, mapping_file,
235236
236237@click .argument ('location' ,
237238 required = True ,
238- type = click .Path (exists = True , file_okay = True , readable = True , resolve_path = True ))
239+ type = click .Path (exists = True , file_okay = True , dir_okay = False , readable = True , resolve_path = True ))
239240
240241@click .argument ('output' ,
241242 required = True ,
242- type = click .Path (exists = False , writable = True , dir_okay = False , resolve_path = True ))
243+ type = click .Path (exists = True , writable = True , file_okay = False , dir_okay = True , resolve_path = True ))
243244
244245# FIXME: the CLI UX should be improved with two separate options for API key and URL
245246@click .option ('--fetch-license' ,
@@ -312,12 +313,12 @@ def gen(location, output,
312313 fetch_license = fetch_license ,
313314 mapping_file = mapping_file
314315 )
315- abouts_count = len (abouts )
316316
317317 errors_count = report_errors (errors , quiet , verbose , log_file_loc = output + '-error.log' )
318318 if not quiet :
319- msg = '{abouts_count} .ABOUT files generated with {errors_count} errors/warnings.'
320- click .echo (msg .format (** locals ()))
319+ abouts_count = len (abouts )
320+ msg = '{abouts_count} .ABOUT files generated in {output}.' .format (** locals ())
321+ click .echo (msg )
321322 sys .exit (errors_count )
322323
323324
@@ -350,7 +351,7 @@ def validate_variables(ctx, param, value):
350351
351352@click .argument ('output' ,
352353 required = True ,
353- type = click .Path (exists = False , writable = True , resolve_path = True ))
354+ type = click .Path (exists = False , writable = True , dir_okay = False , resolve_path = True ))
354355
355356@click .option ('--template' ,
356357 metavar = 'TEMPLATE_FILE_PATH' ,
@@ -364,7 +365,7 @@ def validate_variables(ctx, param, value):
364365 help = 'Add variable(s) as key=value for use in a custom attribution template.' )
365366
366367@click .option ('--inventory' ,
367- type = click .Path (exists = True , file_okay = True , resolve_path = True ),
368+ type = click .Path (exists = True , dir_okay = False , resolve_path = True ),
368369 help = 'Path to an optional JSON or CSV inventory file listing the '
369370 'subset of .ABOUT files paths to consider when generating the attribution document.' )
370371
@@ -407,13 +408,16 @@ def attrib(location, output, template, variable,
407408 if mapping :
408409 mapping_file = DEFAULT_MAPPING
409410
410- # Check for template early
411- with codecs .open (template , 'rb' , encoding = 'utf-8' ) as templatef :
412- template_error = check_template (templatef .read ())
413- if template_error :
414- lineno , message = template_error
415- raise click .UsageError (
416- 'Template validation error at line: {lineno}: "{message}"' .format (** locals ()))
411+ # Check template syntax early
412+ if template :
413+ with io .open (template , encoding = 'utf-8' ) as templatef :
414+ template_error = check_template (templatef .read ())
415+ if template_error :
416+ lineno , message = template_error
417+ raise click .UsageError (
418+ 'Template validation error at line: {lineno}: "{message}"' .format (** locals ()))
419+ else :
420+ template = DEFAULT_TEMPLATE_FILE
417421
418422 # accept zipped ABOUT files as input
419423 if location .lower ().endswith ('.zip' ):
@@ -434,8 +438,8 @@ def attrib(location, output, template, variable,
434438 errors_count = report_errors (errors , quiet , verbose , log_file_loc = output + '-error.log' )
435439
436440 if not quiet :
437- msg = 'Attribution generated with {errors_count} errors/warnings.'
438- click .echo (msg . format ( ** locals ()) )
441+ msg = 'Attribution generated in: {output}' . format ( ** locals ())
442+ click .echo (msg )
439443 sys .exit (errors_count )
440444
441445
@@ -444,7 +448,8 @@ def attrib(location, output, template, variable,
444448######################################################################
445449
446450@cli .command (cls = AboutCommand ,
447- short_help = 'Validate that the format of .ABOUT files is correct.' )
451+ short_help = 'Validate that the format of .ABOUT files is correct and report '
452+ 'errors and warnings.' )
448453
449454@click .argument ('location' ,
450455 required = True ,
@@ -465,17 +470,7 @@ def check(location, verbose):
465470 print_version ()
466471 click .echo ('Checking ABOUT files...' )
467472 errors , _abouts = collect_inventory (location )
468- if verbose :
469- reportable = errors
470- else :
471- reportable = filter_errors (errors , minimum_severity = WARNING )
472-
473- severe_errors_count = report_errors (reportable , quiet = False , verbose = True )
474-
475- if severe_errors_count :
476- click .echo ('Found {severe_error_count} errors or warnings.' .format (severe_errors_count ))
477- else :
478- click .echo ('No error found.' )
473+ severe_errors_count = report_errors (errors , quiet = False , verbose = verbose )
479474 sys .exit (severe_errors_count )
480475
481476
@@ -485,56 +480,49 @@ def check(location, verbose):
485480
486481def report_errors (errors , quiet , verbose , log_file_loc = None ):
487482 """
488- Return a number of severe errors and display the `errors` list of Error
489- objects based on the `quiet` and `verbose` flags.
483+ Report the `errors` list of Error objects to screen based on the `quiet` and
484+ `verbose` flags.
490485
491- If `log_file_loc` directory is provided also write the log to a
492- file as this location if severe errors are detected.
486+ If `log_file_loc` file location is provided also write a verbose log to this
487+ file.
488+ Return True if there were severe error reported.
493489 """
494490 errors = unique (errors )
491+ messages , severe_errors_count = get_error_messages (errors , quiet , verbose )
492+ for msg in messages :
493+ click .echo (msg )
494+ if log_file_loc :
495+ log_msgs , _ = get_error_messages (errors , quiet = False , verbose = True )
496+ with io .open (log_file_loc , 'w' , encoding = 'utf-8' ) as lf :
497+ lf .write ('\n ' .join (log_msgs ))
498+ return severe_errors_count
495499
496- logger = logging .getLogger (__name__ )
497- handler = logging .StreamHandler ()
498- handler .setLevel (logging .CRITICAL )
499- handler .setFormatter (logging .Formatter ('%(levelname)s: %(message)s' ))
500- logger .addHandler (handler )
501-
502- file_logger = logging .getLogger (__name__ + '_file' )
503500
501+ def get_error_messages (errors , quiet = False , verbose = False ):
502+ """
503+ Return a tuple of (list of error message strings to report,
504+ severe_errors_count) given an `errors` list of Error objects and using the
505+ `quiet` and `verbose` flags.
506+ """
507+ errors = unique (errors )
504508 severe_errors = filter_errors (errors , WARNING )
505509 severe_errors_count = len (severe_errors )
506510
507- log_file_obj = None
508- try :
509- # Create error.log if problematic_error detected
510- if severe_errors and log_file_loc :
511- # FIXME: this
512- log_file = open (log_file_loc , 'w' )
513- error_msg = '{} errors or warnings detected.' .format (severe_errors_count )
514- log_file .write (error_msg )
515- file_handler = logging .FileHandler (log_file )
516- file_logger .addHandler (file_handler )
517-
518-
519- for severity , message in errors :
520- sevcode = severities .get (severity ) or 'UNKNOWN'
521- msg = '{sevcode}: {message}' .format (** locals ())
522- if not quiet :
523- if verbose :
524- click .echo (msg )
525- elif severity >= WARNING :
526- click .echo (msg )
527- if log_file_loc :
528- # The logger will only log error for severity >= 30
529- file_logger .log (severity , msg )
530-
531- finally :
532- if log_file_obj :
533- try :
534- log_file_obj .close ()
535- except :
536- pass
537- return severe_errors_count
511+ messages = []
512+
513+ if severe_errors and not quiet :
514+ error_msg = 'Command completed with {} errors or warnings.' .format (severe_errors_count )
515+ messages .append (error_msg )
516+
517+ for severity , message in errors :
518+ sevcode = severities .get (severity ) or 'UNKNOWN'
519+ msg = '{sevcode}: {message}' .format (** locals ())
520+ if not quiet :
521+ if verbose :
522+ messages .append (msg )
523+ elif severity >= WARNING :
524+ messages .append (msg )
525+ return messages , severe_errors_count
538526
539527
540528def filter_errors (errors , minimum_severity = WARNING ):
0 commit comments