diff --git a/.git-lint b/.git-lint index 3d310b5..9892047 100644 --- a/.git-lint +++ b/.git-lint @@ -2,7 +2,7 @@ comment = Check to Ensure no debugger commands get checked in output = Checking for debugger commands in Javascript... command = grep -n debugger -files = .js +match = .js print = True condition = output diff --git a/git_lint/git_lint.py b/git_lint/git_lint.py index ecc965a..18ee109 100644 --- a/git_lint/git_lint.py +++ b/git_lint/git_lint.py @@ -1,4 +1,5 @@ -import ConfigParser +from __future__ import print_function +from functools import reduce import getopt import gettext import operator @@ -15,6 +16,9 @@ _ = gettext.gettext VERSION = '0.0.4' NAME = 'git-lint' +def tap(a): + print("TAP:", a) + return a # ___ _ _ _ # / __|___ _ __ _ __ __ _ _ _ __| | | | (_)_ _ ___ @@ -132,7 +136,8 @@ def get_git_response_raw(cmd): fullcmd = (['git'] + cmd) process = subprocess.Popen(fullcmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + stderr=subprocess.PIPE, + universal_newlines=True) (out, err) = process.communicate() return (out, err, process.returncode) @@ -151,14 +156,16 @@ def run_git_command(cmd): fullcmd = (['git'] + cmd) return subprocess.call(fullcmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + stderr=subprocess.PIPE, + universal_newlines=True) def get_shell_response(fullcmd): process = subprocess.Popen(fullcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - shell=True) + shell=True, + universal_newlines=True) (out, err) = process.communicate() return (out, err, process.returncode) @@ -191,15 +198,15 @@ def base_file_cleaner(files): def make_match_filter_matcher(extensions): - trimmed = reduce(operator.add, [s.strip for s in - [ex.split(',') for ex in extension-s]]) - cleaned = [re.sub(r'^\.', s.strip(), '') for s in trimmed] + trimmed = [s.strip() for s in reduce(operator.add, + [ex.split(',') for ex in extensions], [])] + cleaned = [re.sub(r'^\.', '', s) for s in trimmed] return re.compile(r'\.' + '|'.join(cleaned) + r'$') def make_match_filter(config): matcher = make_match_filter_matcher([v.get('match', '') - for v in config.itervalues()]) + for v in config.values()]) def match_filter(path): return matcher.search(path) @@ -279,7 +286,7 @@ def get_filelist(cmdline, extras): def check_for_conflicts(filesets): """ Scan list of porcelain files for merge conflic state. """ MERGE_CONFLICT_PAIRS = set(['DD', 'DU', 'AU', 'AA', 'UD', 'UA', 'UU']) - status_pairs = set(['' + f[0] + f[1] for f in files]) + status_pairs = set(['' + f[0] + f[1] for f in filesets]) if len(status_pairs & MERGE_CONFLICT_PAIRS): sys.exit( _('Current repository contains merge conflicts. Linters will not be run.')) @@ -363,7 +370,7 @@ def get_filelist(cmdline, extras): if 'staging' in keys: file_list_generator = staging_list - return working_directory_trans(remove_submodules(file_list_generator)) + return working_directory_trans(remove_submodules(file_list_generator())) # ___ _ _ @@ -372,7 +379,7 @@ def get_filelist(cmdline, extras): # |___/\__\__,_\__, |_|_||_\__, | \_/\_/|_| \__,_| .__/ .__/\___|_| # |___/ |___/ |_| |_| -def pick_runner(cmdline): +def pick_stash_runner(cmdline): """Choose a runner. This is the operation that will run the linters. It exists to @@ -410,7 +417,7 @@ def pick_runner(cmdline): # |_|_\\_,_|_||_| |_|_|_||_\__| | .__/\__,_/__/__/ # |_| -def run_external_linter(filename, linter): +def run_external_linter(filename, linter, linter_name): """Run one linter against one file. @@ -420,17 +427,20 @@ def run_external_linter(filename, linter): """ def encode_shell_messages(prefix, messages): - return ['{}{}'.format(prefix, line.decode('utf-8')) + return ['{}{}'.format(prefix, line) for line in messages.splitlines()] - cmd = linter['command'] + '"' + filename + '"' + cmd = linter['command'] + ' "' + filename + '"' (out, err, returncode) = get_shell_response(cmd) - if ((out and (linter.get('condition', 'error') == 'output')) or err or (not (returncode == 0L))): - prefix = linter.get('print', False) and '\t{}:'.format(filename) or '\t' - output = encode_shell_messages(prefix, out) + (err and encode_shell_messages(prefix, err) or []) - return ((returncode or 1), output) - return (0, []) + failed = ((out and (linter.get('condition', 'error') == 'output')) or err or (not (returncode == 0))) + if not failed: + return (filename, linter_name, 0, []) + + prefix = linter.get('print', False) and '\t{}:'.format(filename) or '\t' + output = encode_shell_messages(prefix, out) + (err and encode_shell_messages(prefix, err) or []) + return (filename, linter_name, (returncode or 1), output) + def run_one_linter(linter, filenames): @@ -442,11 +452,10 @@ def run_one_linter(linter, filenames): result as a list of successes and failures. Failures have a return code and the output of the lint process. """ - + linter_name, config = list(linter.items()).pop() match_filter = make_match_filter(linter) - config = linter.values()[0] files = set([file for file in filenames if match_filter(file)]) - return [run_external_linter(file, config) for file in files] + return [run_external_linter(file, config, linter_name) for file in files] def build_lint_runner(linters, filenames): @@ -458,7 +467,8 @@ def build_lint_runner(linters, filenames): """ def lint_runner(): keys = sorted(linters.keys()) - return [run_one_linter({key: linters[key]}, filenames) for key in keys] + return reduce(operator.add, + [run_one_linter({key: linters[key]}, filenames) for key in keys], []) return lint_runner @@ -477,21 +487,21 @@ def run_gitlint(cmdline, config, extras): """ Runs the requested linters """ all_files = get_filelist(cmdline, extras) - runner = pick_runner(cmdline) - - lintable = make_match_filter(config) + stash_runner = pick_runner(cmdline) + is_lintable = make_match_filter(config) lintable_files = set([file for file in all_files if lintable(file)]) - unlintables = (set(all_files) - lintable_files) + unlintable_files = (set(all_files) - lintable_files) + working_linters = get_working_linters(config) broken_linters = (set(config) - set(working_linters)) - cant_lint = make_match_filter(subset_config(config, broken_linters)) - cant_lintable = set([file for file in lintable_files if cant_lint(file)]) + cant_lint_filter = make_match_filter(build_config_subset(broken_linters)) + cant_lint_files = set([file for file in lintable_files if cant_lint_filter(file)]) lint_runner = build_lint_runner(build_config_subset(working_linters), lintable_files) - results = runner(lint_runner) + results = stash_runner(lint_runner) print(list(results)) - return results + return max(*[i[2] for i in results if len(i)]) def print_help(): diff --git a/git_lint/git_lint_config.py b/git_lint/git_lint_config.py index 15bfa30..23b62f0 100644 --- a/git_lint/git_lint_config.py +++ b/git_lint/git_lint_config.py @@ -1,7 +1,11 @@ import sys import os.path import gettext -import ConfigParser +try: + import configparser +except ImportError as e: + import ConfigParser as configparser + _ = gettext.gettext @@ -56,7 +60,7 @@ def get_config(options, base): """ path = find_config_file(options, base) - configloader = ConfigParser.SafeConfigParser() + configloader = configparser.SafeConfigParser() configloader.read(path) configloader.set('DEFAULT', 'repodir', base) return {section: {k: v for (k, v) in configloader.items(section)} diff --git a/git_lint/git_lint_options.py b/git_lint/git_lint_options.py index ba94fab..f9ffe3e 100644 --- a/git_lint/git_lint_options.py +++ b/git_lint/git_lint_options.py @@ -1,119 +1,75 @@ -from hy.core.language import filter, map, name, reduce import os import sys -import inspect import getopt import gettext _ = gettext.gettext -# -> scriptNameString +def make_rational_options(optlist, args): -def get_script_name(): - """ Returns the best possible name for the script. """ - if getattr(sys, 'frozen', False): - (path, name) = os.path.split(sys.executable) - return name - - prefix = sys.exec_prefix.upper() - names = [name for name in [frame[1] for frame in inspect.stack()] - if name.startswith('<') or names.upper().startswith(prefix)] - return names.pop() - - -# OptionTupleList -> (getOptOptions -> dictionaryOfOptions) - -def make_options_rationalizer(optlist): - """Takes a list of option tuples, and returns a function that takes - the output of getopt and reduces it to the longopt key and - associated values as a dictionary. - """ - - def make_opt_assoc(prefix, pos): - def associater(acc, it): - acc[(prefix + it[pos])] = it[1] + # OptionTupleList -> (getOptOptions -> dictionaryOfOptions) + def make_options_rationalizer(optlist): + """Takes a list of option tuples, and returns a function that takes + the output of getopt and reduces it to the longopt key and + associated values as a dictionary. + """ + + def make_opt_assoc(prefix, pos): + def associater(acc, it): + acc[(prefix + it[pos])] = it[1] + return acc + return associater + + short_opt_assoc = make_opt_assoc('-', 0) + long_opt_assoc = make_opt_assoc('--', 1) + + def make_full_set(acc, i): + return long_opt_assoc(short_opt_assoc(acc, i), i) + + fullset = reduce(make_full_set, optlist, {}) + + def rationalizer(acc, it): + acc[fullset[it[0]]] = it[1] return acc - return associater + + return rationalizer - short_opt_assoc = make_opt_assoc('-', 0) - long_opt_assoc = make_opt_assoc('--', 1) + + # (OptionTupleList, dictionaryOfOptions) -> (dictionaryOfOptions, excludedOptions) + def remove_conflicted_options(optlist, request): + """Takes our list of option tuples, and a cleaned copy of what was + requested from getopt, and returns a copy of the request + without any options that are marked as superseded, along with + the list of superseded options + """ + def get_excluded_keys(memo, opt): + return memo + (len(opt) > 4 and opt[4] or []) + + keys = request.keys() + marked = [option for option in optlist if option[1] in keys] + exclude = reduce(get_excluded_keys, marked, []) + excluded = [key for key in keys if key in exclude] + cleaned = {key: request[key] for key in keys + if key not in excluded} + return (cleaned, excluded) + + def shortoptstogo(i): + return i[0] + (i[2] and ':' or '') - def make_full_set(acc, i): - return long_opt_assoc(short_opt_assoc(acc, i), i) + def longoptstogo(i): + return i[1] + (i[2] and '=' or '') - fullset = reduce(make_full_set, optlist, {}) + optstringsshort = ''.join([shortoptstogo(opt) for opt in optlist]) + optstringslong = [longoptstogo(opt) for opt in optlist] + (options, filenames) = getopt.getopt(args[1:], optstringsshort, + optstringslong) - def rationalizer(acc, it): - acc[fullset[it[0]]] = it[1] - return acc + # Turns what getopt returns into something more human-readable + rationalize_options = make_options_rationalizer(optlist) - return rationalizer + # Remove any options that + (retoptions, excluded) = remove_conflicted_options( + optlist, reduce(rationalize_options, options, {})) - -# (OptionTupleList, dictionaryOfOptions) -> (dictionaryOfOptions, excludedOptions) - -def remove_conflicted_options(optlist, request): - """Takes our list of option tuples, and a cleaned copy of what was - requested from getopt, and returns a copy of the request - without any options that are marked as superseded, along with - the list of superseded options - """ - def get_excluded_keys(memo, opt): - return memo + (len(opt) > 4 and opt[4] or []) - - keys = request.keys() - marked = [option for option in optlist if option[1] in keys] - exclude = reduce(get_excluded_keys, marked, []) - excluded = [key for key in keys if key in exclude] - cleaned = {key: request[key] for key in keys - if key not in excluded} - return (cleaned, excluded) - - -class RationalOptions: - - def __init__(self, optlist, args, name='', copyright='', version='0.0.1'): - def shortoptstogo(i): - return i[0] + (i[2] and ':' or '') - - def longoptstogo(i): - return i[1] + (i[2] and '=' or '') - - optstringsshort = ''.join([shortoptstogo(opt) for opt in optlist]) - optstringslong = [longoptstogo(opt) for opt in optlist] - (options, arg) = getopt.getopt(args[1:], optstringsshort, - optstringslong) - - # Turns what getopt returns into something more human-readable - rationalize_options = make_options_rationalizer(optlist) - - # Remove any options that - (newoptions, excluded) = remove_conflicted_options( - optlist, reduce(rationalize_options, options, {})) - - self.optlist = optlist - self.options = newoptions - self.excluded = excluded - self.filenames = arg - self.name = (name if name else get_script_name()) - self.version = version - self.copyright = copyright - - def get_options(self): - return self.options - - def get_keys(self): - return set(self.options.keys()) - - def print_help(self): - print(_('Usage: {} [options] [filenames]').format(self.name)) - for item in self.optlist: - print(' -{:<1} --{:<12} {}'.format(item[0], item[1], item[3])) - return sys.exit() - - def print_version(self): - print('{}'.format(self.name, self.version)) - if self.copyright: - print(self.copyright) - return sys.exit() + return (retoptions, filenames, excluded)