diff --git a/README.rst b/README.rst index 2c4b4f4..b0abc4e 100644 --- a/README.rst +++ b/README.rst @@ -12,26 +12,30 @@ checkers against changed files in your current working directory or staging area. It can be configured to work with any `lint` command. Some commands may require shell wrappers. +While it may be possible to create a custom lint command in your npm, +grunt, Make, CMake, or whatever, the fact is we all use a VCS, and +most of us use git. Having a centralized repository for what we want +checked and how we want it checked, associated with git (and by +extension, as a pre-commit hook), that can be run at any time for any +reason. + Features -------- * Highly configurable - configuration files, both for git-lint and individual linters, can be global or per-project. -* Only checks what has been changed, but this can be overriden from the - command line. - -* For some linters, can lint both full-files and or only the parts that - have changed. +* Only checks files that have been changed, but this can be overriden + from the command line. * Can be used directly as a pre-commit hook, to ensure you personally don't check in anything broken. - * Used this way, it checks the *staging* area, not your workspace. + * Used this way, it checks the *staging* area, not your workspace. - * When using the staging area, it stashes your current work. Upon - restoration of your workspace, it ensures the timestamps are the - same, so as not to confuse your build system or IDE. + * When using the staging area, it stashes your current work. Upon + restoration of your workspace, it ensures the timestamps are the + same, so as not to confuse your build system or IDE. Credits ------- diff --git a/bin/git-lint-style.py b/bin/git-lint-style.py new file mode 100644 index 0000000..21e6c14 --- /dev/null +++ b/bin/git-lint-style.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +from __future__ import print_function +from functools import reduce +import subprocess +import os.path +import getopt +import sys +import re +import os + +import gettext +_ = gettext.gettext + + +def fullshell(cmd): + process = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True) + (stdout, stderr) = process.communicate() + return (stdout, stderr, process.returncode) + + +def path_which(scriptname): + def is_executable(path): + return os.path.exists(path) and os.access(path, os.X_OK) + + possibles = [path for path in + [os.path.join(path, scriptname) + for path in os.environ.get('PATH').split(':')] + if is_executable(path)] + + return (len(possibles) and possibles.pop(0)) or False + + +def main(*args): + (opts, filenames) = getopt.getopt(args[1:], '', ['min=', 'max=']) + options = {re.sub('^\-+', '', i[0]): i[1] for i in opts} + style = path_which('style') + if not style: + sys.exit(_('Could not find the style program')) + if len(filenames) == 0: + sys.exit(_('No files specifed to scan')) + (values, error, returncode) = fullshell([style, filenames[0]]) + if returncode != 0: + print(error) + return returncode + kincaid = re.search(r'Kincaid:\s*([\d\.]+)', values, re.MULTILINE) + val = float(kincaid.group(1)) + + msg = "" + if options.get('min', None): + if val < int(options.get('min')): + msg = _('Readability too low') + if options.get('max', None): + if val > int(options.get('max')): + msg = _('Readability too high') + print(_("{}: {} {}").format(filenames[0], val, msg)) + return (msg != "" and 1) or 0 + + +if __name__ == '__main__': + import sys + sys.exit(main(*sys.argv)) + + + + diff --git a/bin/pre-commit b/bin/pre-commit index 16b345e..eab5e11 100755 --- a/bin/pre-commit +++ b/bin/pre-commit @@ -1,17 +1,38 @@ #!/usr/bin/env python from git_lint import load_config, run_linters, git_base +import gettext +_ = gettext.gettext + + def main(*args): if git_base is None: sys.exit(_('A git repository was not found.')) - pre_commit_config = { + pre_commit_options = { 'staging': True, 'base': True } - config = load_config(pre_commit_config, git_base) - return run_linters(pre_commit_config, config, []) + config = load_config(pre_commit_options, git_base) + + (results, + unlintable_filenames, + cant_lint_filenames, + broken_linter_names, + unfindable_filenames) = run_linters(options, config) + + print_report(results, + unlintable_filenames, + cant_lint_filenames, + broken_linter_names, + unfindable_filenames, + options) + + if not len(results): + return 0 + + return max([i[2] for i in results if len(i)]) if __name__ == '__main__': diff --git a/git_lint/git_lint.py b/git_lint/git_lint.py index 634710a..831ae1d 100644 --- a/git_lint/git_lint.py +++ b/git_lint/git_lint.py @@ -188,6 +188,7 @@ def executable_exists(script, label): if scriptname.startswith('/'): return (is_executable(scriptname) and scriptname) or None + # shutil.which() doesn't appear until Python 3, darnit. possibles = [path for path in [os.path.join(path, scriptname) for path in os.environ.get('PATH').split(':')] @@ -253,6 +254,9 @@ def get_filelist(options, extras): stream = [entry for entry in get_git_response(cmd).split(u'\x00') if len(entry) > 0] + # Yeah, baby, recursion, the way this is meant to be handled. + # If you have more than 999 files that need linting, you have + # a bigger problem... def parse_stream(acc, stream): """Parse the list of files. T @@ -420,7 +424,7 @@ class Linters: return reduce(operator.add, [dryrunonce(linter, self.filenames) for linter in self.linters], []) -def run_linters(options, config, extras): +def run_linters(options, config, extras=[]): def build_config_subset(keys): """ Returns a subset of the configuration, with only those linters mentioned in keys """