diff --git a/lib/ansiblereview/__init__.py b/lib/ansiblereview/__init__.py index cf24da1..e00ed4e 100644 --- a/lib/ansiblereview/__init__.py +++ b/lib/ansiblereview/__init__.py @@ -170,7 +170,13 @@ class Rolesfile(Unversioned): pass +class Folder(Unversioned): + pass + + def classify(filename): + if (os.path.isdir(filename)): + return Folder(filename) parentdir = os.path.basename(os.path.dirname(filename)) if parentdir in ['tasks']: return Task(filename) diff --git a/lib/ansiblereview/__main__.py b/lib/ansiblereview/__main__.py index c04f077..55da7f7 100755 --- a/lib/ansiblereview/__main__.py +++ b/lib/ansiblereview/__main__.py @@ -11,6 +11,9 @@ from appdirs import AppDirs from pkg_resources import resource_filename +from copy import copy +from optparse import Option, OptionValueError + def get_candidates_from_diff(difftext): try: @@ -31,12 +34,25 @@ def get_candidates_from_diff(difftext): return candidates +def check_boolean(option, opt, value): + try: + return value.lower() in ("yes", "true", "t", "1") + except ValueError: + raise OptionValueError( + "option %s: invalid boolean value: %r" % (opt, value)) + +class MyOption (Option): + TYPES = Option.TYPES + ("boolean",) + TYPE_CHECKER = copy(Option.TYPE_CHECKER) + TYPE_CHECKER["boolean"] = check_boolean + + def main(): config_dir = AppDirs("ansible-review", "com.github.willthames").user_config_dir default_config_file = os.path.join(config_dir, "config.ini") parser = optparse.OptionParser("%prog playbook_file|role_file|inventory_file", - version="%prog " + __version__) + version="%prog " + __version__, option_class=MyOption) parser.add_option('-c', dest='configfile', default=default_config_file, help="Location of configuration file: [%s]" % default_config_file) parser.add_option('-d', dest='rulesdir', @@ -50,6 +66,22 @@ def main(): parser.add_option('-v', dest='log_level', action="store_const", default=logging.WARN, const=logging.INFO, help="Show more verbose output") + parser.add_option('--output_info', dest='output_info', + help="Where to output INFO messages: stdout|stderr") + parser.add_option('--output_warn', dest='output_warn', + help="Where to output WARN messages: stdout|stderr") + parser.add_option('--output_error', dest='output_error', + help="Where to output ERROR messages: stdout|stderr") + parser.add_option('--output_fatal', dest='output_fatal', + help="Where to output FATAL messages: stdout|stderr") + + parser.add_option('--wrap_long_lines', dest='wrap_long_lines', type="boolean", + help="Wrap long lines of output, or output each message on a single line.") + + parser.add_option('--print_options', dest='print_options', type="boolean", default=False, + help="Print out effective config options, before starting.") + + options, args = parser.parse_args(sys.argv[1:]) settings = read_config(options.configfile) @@ -72,6 +104,9 @@ def main(): warn("Using example lint-rules found at %s" % lint_dir, options, file=sys.stderr) options.lintdir = lint_dir + if options.print_options: + print(str(options)) + if len(args) == 0: candidates = get_candidates_from_diff(sys.stdin) else: diff --git a/lib/ansiblereview/utils/__init__.py b/lib/ansiblereview/utils/__init__.py index 541744a..48e9076 100644 --- a/lib/ansiblereview/utils/__init__.py +++ b/lib/ansiblereview/utils/__init__.py @@ -21,21 +21,29 @@ import configparser -def abort(message, file=sys.stderr): +def abort(message, settings, file=None): + if file is None: + file = getattr(sys, settings.output_abort) print(stringc("FATAL: %s" % message, 'red'), file=file) sys.exit(1) -def error(message, file=sys.stderr): +def error(message, settings, file=None): + if file is None: + file = getattr(sys, settings.output_error) print(stringc("ERROR: %s" % message, 'red'), file=file) -def warn(message, settings, file=sys.stdout): +def warn(message, settings, file=None): + if file is None: + file = getattr(sys, settings.output_warn) if settings.log_level <= logging.WARNING: print(stringc("WARN: %s" % message, 'yellow'), file=file) -def info(message, settings, file=sys.stdout): +def info(message, settings, file=None): + if file is None: + file = getattr(sys, settings.output_info) if settings.log_level <= logging.INFO: print(stringc("INFO: %s" % message, 'green'), file=file) @@ -61,12 +69,12 @@ def is_line_in_ranges(line, ranges): def read_standards(settings): if not settings.rulesdir: - abort("Standards directory is not set on command line or in configuration file - aborting") + abort("Standards directory is not set on command line or in configuration file - aborting", settings) sys.path.append(os.path.abspath(os.path.expanduser(settings.rulesdir))) try: standards = importlib.import_module('standards') except ImportError as e: - abort("Could not import standards from directory %s: %s" % (settings.rulesdir, str(e))) + abort("Could not import standards from directory %s: %s" % (settings.rulesdir, str(e)), settings) return standards @@ -112,6 +120,11 @@ def review(candidate, settings, lines=None): (type(candidate).__name__, candidate.path, candidate.version), settings) + if settings.wrap_long_lines: + br = '\n' + else: + br = ' ' + for standard in standards.standards: if type(candidate).__name__.lower() not in standard.types: continue @@ -122,14 +135,14 @@ def review(candidate, settings, lines=None): if not err.lineno or is_line_in_ranges(err.lineno, lines_ranges(lines))]: if not standard.version: - warn("Best practice \"%s\" not met:\n%s:%s" % - (standard.name, candidate.path, err), settings) + warn("Best practice \"%s\" not met:%s%s:%s" % + (standard.name, br, candidate.path, err), settings) elif LooseVersion(standard.version) > LooseVersion(candidate.version): - warn("Future standard \"%s\" not met:\n%s:%s" % - (standard.name, candidate.path, err), settings) + warn("Future standard \"%s\" not met:%s%s:%s" % + (standard.name, br, candidate.path, err), settings) else: - error("Standard \"%s\" not met:\n%s:%s" % - (standard.name, candidate.path, err)) + error("Standard \"%s\" not met:%s%s:%s" % + (standard.name, br, candidate.path, err), settings) errors = errors + 1 if not result.errors: if not standard.version: @@ -142,23 +155,44 @@ def review(candidate, settings, lines=None): return errors +# TODO: Settings & read_config should probably just use: +# +# config.read(...) +# config._sections - which is a dict of the config file contents class Settings(object): def __init__(self, values): - self.rulesdir = values.get('rulesdir') - self.lintdir = values.get('lintdir') + self.rulesdir = values.get('rulesdir', None) + self.lintdir = values.get('lintdir', None) + self.configfile = values.get('configfile') + self.output_info = values.get('output_info', 'stdout') + self.output_warn = values.get('output_warn', 'stdout') + self.output_error = values.get('output_error', 'stderr') + self.output_fatal = values.get('output_fatal', 'stderr') + + self.wrap_long_lines = values.get('wrap_long_lines', True) == 'True' + def read_config(config_file): config = configparser.RawConfigParser({'standards': None, 'lint': None}) config.read(config_file) + tmp_settings = dict(configfile=config_file) + if config.has_section('rules'): - return Settings(dict(rulesdir=config.get('rules', 'standards'), - lintdir=config.get('rules', 'lint'), - configfile=config_file)) - else: - return Settings(dict(rulesdir=None, lintdir=None, configfile=config_file)) + tmp_settings['rulesdir'] = config.get('rules', 'standards') + tmp_settings['lintdir'] = config.get('rules', 'lint') + + if config.has_section('output'): + tmp_settings['output_info'] = config.get('output', 'info') + tmp_settings['output_warn'] = config.get('output', 'warn') + tmp_settings['output_error'] = config.get('output', 'error') + tmp_settings['output_fatal'] = config.get('output', 'fatal') + + tmp_settings['wrap_long_lines'] = config.get('output', 'wrap_long_lines') + + return Settings(tmp_settings) class ExecuteResult(object): diff --git a/lib/ansiblereview/version.py b/lib/ansiblereview/version.py index 1139861..df6b1d9 100644 --- a/lib/ansiblereview/version.py +++ b/lib/ansiblereview/version.py @@ -1 +1 @@ -__version__ = '0.13.2' +__version__ = '0.13.3'