From 33c3d7a189157c341d7b494d7e8d80e4e5f06b7d Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Wed, 28 Sep 2016 10:01:02 -0700 Subject: [PATCH] Trying to restructure this to be more sensible. --- docs/arguments.rst | 25 +++++++ docs/conf.py | 2 +- docs/git-lint.tex | 143 ------------------------------------- docs/manual.rst | 56 +++++++++++++++ docs/usage.rst | 32 +-------- git_lint/__init__.py | 4 +- git_lint/git_lint.py | 130 ++++----------------------------- git_lint/option_handler.py | 77 ++++++++++++++++++++ git_lint/options.py | 35 +++++++++ 9 files changed, 214 insertions(+), 290 deletions(-) create mode 100644 docs/arguments.rst delete mode 100755 docs/git-lint.tex create mode 100644 docs/manual.rst create mode 100644 git_lint/option_handler.py create mode 100644 git_lint/options.py diff --git a/docs/arguments.rst b/docs/arguments.rst new file mode 100644 index 0000000..1a32dca --- /dev/null +++ b/docs/arguments.rst @@ -0,0 +1,25 @@ +**-o , --only=** + A comma-separated list of only those linters to run. +**-x --exclude=** + A comma-separated list of linters to skip. +**-l, --linters** + Show the list of configured linters. +**-b, --base** + Check all changed files from GIT_DIR, not just those in the current directory and down. +**-a, --all** + Scan all files, not just those that have changed. +**-e, --every** + Scan all files, not just those that have changed, from GIT_DIR. Short for -b -a +**-w, --workspace** + Scan the workspace [default] +**-s, --staging** + Scan the staging area (useful for pre-commit). +**-c** , --config**= Path to config file +**-d, --dryrun** + Report what git-lint would do, but don't actually do anything. +**-q, --quiet** + Produce a short report of files that failed to pass. +**-h, --help** + Print a short help message +**-v, --version** + Print version information diff --git a/docs/conf.py b/docs/conf.py index f032372..7a825bb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -239,7 +239,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'git_lint', + ('manual', 'git-lint', u'Git Lint Documentation', [u'Kenneth M. "Elf" Sternberg'], 1) ] diff --git a/docs/git-lint.tex b/docs/git-lint.tex deleted file mode 100755 index 9968cd2..0000000 --- a/docs/git-lint.tex +++ /dev/null @@ -1,143 +0,0 @@ -\documentclass[english]{article} -\usepackage[latin1]{inputenc} -\usepackage{babel} -\usepackage{verbatim} - -%% do we have the `hyperref package? -\IfFileExists{hyperref.sty}{ - \usepackage[bookmarksopen,bookmarksnumbered]{hyperref} -}{} - -%% do we have the `fancyhdr' package? -\IfFileExists{fancyhdr.sty}{ -\usepackage[fancyhdr]{latex2man} -}{ -%% do we have the `fancyheadings' package? -\IfFileExists{fancyheadings.sty}{ -\usepackage[fancy]{latex2man} -}{ -\usepackage[nofancy]{latex2man} -\message{no fancyhdr or fancyheadings package present, discard it} -}} - -\setDate{2016/09/26} %%%% must be manually set, if rcsinfo is not present -\setVersionWord{Version:} %%% that's the default, no need to set it. -\setVersion{0.4} - -\begin{document} - -\begin{Name}{1}{git-lint}{Elf M. Sternberg}{Utilities}{Git Lint - A smart lint wrapper around your git repository} - \Prog{Git Lint} - A smart wrapper around your git repository -\end{Name} - -\section{Synopsis} -%%%%%%%%%%%%%%%%%% - -\Prog{git lint} [..] [filenames..] - -\section{Description} -%%%%%%%%%%%%%%%%%%%%% -\Prog{git-lint} provides a simple, single call to perform syntatic and stylistic -checks of your repository, in order to ensure they comply with your standards -before you commit your work. - -\section{Options} -%%%%%%%%%%%%%%%%% -\begin{Description} - -\item[\OptArg{-o}{ linters}]Specify the list of linters to run, excluding all others -\item[\OptArg{-x}{ linters}]Specify the list of linters to exclude, running all others -\item[\Opt{-l}]List configured linters. The list will specify whether or not the - command line argument provided leads to an identifiable executable linter. -\item[\Opt{-b}]Run check from the repository base, rather than the current working directory. -\item[\Opt{-a}]Check all files, not just those that have changed. -\item[\Opt{-e}]Check everything (short for {-a -b}). -\item[\Opt{-w}]Check the workspace (default when run as \Prog{git-lint}). -\item[\Opt{-s}]Check the staging area. \Prog{git-lint} stashes the - current workspace, restoring it to the same state as the index. - After the check, \Prog{git-lint} restores the workspace and attempts - to reset all fill times correctly. - - -\item[\Opt{-g}]Only error if lint failures overlap diffed regions. -\item[\Opt{-p}]Error if a lint failure happens anywhere in a checked file. -\item[\Opt{-t}]Group output by linter first, then filenames [default]. -\item[\Opt{-f}]Group output by filenames first, then linter. -\item[\Opt{-d}]Dryrun - Report what \Prog{git-lint} would do, but don't actually run linters. -\item[\OptArg{-c}{ config-file}]Specify an alternative configuration file. -\item[\Opt{-h}]Print short help message -\item[\Opt{-v}]Print version information - -\end{Description} - -\section{Requirements} -%%%%%%%%%%%%%%%%%%%%%% - -\begin{description}\setlength{\itemsep}{0cm} -\item[An IBM or Lenovo Thinkpad with HDAPS] \Prog{thinksaber} only - runs on laptops with accelerometers, which get their values through - the HDAPS joystick emulator. - -\item[PyGame] \Prog{thinksaber} uses the PyGame library - (www.pygame.org), which in turn has dependencies on the Simple - Direct Layer gaming library as well as Python. Most Linux - distributions either come with this stock or provide it through the - installation tool. Pygame is a dependency of a number of popular - Linux games, so if you have any games on your system it's entirely - likely this has already been done for you. - -\item[Make] If you want to install the system with the distributed - \File{Makefile}, you need GNU-\Prog{make}. If you don't have it, you - should execute the steps shown in the \File{Makefile} manually. - -\end{description} - -\section{Acknowledgements} -%%%%%%%%%%%%%%%%%%%%%% - -\Prog{Thinksaber} is obviously inspired by the program MacSaber, and I'm -grateful to the MacSaber people for assembling the Star Wars sound -effects collection needed to make it so successful. - -\Prog{Thinksaber} uses a motion-detection algorithm derived from the -one written by Tatsuhiko Miyagawa (miyagawa at gmail.com) for his own -\Prog{thinkpad-saber} program, which ran only under Perl for Windows. -Obviously, I think mine's better. - -\section{Changes} -%@% IF LATEX %@% -{\small\verbatiminput{CHANGES}} -%@% ELSE %@% -Please check the file \URL{CHANGES} for the list of changes. -%@% END-IF %@% - -\section{Version} -%%%%%%%%%%%%%%%%% - -Version: \Version\ of \Date. - -\section{License and Copyright} -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -\begin{description} -\item[Copyright] \copyright\ 2008, Elf M. Sternberg, - \Email{Elf.Sternberg@gmail.com} - -\item[License] This program can be redistributed and/or modified under - the terms of the GNU Public License, version 2. You should have - found a copy of this licence with this distribution in the file - \File{COPYING}. - -\end{description} - -\section{Author} -%%%%%%%%%%%%%%%% - -\noindent -Elf M. Sternberg -Email: \Email{Elf.Sternberg@gmail.com} \\ -WWW: \URL{http://www.elfsternberg.com}. - -\LatexManEnd - -\end{document} diff --git a/docs/manual.rst b/docs/manual.rst new file mode 100644 index 0000000..4afaba7 --- /dev/null +++ b/docs/manual.rst @@ -0,0 +1,56 @@ +git-lint(1) +=========== + +NAME +---- +git-lint - Run configured linters against changed files + +SYNOPSIS +-------- + +[verse] +``git lint`` [...] [] + +DESCRIPTION +----------- + +Runs a list of configured linters against a specified list of files in +your repository. By default all linters will be run against the +changed files in your current workspace, from the current working +directory on down. Command line options let you choose a different +directory, a different of files, the complete set of files, and even +the files currently in the staging area. + +OPTIONS +------- + +.. include:: arguments.rst + +OUTPUT +------ + +By default, the output is that of all the linters specified, in the +order in which they appear in the configuration file, followed by +every file specified, sorted ASCIIbetically. This order can be +flipped (files first, then linters) with the ``--byfiles`` option. + +``git lint`` returns the maximal error code if any linters fail a +pass, or zero if they all succeed. + +CONFIGURATION +------------- + +``git lint`` uses a standard INI-style configuration file. Aside from the +DEFAULT section, the name of each section is an alphanumeric token name for +a linter, followed by configuration details for that linter. Standard details +are: + +* output - Text to print before running a linter. +* command - The actual command to run, minus the file path +* match - A comma-separated list of extensions to match against the linter +* print - If true, will prefix each line of output from the linter with the filename +* condition - if "error", the return code of the linter is the status of the pass. If "output," any output will result in a failure. +* comment - Text to include when running the ``--linters`` option + + + diff --git a/docs/usage.rst b/docs/usage.rst index fd5863d..ae5ce1b 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -11,36 +11,8 @@ git lint [options] [filenames] Options ------- -``-o`` ``--only`` - A comma-separated list of only those linters to run -``-x`` ``--exclude`` - A comma-separated list of linters to skip -``-l`` ``--linters`` - Show the list of configured linters -``-b`` ``--base`` - Check all changed files in the repository, not just those in the current directory. -``-a`` ``--all`` - Scan all files in the repository, not just those that have changed. -``-e`` ``--every`` - Short for -b -a: scan everything -``-w`` ``--workspace`` - Scan the workspace -``-s`` ``--staging`` - Scan the staging area (useful for pre-commit). -``-g`` ``--changes`` - Report lint failures only for diff'd sections -``-p`` ``--complete`` - Report lint failures for all files -``-c`` ``--config`` - Path to config file -``-d`` ``--dryrun`` - Report what git-lint would do, but don't actually do anything. -``-q`` ``--quiet`` - Produce a short report of files that failed to pass. -``-h`` ``--help`` - This help message -``-v`` ``--version`` - Version information + +.. include: arguments.rst As a pre-commit hook: --------------------- diff --git a/git_lint/__init__.py b/git_lint/__init__.py index c763072..edd1fcb 100644 --- a/git_lint/__init__.py +++ b/git_lint/__init__.py @@ -2,4 +2,6 @@ __author__ = 'Kenneth M. "Elf" Sternberg' __email__ = 'elf.sternberg@gmail.com' -__version__ = '0.0.2' +__version__ = '0.0.4' + +__all__ = ['git_lint'] diff --git a/git_lint/git_lint.py b/git_lint/git_lint.py index 3a609aa..303bc17 100644 --- a/git_lint/git_lint.py +++ b/git_lint/git_lint.py @@ -19,40 +19,7 @@ _ = gettext.gettext VERSION = '0.0.4' NAME = 'git-lint' -OPTIONS_LIST = [ - ('o', 'only', True, - _('A comma-separated list of only those linters to run'), ['exclude']), - ('x', 'exclude', True, - _('A comma-separated list of linters to skip'), []), - ('l', 'linters', False, - _('Show the list of configured linters'), []), - ('b', 'base', False, - _('Check all changed files in the repository, not just those in the current directory.'), []), - ('a', 'all', False, - _('Scan all files in the repository, not just those that have changed.'), []), - ('e', 'every', False, - _('Short for -b -a: scan everything'), []), - ('w', 'workspace', False, - _('Scan the workspace'), ['staging']), - ('s', 'staging', False, - _('Scan the staging area (useful for pre-commit).'), []), - ('g', 'changes', False, - _("Report lint failures only for diff'd sections"), ['complete']), - ('p', 'complete', False, - _('Report lint failures for all files'), []), - ('t', 'bylinter', False, - _('Group the reports by linter first as they appear in the config file [default]'), []), - ('f', 'byfile', False, - _('Group the reports by file first'), []), - ('d', 'dryrun', False, - _('Dry run - report what would be done, but do not run linters'), []), - ('c', 'config', True, - _('Path to config file'), []), - ('h', 'help', False, - _('This help message'), []), - ('v', 'version', False, - _('Version information'), []) -] + # ___ _ _ _ # / __|___ _ __ _ __ __ _ _ _ __| | | | (_)_ _ ___ @@ -61,74 +28,6 @@ OPTIONS_LIST = [ # -# This was a lot shorter and smarter in Hy... -def make_rational_options(optlist, args): - - # 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 rationalizer - - # (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 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, filenames) = 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 are superseded by others. - (retoptions, excluded) = remove_conflicted_options( - optlist, reduce(rationalize_options, options, {})) - - return (retoptions, filenames, excluded) - # ___ __ _ ___ _ # / __|___ _ _ / _(_)__ _ | _ \___ __ _ __| |___ _ _ # | (__/ _ \ ' \| _| / _` | | / -_) _` / _` / -_) '_| @@ -587,6 +486,20 @@ def print_report(results, cmdline, unlintable_filenames, cant_lint_filenames, print("\n".join([" {}".format(f) for f in unfindable_filenames])) + + +def print_help(options_list, name): + print(_('Usage: {} [options] [filenames]').format(name)) + for item in options_list: + print(' -{:<1} --{:<12} {}'.format(item[0], item[1], item[3])) + return sys.exit() + + +def print_version(name, version): + print('{} {} Copyright (c) 2009, 2016 Kennth M. "Elf" Sternberg'.format(name, version)) + + + def run_gitlint(cmdline, config, extras): def build_config_subset(keys): @@ -630,16 +543,3 @@ def run_gitlint(cmdline, config, extras): if not len(results): return 0 return max([i[2] for i in results if len(i)]) - - -def print_help(options_list, name): - print(_('Usage: {} [options] [filenames]').format(name)) - for item in options_list: - print(' -{:<1} --{:<12} {}'.format(item[0], item[1], item[3])) - return sys.exit() - - -def print_version(name, version): - print('{} {} Copyright (c) 2009, 2016 Kennth M. "Elf" Sternberg'.format(name, version)) - - diff --git a/git_lint/option_handler.py b/git_lint/option_handler.py new file mode 100644 index 0000000..85eea1d --- /dev/null +++ b/git_lint/option_handler.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# +# Copyright (C) 2015 Elf M. Sternberg +# Author: Elf M. Sternberg +# + +# This was a lot shorter and smarter in Hy... + + +def make_rational_options(options, commandline): + """Takes a table of options and the commandline, and returns a + dictionary of those options that appear on the commandline + along with any extra arguments. + + :param List(Tuple (string, string, boolean, string, List(string))) options, + The table of options: One-letter option, long option, takes arguments, + Help text, list of (long) options superseded by this one. + : param List(strings) commandline + The arguments as received by the start-up process + """ + + def make_options_rationalizer(options): + + """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. + """ + + fullset = {} + for option in options: + if not option[1]: + continue + if option[0]: + fullset['-' + option[0]] = option[1] + fullset['--' + option[1]] = option[1] + + def rationalizer(acc, it): + acc[fullset[it[0]]] = it[1] + return acc + + return rationalizer + + def remove_conflicted_options(options, 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 options 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 longoptstogo(i): return i[1] + ((i[2] and '=') or '') + + optstringsshort = ''.join([shortoptstogo(opt) for opt in options]) + optstringslong = [longoptstogo(opt) for opt in options] + (options, filenames) = getopt.getopt(commandline[1:], + optstringsshort, + optstringslong) + + # Turns what getopt returns into something more human-readable + rationalize_options = make_options_rationalizer(options) + + # Remove any options that are superseded by others. + (retoptions, excluded) = remove_conflicted_options( + optlist, reduce(rationalize_options, options, {})) + + return (retoptions, filenames, excluded) diff --git a/git_lint/options.py b/git_lint/options.py new file mode 100644 index 0000000..7f14f14 --- /dev/null +++ b/git_lint/options.py @@ -0,0 +1,35 @@ + +OPTIONS_LIST = [ + ('o', 'only', True, + _('A comma-separated list of only those linters to run'), ['exclude']), + ('x', 'exclude', True, + _('A comma-separated list of linters to skip'), []), + ('l', 'linters', False, + _('Show the list of configured linters'), []), + ('b', 'base', False, + _('Check all changed files in the repository, not just those in the current directory.'), []), + ('a', 'all', False, + _('Scan all files in the repository, not just those that have changed.'), []), + ('e', 'every', False, + _('Short for -b -a: scan everything'), []), + ('w', 'workspace', False, + _('Scan the workspace'), ['staging']), + ('s', 'staging', False, + _('Scan the staging area (useful for pre-commit).'), []), + ('g', 'changes', False, + _("Report lint failures only for diff'd sections"), ['complete']), + ('p', 'complete', False, + _('Report lint failures for all files'), []), + ('t', 'bylinter', False, + _('Group the reports by linter first as they appear in the config file [default]'), []), + ('f', 'byfile', False, + _('Group the reports by file first'), []), + ('d', 'dryrun', False, + _('Dry run - report what would be done, but do not run linters'), []), + ('c', 'config', True, + _('Path to config file'), []), + ('h', 'help', False, + _('This help message'), []), + ('v', 'version', False, + _('Version information'), []) +]