Compare commits
32 Commits
inotify-ca
...
master
Author | SHA1 | Date |
---|---|---|
Elf M. Sternberg | e86fe1ff07 | |
Ken Sternberg | 41d61476e1 | |
Elf M. Sternberg | ef2a5c579b | |
Tino de Bruijn | b485697742 | |
Ken Sternberg | 1e26d46d17 | |
Ken Sternberg | e5a8f93630 | |
Ken Sternberg | 09dcb1a050 | |
Ken Sternberg | e11655e3b9 | |
Elf M. Sternberg | 5cc8770ac3 | |
Elf M. Sternberg | 8fec735719 | |
Elf M. Sternberg | a8451c83bf | |
Elf M. Sternberg | 1a942e6b1c | |
Elf M. Sternberg | 9b40abe4ae | |
Elf M. Sternberg | 6e09df1427 | |
Elf M. Sternberg | f70aea7ec3 | |
Elf M. Sternberg | 3461709ebf | |
Elf M. Sternberg | 74d217d6fe | |
Elf M. Sternberg | f12b746ade | |
Elf M. Sternberg | 4895dde1b6 | |
Elf M. Sternberg | d046268ad6 | |
Elf M. Sternberg | 1b5b215e3b | |
Elf M. Sternberg | 556c56fb43 | |
Elf M. Sternberg | a1b5ae9eb0 | |
Elf M. Sternberg | 41a70e2592 | |
Elf M. Sternberg | 4ce0dd4526 | |
Elf M. Sternberg | 8a6c808fdd | |
Elf M. Sternberg | 5208f706f4 | |
Elf M. Sternberg | f2ae3371e7 | |
Elf M. Sternberg | 13b0378b67 | |
Elf M. Sternberg | aa4b5aa978 | |
Elf M. Sternberg | dd4aeb1cad | |
Elf M. Sternberg | 08537128bb |
|
@ -63,3 +63,9 @@ match = .json
|
||||||
print = False
|
print = False
|
||||||
condition = error
|
condition = error
|
||||||
|
|
||||||
|
[readability]
|
||||||
|
output = Running Text Style Check
|
||||||
|
match = .rst
|
||||||
|
command = git-lint-style --min=6 --max=12
|
||||||
|
print = False
|
||||||
|
condition = error
|
||||||
|
|
|
@ -10,4 +10,4 @@ Development Lead
|
||||||
Contributors
|
Contributors
|
||||||
------------
|
------------
|
||||||
|
|
||||||
None yet. Why not be the first?
|
* Tino de Bruijn <work@tino.io>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
GitLint is a git command to automatically run a suite of pre-defined linters.
|
|
@ -8,4 +8,6 @@ recursive-include tests *
|
||||||
recursive-exclude * __pycache__
|
recursive-exclude * __pycache__
|
||||||
recursive-exclude * *.py[co]
|
recursive-exclude * *.py[co]
|
||||||
|
|
||||||
recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif
|
recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif *.1
|
||||||
|
include docs/_build/man/git-lint.1
|
||||||
|
include bin/pre-commit bin/git-lint-style
|
||||||
|
|
10
Makefile
10
Makefile
|
@ -12,6 +12,7 @@ export BROWSER_PYSCRIPT
|
||||||
BROWSER := python -c "$$BROWSER_PYSCRIPT"
|
BROWSER := python -c "$$BROWSER_PYSCRIPT"
|
||||||
|
|
||||||
help:
|
help:
|
||||||
|
@echo "all - local build including docs"
|
||||||
@echo "clean - remove all build, test, coverage and Python artifacts"
|
@echo "clean - remove all build, test, coverage and Python artifacts"
|
||||||
@echo "clean-build - remove build artifacts"
|
@echo "clean-build - remove build artifacts"
|
||||||
@echo "clean-pyc - remove Python file artifacts"
|
@echo "clean-pyc - remove Python file artifacts"
|
||||||
|
@ -21,10 +22,14 @@ help:
|
||||||
@echo "test-all - run tests on every Python version with tox"
|
@echo "test-all - run tests on every Python version with tox"
|
||||||
@echo "coverage - check code coverage quickly with the default Python"
|
@echo "coverage - check code coverage quickly with the default Python"
|
||||||
@echo "docs - generate Sphinx HTML documentation, including API docs"
|
@echo "docs - generate Sphinx HTML documentation, including API docs"
|
||||||
|
@echo "docsbrowse - generate Sphinx HTML documentation and start browser"
|
||||||
@echo "release - package and upload a release"
|
@echo "release - package and upload a release"
|
||||||
@echo "dist - package"
|
@echo "dist - package"
|
||||||
@echo "install - install the package to the active Python's site-packages"
|
@echo "install - install the package to the active Python's site-packages"
|
||||||
|
|
||||||
|
all: docs
|
||||||
|
python setup.py build
|
||||||
|
|
||||||
clean: clean-build clean-pyc clean-test
|
clean: clean-build clean-pyc clean-test
|
||||||
|
|
||||||
clean-build:
|
clean-build:
|
||||||
|
@ -67,6 +72,9 @@ docs:
|
||||||
sphinx-apidoc -o docs/ git_lint
|
sphinx-apidoc -o docs/ git_lint
|
||||||
$(MAKE) -C docs clean
|
$(MAKE) -C docs clean
|
||||||
$(MAKE) -C docs html
|
$(MAKE) -C docs html
|
||||||
|
$(MAKE) -C docs man
|
||||||
|
|
||||||
|
docbrowse: docs
|
||||||
$(BROWSER) docs/_build/html/index.html
|
$(BROWSER) docs/_build/html/index.html
|
||||||
|
|
||||||
servedocs: docs
|
servedocs: docs
|
||||||
|
@ -82,4 +90,4 @@ dist: clean
|
||||||
ls -l dist
|
ls -l dist
|
||||||
|
|
||||||
install: clean
|
install: clean
|
||||||
python setup.py install
|
python setup.py install --prefix=/usr/local
|
||||||
|
|
79
README.rst
79
README.rst
|
@ -1,23 +1,65 @@
|
||||||
===============================
|
===============================
|
||||||
Git Lint
|
Git Lint: README
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
A git command that automatically runs identifiable linters against
|
A git command that automatically runs identifiable linters against
|
||||||
changed files in current repository or staging.
|
changed files in your current git repository or staging area.
|
||||||
|
|
||||||
* Free software: MIT license
|
* Free software: MIT license
|
||||||
|
|
||||||
**Git Lint** runs a configurable set of syntax, style, and complexity
|
**Git Lint** runs a configurable set of syntax, style, and complexity
|
||||||
checkers against changed files in your current working directory or
|
checkers against changed files in your current working directory or
|
||||||
staging area. It can be configured to work with any `lint` command.
|
staging area. It can be configured to work with any `lint`-like
|
||||||
Some commands may require shell wrappers.
|
command. Some commands may require shell wrappers.
|
||||||
|
|
||||||
While it may be possible to create a custom lint command in your npm,
|
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
|
grunt, Make, CMake, or whatever, the fact is we all use a VCS, and most
|
||||||
most of us use git. Having a centralized repository for what we want
|
of us use git. Having a centralized repository for what we want checked
|
||||||
checked and how we want it checked, associated with git (and by
|
and how we want it checked, associated with git (and by extension, as a
|
||||||
extension, as a pre-commit hook), that can be run at any time for any
|
pre-commit hook), that can be run at any time for any reason.
|
||||||
reason.
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
To lint only what's changed recently in your current working directory:
|
||||||
|
`git lint`
|
||||||
|
|
||||||
|
To lint everything, changed or otherwise, from the current directory down:
|
||||||
|
`git lint -a`
|
||||||
|
|
||||||
|
To lint what's changed from the repo's base:
|
||||||
|
`git lint -b`
|
||||||
|
|
||||||
|
To lint what's in your staging directory:
|
||||||
|
`git lint -s`
|
||||||
|
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Complete documentation for the project is available in the docs directory, or at `Git
|
||||||
|
Linter Docs <https://elfsternberg.github.io/git-linter/index.html>`_.
|
||||||
|
|
||||||
|
|
||||||
|
Install
|
||||||
|
-------
|
||||||
|
|
||||||
|
This *ought* to work:
|
||||||
|
|
||||||
|
`pip install git-linter`
|
||||||
|
|
||||||
|
You will need to copy the .git-lint configuration file to either your
|
||||||
|
home directory or the repo`s base directory. Edit the configuration
|
||||||
|
file as needed. You will also need any linters that you plan on
|
||||||
|
running.
|
||||||
|
|
||||||
|
As git-linter is still mostly alpha code, it might be better to install
|
||||||
|
from source:
|
||||||
|
|
||||||
|
``
|
||||||
|
git clone https://github.com/elfsternberg/git-linter
|
||||||
|
python setup.py install
|
||||||
|
``
|
||||||
|
|
||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
|
@ -37,11 +79,22 @@ Features
|
||||||
restoration of your workspace, it ensures the timestamps are the
|
restoration of your workspace, it ensures the timestamps are the
|
||||||
same, so as not to confuse your build system or IDE.
|
same, so as not to confuse your build system or IDE.
|
||||||
|
|
||||||
Credits
|
|
||||||
-------
|
|
||||||
|
|
||||||
The completion of this project was graciously sponsored by my employer,
|
Acknowledgements
|
||||||
Splunk <http://splunk.com>.
|
----------------
|
||||||
|
|
||||||
|
`Git lint` started life as a simple pre-commit hook. Most of the changes since were
|
||||||
|
inspired by Steve Pulec's `Why You Need a Git Pre-Commit Hook and Why Most Are Wrong <https://dzone.com/articles/why-your-need-git-pre-commit>`_, as well as just my own needs
|
||||||
|
as a software developer.
|
||||||
|
|
||||||
|
To do
|
||||||
|
-----
|
||||||
|
|
||||||
|
* The '-q' and '--quiet' arguments do not work.
|
||||||
|
* Ought to be able to silence the "no linter found" message.
|
||||||
|
* Ought to be able to configure '-q' and '--silence' commands in .git-lint/config file.
|
||||||
|
* Ought to be able to override config file for above.
|
||||||
|
|
||||||
|
|
||||||
Disclaimer
|
Disclaimer
|
||||||
----------
|
----------
|
||||||
|
|
|
@ -46,6 +46,10 @@ def main(*args):
|
||||||
print(error)
|
print(error)
|
||||||
return returncode
|
return returncode
|
||||||
kincaid = re.search(r'Kincaid:\s*([\d\.]+)', values, re.MULTILINE)
|
kincaid = re.search(r'Kincaid:\s*([\d\.]+)', values, re.MULTILINE)
|
||||||
|
|
||||||
|
# Assumes an uncheckable issue, like an empty RST. Not unusual.
|
||||||
|
if not kincaid:
|
||||||
|
return 0
|
||||||
val = float(kincaid.group(1))
|
val = float(kincaid.group(1))
|
||||||
|
|
||||||
msg = ""
|
msg = ""
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from git_lint import load_config, run_linters, git_base
|
from git_lint.git_lint import load_config, run_linters, git_base
|
||||||
|
from git_lint.reporters import print_report
|
||||||
|
|
||||||
import gettext
|
import gettext
|
||||||
_ = gettext.gettext
|
_ = gettext.gettext
|
||||||
|
@ -20,14 +21,14 @@ def main(*args):
|
||||||
unlintable_filenames,
|
unlintable_filenames,
|
||||||
cant_lint_filenames,
|
cant_lint_filenames,
|
||||||
broken_linter_names,
|
broken_linter_names,
|
||||||
unfindable_filenames) = run_linters(options, config)
|
unfindable_filenames) = run_linters(pre_commit_options, config)
|
||||||
|
|
||||||
print_report(results,
|
print_report(results,
|
||||||
unlintable_filenames,
|
unlintable_filenames,
|
||||||
cant_lint_filenames,
|
cant_lint_filenames,
|
||||||
broken_linter_names,
|
broken_linter_names,
|
||||||
unfindable_filenames,
|
unfindable_filenames,
|
||||||
options)
|
pre_commit_options)
|
||||||
|
|
||||||
if not len(results):
|
if not len(results):
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -14,12 +14,19 @@
|
||||||
Scan the workspace [default]
|
Scan the workspace [default]
|
||||||
**-s, --staging**
|
**-s, --staging**
|
||||||
Scan the staging area (useful for pre-commit).
|
Scan the staging area (useful for pre-commit).
|
||||||
**-c** <path>, --config**=<path> Path to config file
|
**-c <path>, --config=<path>**
|
||||||
|
Path to config file
|
||||||
|
**-t, --bylinter**
|
||||||
|
Group reports by linter first as they appear in the config file [default]
|
||||||
|
**-f, --byfile**
|
||||||
|
Group reports by file first, linter second
|
||||||
**-d, --dryrun**
|
**-d, --dryrun**
|
||||||
Report what git-lint would do, but don't actually do anything.
|
Report what git-lint would do, but don't actually do anything.
|
||||||
**-q, --quiet**
|
**-q, --quiet**
|
||||||
Produce a short report of files that failed to pass.
|
Produce a short report of file that failed to pass.
|
||||||
**-h, --help**
|
**-h, --help**
|
||||||
Print a short help message
|
Print a short help message
|
||||||
|
**-V, --verbose**
|
||||||
|
Print a slightly more verbose long report
|
||||||
**-v, --version**
|
**-v, --version**
|
||||||
Print version information
|
Print version information
|
||||||
|
|
92
docs/conf.py
92
docs/conf.py
|
@ -20,7 +20,7 @@ import os
|
||||||
# directory, add these directories to sys.path here. If the directory is
|
# directory, add these directories to sys.path here. If the directory is
|
||||||
# relative to the documentation root, use os.path.abspath to make it
|
# relative to the documentation root, use os.path.abspath to make it
|
||||||
# absolute, like shown here.
|
# absolute, like shown here.
|
||||||
#sys.path.insert(0, os.path.abspath('.'))
|
# sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
# Get the project root dir, which is the parent dir of this
|
# Get the project root dir, which is the parent dir of this
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
|
@ -31,12 +31,12 @@ project_root = os.path.dirname(cwd)
|
||||||
# version is used.
|
# version is used.
|
||||||
sys.path.insert(0, project_root)
|
sys.path.insert(0, project_root)
|
||||||
|
|
||||||
import git_lint
|
import git_lint # noqa
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------
|
# -- General configuration ---------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
#needs_sphinx = '1.0'
|
# needs_sphinx = '1.0'
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
|
@ -49,7 +49,7 @@ templates_path = ['_templates']
|
||||||
source_suffix = '.rst'
|
source_suffix = '.rst'
|
||||||
|
|
||||||
# The encoding of source files.
|
# The encoding of source files.
|
||||||
#source_encoding = 'utf-8-sig'
|
# source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
@ -69,13 +69,13 @@ release = git_lint.__version__
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
#language = None
|
# language = None
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to
|
# There are two options for replacing |today|: either, you set today to
|
||||||
# some non-false value, then it is used:
|
# some non-false value, then it is used:
|
||||||
#today = ''
|
# today = ''
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
#today_fmt = '%B %d, %Y'
|
# today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
|
@ -83,60 +83,60 @@ exclude_patterns = ['_build']
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all
|
# The reST default role (used for this markup: `text`) to use for all
|
||||||
# documents.
|
# documents.
|
||||||
#default_role = None
|
# default_role = None
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
#add_function_parentheses = True
|
# add_function_parentheses = True
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
# If true, the current module name will be prepended to all description
|
||||||
# unit titles (such as .. function::).
|
# unit titles (such as .. function::).
|
||||||
#add_module_names = True
|
# add_module_names = True
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
# output. They are ignored by default.
|
# output. They are ignored by default.
|
||||||
#show_authors = False
|
# show_authors = False
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
pygments_style = 'sphinx'
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
# A list of ignored prefixes for module index sorting.
|
||||||
#modindex_common_prefix = []
|
# modindex_common_prefix = []
|
||||||
|
|
||||||
# If true, keep warnings as "system message" paragraphs in the built
|
# If true, keep warnings as "system message" paragraphs in the built
|
||||||
# documents.
|
# documents.
|
||||||
#keep_warnings = False
|
# keep_warnings = False
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output -------------------------------------------
|
# -- Options for HTML output -------------------------------------------
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
html_theme = 'default'
|
html_theme = 'alabaster'
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a
|
# Theme options are theme-specific and customize the look and feel of a
|
||||||
# theme further. For a list of options available for each theme, see the
|
# theme further. For a list of options available for each theme, see the
|
||||||
# documentation.
|
# documentation.
|
||||||
#html_theme_options = {}
|
# html_theme_options = {}
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
#html_theme_path = []
|
# html_theme_path = []
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
# "<project> v<release> documentation".
|
# "<project> v<release> documentation".
|
||||||
#html_title = None
|
# html_title = None
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as
|
# A shorter title for the navigation bar. Default is the same as
|
||||||
# html_title.
|
# html_title.
|
||||||
#html_short_title = None
|
# html_short_title = None
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the
|
# The name of an image file (relative to this directory) to place at the
|
||||||
# top of the sidebar.
|
# top of the sidebar.
|
||||||
#html_logo = None
|
# html_logo = None
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon
|
# The name of an image file (within the static path) to use as favicon
|
||||||
# of the docs. This file should be a Windows icon file (.ico) being
|
# of the docs. This file should be a Windows icon file (.ico) being
|
||||||
# 16x16 or 32x32 pixels large.
|
# 16x16 or 32x32 pixels large.
|
||||||
#html_favicon = None
|
# html_favicon = None
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets)
|
# Add any paths that contain custom static files (such as style sheets)
|
||||||
# here, relative to this directory. They are copied after the builtin
|
# here, relative to this directory. They are copied after the builtin
|
||||||
|
@ -146,46 +146,46 @@ html_static_path = ['_static']
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page
|
# If not '', a 'Last updated on:' timestamp is inserted at every page
|
||||||
# bottom, using the given strftime format.
|
# bottom, using the given strftime format.
|
||||||
#html_last_updated_fmt = '%b %d, %Y'
|
# html_last_updated_fmt = '%b %d, %Y'
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
# typographically correct entities.
|
# typographically correct entities.
|
||||||
#html_use_smartypants = True
|
# html_use_smartypants = True
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
# Custom sidebar templates, maps document names to template names.
|
||||||
#html_sidebars = {}
|
# html_sidebars = {}
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names
|
# Additional templates that should be rendered to pages, maps page names
|
||||||
# to template names.
|
# to template names.
|
||||||
#html_additional_pages = {}
|
# html_additional_pages = {}
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
#html_domain_indices = True
|
# html_domain_indices = True
|
||||||
|
|
||||||
# If false, no index is generated.
|
# If false, no index is generated.
|
||||||
#html_use_index = True
|
# html_use_index = True
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
# If true, the index is split into individual pages for each letter.
|
||||||
#html_split_index = False
|
# html_split_index = False
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
# If true, links to the reST sources are added to the pages.
|
||||||
#html_show_sourcelink = True
|
# html_show_sourcelink = True
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer.
|
# If true, "Created using Sphinx" is shown in the HTML footer.
|
||||||
# Default is True.
|
# Default is True.
|
||||||
#html_show_sphinx = True
|
# html_show_sphinx = True
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer.
|
# If true, "(C) Copyright ..." is shown in the HTML footer.
|
||||||
# Default is True.
|
# Default is True.
|
||||||
#html_show_copyright = True
|
# html_show_copyright = True
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages
|
# If true, an OpenSearch description file will be output, and all pages
|
||||||
# will contain a <link> tag referring to it. The value of this option
|
# will contain a <link> tag referring to it. The value of this option
|
||||||
# must be the base URL from which the finished HTML is served.
|
# must be the base URL from which the finished HTML is served.
|
||||||
#html_use_opensearch = ''
|
# html_use_opensearch = ''
|
||||||
|
|
||||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
#html_file_suffix = None
|
# html_file_suffix = None
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'git_lintdoc'
|
htmlhelp_basename = 'git_lintdoc'
|
||||||
|
@ -195,13 +195,13 @@ htmlhelp_basename = 'git_lintdoc'
|
||||||
|
|
||||||
latex_elements = {
|
latex_elements = {
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
#'papersize': 'letterpaper',
|
# 'papersize': 'letterpaper',
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
#'pointsize': '10pt',
|
# 'pointsize': '10pt',
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Additional stuff for the LaTeX preamble.
|
||||||
#'preamble': '',
|
# 'preamble': '',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
@ -215,23 +215,23 @@ latex_documents = [
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at
|
# The name of an image file (relative to this directory) to place at
|
||||||
# the top of the title page.
|
# the top of the title page.
|
||||||
#latex_logo = None
|
# latex_logo = None
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings
|
# For "manual" documents, if this is true, then toplevel headings
|
||||||
# are parts, not chapters.
|
# are parts, not chapters.
|
||||||
#latex_use_parts = False
|
# latex_use_parts = False
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
# If true, show page references after internal links.
|
||||||
#latex_show_pagerefs = False
|
# latex_show_pagerefs = False
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
#latex_show_urls = False
|
# latex_show_urls = False
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# Documents to append as an appendix to all manuals.
|
||||||
#latex_appendices = []
|
# latex_appendices = []
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
#latex_domain_indices = True
|
# latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output ------------------------------------
|
# -- Options for manual page output ------------------------------------
|
||||||
|
@ -245,7 +245,7 @@ man_pages = [
|
||||||
]
|
]
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
#man_show_urls = False
|
# man_show_urls = False
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output ----------------------------------------
|
# -- Options for Texinfo output ----------------------------------------
|
||||||
|
@ -263,13 +263,13 @@ texinfo_documents = [
|
||||||
]
|
]
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# Documents to append as an appendix to all manuals.
|
||||||
#texinfo_appendices = []
|
# texinfo_appendices = []
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
#texinfo_domain_indices = True
|
# texinfo_domain_indices = True
|
||||||
|
|
||||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||||
#texinfo_show_urls = 'footnote'
|
# texinfo_show_urls = 'footnote'
|
||||||
|
|
||||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||||
#texinfo_no_detailmenu = False
|
# texinfo_no_detailmenu = False
|
||||||
|
|
|
@ -12,6 +12,30 @@ git_lint.git_lint module
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
git_lint.option_handler module
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
.. automodule:: git_lint.option_handler
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
git_lint.options module
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
.. automodule:: git_lint.options
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
git_lint.reporters module
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. automodule:: git_lint.reporters
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
|
@ -3,8 +3,12 @@
|
||||||
You can adapt this file completely to your liking, but it should at least
|
You can adapt this file completely to your liking, but it should at least
|
||||||
contain the root `toctree` directive.
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
Welcome to Git Lint's documentation!
|
Git Lint
|
||||||
======================================
|
========
|
||||||
|
|
||||||
|
Git Lint provides a new git command that automatically runs identifiable linters (style
|
||||||
|
and syntax quality assurance programs) against changed files in your current git
|
||||||
|
repository or staging area.
|
||||||
|
|
||||||
Contents:
|
Contents:
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ To install Git Lint, run this command in your terminal:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ pip install git_lint
|
$ pip install git-linter
|
||||||
|
|
||||||
If you don't have `pip`_ installed, this `Python installation guide`_ can guide
|
If you don't have `pip`_ installed, this `Python installation guide`_ can guide
|
||||||
you through the process.
|
you through the process.
|
||||||
|
@ -30,13 +30,13 @@ You can either clone the public repository:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ git clone git://github.com/elfsternberg/git_lint
|
$ git clone git://github.com/elfsternberg/git-linter
|
||||||
|
|
||||||
Or download the `tarball`_:
|
Or download the `tarball`_:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ curl -OL https://github.com/elfsternberg/git_lint/tarball/master
|
$ curl -OL https://github.com/elfsternberg/git-linter/tarball/master
|
||||||
|
|
||||||
Once you have a copy of the source, you can install it with:
|
Once you have a copy of the source, you can install it with:
|
||||||
|
|
||||||
|
@ -44,10 +44,8 @@ Once you have a copy of the source, you can install it with:
|
||||||
|
|
||||||
$ python setup.py install
|
$ python setup.py install
|
||||||
|
|
||||||
.. _Github repo: https://github.com/elfsternberg/git_lint
|
.. _Github repo: https://github.com/elfsternberg/git-linter
|
||||||
.. _tarball: https://github.com/elfsternberg/git_lint/tarball/master
|
.. _tarball: https://github.com/elfsternberg/git-linter/tarball/master
|
||||||
|
|
||||||
Once installed, you may run the 'git lint --make-config' command, which
|
Once installed, please copy the '.git-lint' example file. You may install this either in
|
||||||
will generate a simple configuration file. You may install this either
|
your home or repository directory as ``.git-lint``.
|
||||||
in your home directory as ``.git-lint.conf`` or in your project's git
|
|
||||||
directory as ``.git/lint/git-lint.conf``
|
|
||||||
|
|
|
@ -12,24 +12,13 @@ git lint [options] [filenames]
|
||||||
Options
|
Options
|
||||||
-------
|
-------
|
||||||
|
|
||||||
.. include: arguments.rst
|
.. include:: arguments.rst
|
||||||
|
|
||||||
As a pre-commit hook:
|
As a pre-commit hook:
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
.. code-block:: python
|
There's a file, pre-commit, in the /bin directory with the project. (Or you can download
|
||||||
|
it from the github repository.) Install it in you .git/hooks/pre-commit file, and
|
||||||
#!/usr/bin/env python
|
chmod +x .git/hooks/pre-commit.
|
||||||
import git_lint
|
|
||||||
git_lint.run_precommit(staging = True, timestamps = True)
|
|
||||||
|
|
||||||
Install this file in your project's ``.git/hooks/pre-commit``, and set
|
|
||||||
the file's executable flag to ``true``:
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
chmod +x pre-commit
|
|
||||||
|
|
||||||
Please see the :ref:`api` for more details on options taken by the
|
|
||||||
``run_precommit()`` and ``run_gitlint`` commands.
|
|
||||||
|
|
||||||
|
The pre-commit hook is *experimental*. Please be careful with it.
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
__author__ = 'Kenneth M. "Elf" Sternberg'
|
__author__ = 'Kenneth M. "Elf" Sternberg'
|
||||||
__email__ = 'elf.sternberg@gmail.com'
|
__email__ = 'elf.sternberg@gmail.com'
|
||||||
__version__ = '0.0.4'
|
__version__ = '0.0.7'
|
||||||
|
|
||||||
__all__ = ['git_lint']
|
__all__ = ['git_lint']
|
||||||
|
|
|
@ -11,7 +11,7 @@ import gettext
|
||||||
_ = gettext.gettext
|
_ = gettext.gettext
|
||||||
|
|
||||||
NAME = 'git-lint'
|
NAME = 'git-lint'
|
||||||
VERSION = '0.0.4'
|
VERSION = '0.0.7'
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
|
@ -9,11 +9,18 @@ import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import pprint
|
import pprint
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import configparser
|
import configparser
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
import ConfigParser as configparser
|
import ConfigParser as configparser
|
||||||
|
|
||||||
|
try: # noqa: F401
|
||||||
|
from typing import Dict, List, Text, Any, Optional, Union, Callable, Tuple # noqa: F401
|
||||||
|
except: # noqa: F401
|
||||||
|
pass # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
_ = gettext.gettext
|
_ = gettext.gettext
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,7 +31,20 @@ _ = gettext.gettext
|
||||||
# |___/
|
# |___/
|
||||||
|
|
||||||
|
|
||||||
def find_config_file(options, base):
|
# (commandLineDictionary, repositoryLocation) -> (configurationDictionary | exit)
|
||||||
|
def load_config(options, base):
|
||||||
|
"""Loads the git-lint configuration file.
|
||||||
|
|
||||||
|
Returns the configuration file as a dictionary of dictionaries.
|
||||||
|
Performs substitutions as specified in the SafeConfigParser
|
||||||
|
specification; the only one performed currently is the 'repodir'
|
||||||
|
will be replaced with the base directory of the repository.
|
||||||
|
Combined with the option to specify the .git-lint configuration as
|
||||||
|
a directory, this allows users to keep per-project configuration
|
||||||
|
files for specific linters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def find_config_file(options, base):
|
||||||
""" Returns the configuration file from a prioritized list of locations.
|
""" Returns the configuration file from a prioritized list of locations.
|
||||||
|
|
||||||
Locations are prioritized as:
|
Locations are prioritized as:
|
||||||
|
@ -56,20 +76,6 @@ def find_config_file(options, base):
|
||||||
|
|
||||||
return matches[0]
|
return matches[0]
|
||||||
|
|
||||||
|
|
||||||
# (commandLineDictionary, repositoryLocation) -> (configurationDictionary | exit)
|
|
||||||
def load_config(options, base):
|
|
||||||
"""Loads the git-lint configuration file.
|
|
||||||
|
|
||||||
Returns the configuration file as a dictionary of dictionaries.
|
|
||||||
Performs substitutions as specified in the SafeConfigParser
|
|
||||||
specification; the only one performed currently is the 'repodir'
|
|
||||||
will be replaced with the base directory of the repository.
|
|
||||||
Combined with the option to specify the .git-lint configuration as
|
|
||||||
a directory, this allows users to keep per-project configuration
|
|
||||||
files for specific linters.
|
|
||||||
"""
|
|
||||||
|
|
||||||
Linter = namedtuple('Linter', ['name', 'linter'])
|
Linter = namedtuple('Linter', ['name', 'linter'])
|
||||||
path = find_config_file(options, base)
|
path = find_config_file(options, base)
|
||||||
configloader = configparser.SafeConfigParser()
|
configloader = configparser.SafeConfigParser()
|
||||||
|
@ -146,10 +152,6 @@ git_head = get_git_head()
|
||||||
# \___/ \__|_|_|_|\__|_\___/__/
|
# \___/ \__|_|_|_|\__|_\___/__/
|
||||||
#
|
#
|
||||||
|
|
||||||
def base_file_cleaner(files):
|
|
||||||
return [file.replace(git_base + '/', '', 1) for file in files]
|
|
||||||
|
|
||||||
|
|
||||||
class MatchFilter:
|
class MatchFilter:
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
@ -172,37 +174,34 @@ class MatchFilter:
|
||||||
# \___|_||_\___\__|_\_\ |_|_|_||_\__\___|_| /__/
|
# \___|_||_\___\__|_\_\ |_|_|_||_\__\___|_| /__/
|
||||||
#
|
#
|
||||||
|
|
||||||
def executable_exists(script, label):
|
def linter_exists(linter, label):
|
||||||
if not len(script):
|
if not len(linter):
|
||||||
sys.exit(
|
sys.exit(_('Syntax error in linter configuration for {} ').format(label))
|
||||||
_('Syntax error in command configuration for {} ').format(label))
|
|
||||||
|
|
||||||
scriptname = script.split(' ').pop(0)
|
lintername = linter.split(' ').pop(0)
|
||||||
if not len(scriptname):
|
if not len(lintername):
|
||||||
sys.exit(
|
sys.exit(_('Syntax error in linter configuration for {} ').format(label))
|
||||||
_('Syntax error in command configuration for {} ').format(label))
|
|
||||||
|
|
||||||
def is_executable(path):
|
def is_executable(path):
|
||||||
return os.path.exists(path) and os.access(path, os.X_OK)
|
return os.path.exists(path) and os.access(path, os.X_OK)
|
||||||
|
|
||||||
if scriptname.startswith('/'):
|
if lintername.startswith('/'):
|
||||||
return (is_executable(scriptname) and scriptname) or None
|
return (is_executable(lintername) and lintername) or None
|
||||||
|
|
||||||
# shutil.which() doesn't appear until Python 3, darnit.
|
# shutil.which() doesn't appear until Python 3, darnit.
|
||||||
possibles = [path for path in
|
possibles = [path for path in
|
||||||
[os.path.join(path, scriptname)
|
[os.path.join(path, lintername)
|
||||||
for path in os.environ.get('PATH').split(':')]
|
for path in os.environ.get('PATH').split(':')]
|
||||||
if is_executable(path)]
|
if is_executable(path)]
|
||||||
|
|
||||||
return (len(possibles) and possibles.pop(0)) or False
|
return (len(possibles) and possibles.pop(0)) or False
|
||||||
|
|
||||||
|
|
||||||
def get_working_linter_names(config):
|
|
||||||
return [i.name for i in config
|
|
||||||
if executable_exists(i.linter['command'], i.name)]
|
|
||||||
|
|
||||||
|
|
||||||
def get_linter_status(config):
|
def get_linter_status(config):
|
||||||
|
def get_working_linter_names(config):
|
||||||
|
return [i.name for i in config
|
||||||
|
if linter_exists(i.linter['command'], i.name)]
|
||||||
|
|
||||||
working_linter_names = get_working_linter_names(config)
|
working_linter_names = get_working_linter_names(config)
|
||||||
broken_linter_names = (set([i.name for i in config]) - set(working_linter_names))
|
broken_linter_names = (set([i.name for i in config]) - set(working_linter_names))
|
||||||
return working_linter_names, broken_linter_names
|
return working_linter_names, broken_linter_names
|
||||||
|
@ -276,6 +275,11 @@ def get_filelist(options, extras):
|
||||||
|
|
||||||
return check_for_conflicts(parse_stream([], stream))
|
return check_for_conflicts(parse_stream([], stream))
|
||||||
|
|
||||||
|
def revision_list():
|
||||||
|
cmd = ['diff', '--name-only', '-z', options.get('revision')]
|
||||||
|
return [entry for entry in get_git_response(cmd).split(u'\x00')
|
||||||
|
if len(entry) > 0]
|
||||||
|
|
||||||
def staging_list():
|
def staging_list():
|
||||||
""" Return the list of files added or modified to the stage """
|
""" Return the list of files added or modified to the stage """
|
||||||
|
|
||||||
|
@ -307,6 +311,9 @@ def get_filelist(options, extras):
|
||||||
working_directory_trans = base_file_filter
|
working_directory_trans = base_file_filter
|
||||||
|
|
||||||
file_list_generator = working_list
|
file_list_generator = working_list
|
||||||
|
if 'revision' in options:
|
||||||
|
file_list_generator = revision_list
|
||||||
|
working_directory_trans = base_file_filter
|
||||||
if 'all' in options:
|
if 'all' in options:
|
||||||
file_list_generator = all_list
|
file_list_generator = all_list
|
||||||
if 'staging' in options:
|
if 'staging' in options:
|
||||||
|
@ -322,33 +329,33 @@ def get_filelist(options, extras):
|
||||||
# |___/ |___/ |_| |_|
|
# |___/ |___/ |_| |_|
|
||||||
|
|
||||||
|
|
||||||
class Runner:
|
class StagingRunner:
|
||||||
def __init__(self, options):
|
def __init__(self, filenames):
|
||||||
self.runner = ('staging' in options and Runner.staging_wrapper) or Runner.workspace_wrapper
|
self.filenames = filenames
|
||||||
|
|
||||||
def __call__(self, run_linters, filenames):
|
def __enter__(self):
|
||||||
return self.runner(run_linters, filenames)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def staging_wrapper(run_linters, filenames):
|
|
||||||
def time_gather(f):
|
def time_gather(f):
|
||||||
stats = os.stat(f)
|
stats = os.stat(f)
|
||||||
return (f, (stats.st_atime, stats.st_mtime))
|
return (f, (stats.st_atime, stats.st_mtime))
|
||||||
|
self.times = [time_gather(filename) for filename in self.filenames]
|
||||||
times = [time_gather(file) for file in filenames]
|
|
||||||
run_git_command(['stash', '--keep-index'])
|
run_git_command(['stash', '--keep-index'])
|
||||||
|
|
||||||
results = run_linters()
|
def __exit__(self, type, value, traceback):
|
||||||
run_git_command(['reset', '--hard'])
|
run_git_command(['reset', '--hard'])
|
||||||
run_git_command(['stash', 'pop', '--quiet', '--index'])
|
run_git_command(['stash', 'pop', '--quiet', '--index'])
|
||||||
|
for (filename, timepair) in self.times:
|
||||||
for (filename, timepair) in times:
|
|
||||||
os.utime(filename, timepair)
|
os.utime(filename, timepair)
|
||||||
return results
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def workspace_wrapper(run_linters, filenames):
|
class WorkspaceRunner(object):
|
||||||
return run_linters()
|
def __init__(self, filenames):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# ___ _ _ _
|
# ___ _ _ _
|
||||||
|
@ -372,7 +379,7 @@ class Linters:
|
||||||
"""Run one linter against one file.
|
"""Run one linter against one file.
|
||||||
|
|
||||||
If the result matches the error condition specified in the configuration file,
|
If the result matches the error condition specified in the configuration file,
|
||||||
return the error code and messages, either return nothing.
|
return the error code and messages, otherwise return nothing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cmd = linter['command'] + ' "' + filename + '"'
|
cmd = linter['command'] + ' "' + filename + '"'
|
||||||
|
@ -425,6 +432,9 @@ class Linters:
|
||||||
|
|
||||||
|
|
||||||
def run_linters(options, config, extras=[]):
|
def run_linters(options, config, extras=[]):
|
||||||
|
if 'pr' in options:
|
||||||
|
options.pop('pr')
|
||||||
|
options['revision'] = 'HEAD^..HEAD'
|
||||||
|
|
||||||
def build_config_subset(keys):
|
def build_config_subset(keys):
|
||||||
""" Returns a subset of the configuration, with only those linters mentioned in keys """
|
""" Returns a subset of the configuration, with only those linters mentioned in keys """
|
||||||
|
@ -434,21 +444,31 @@ def run_linters(options, config, extras=[]):
|
||||||
all_filenames, unfindable_filenames = get_filelist(options, extras)
|
all_filenames, unfindable_filenames = get_filelist(options, extras)
|
||||||
|
|
||||||
is_lintable = MatchFilter(config)
|
is_lintable = MatchFilter(config)
|
||||||
|
|
||||||
lintable_filenames = set([filename for filename in all_filenames
|
lintable_filenames = set([filename for filename in all_filenames
|
||||||
if is_lintable(filename)])
|
if is_lintable(filename)])
|
||||||
|
|
||||||
unlintable_filenames = set(all_filenames) - lintable_filenames
|
unlintable_filenames = set(all_filenames) - lintable_filenames
|
||||||
|
|
||||||
|
# Filter the linter config down to the selected ones.
|
||||||
|
if 'only' in options:
|
||||||
|
config = [linter for linter in config
|
||||||
|
if linter.name in options['only']]
|
||||||
|
elif 'exclude' in options:
|
||||||
|
config = [linter for linter in config
|
||||||
|
if linter.name not in options['exclude']]
|
||||||
|
if not len(config):
|
||||||
|
raise RuntimeError('No linters left to run! Be less strict with --only and --exclude.')
|
||||||
|
|
||||||
working_linter_names, broken_linter_names = get_linter_status(config)
|
working_linter_names, broken_linter_names = get_linter_status(config)
|
||||||
|
|
||||||
cant_lint_filter = MatchFilter(build_config_subset(
|
cant_lint_filter = MatchFilter(build_config_subset(
|
||||||
broken_linter_names))
|
broken_linter_names))
|
||||||
|
|
||||||
cant_lint_filenames = [filename for filename in lintable_filenames
|
cant_lint_filenames = [filename for filename in lintable_filenames
|
||||||
if cant_lint_filter(filename)]
|
if cant_lint_filter(filename)]
|
||||||
|
|
||||||
runner = Runner(options)
|
runner = WorkspaceRunner
|
||||||
|
if 'staging' in options:
|
||||||
|
runner = StagingRunner
|
||||||
|
|
||||||
linters = Linters(build_config_subset(working_linter_names),
|
linters = Linters(build_config_subset(working_linter_names),
|
||||||
sorted(lintable_filenames))
|
sorted(lintable_filenames))
|
||||||
|
@ -458,7 +478,8 @@ def run_linters(options, config, extras=[]):
|
||||||
return (dryrun_results, unlintable_filenames, cant_lint_filenames,
|
return (dryrun_results, unlintable_filenames, cant_lint_filenames,
|
||||||
broken_linter_names, unfindable_filenames)
|
broken_linter_names, unfindable_filenames)
|
||||||
|
|
||||||
results = runner(linters, lintable_filenames)
|
with runner(lintable_filenames):
|
||||||
|
results = linters()
|
||||||
|
|
||||||
return (results, unlintable_filenames, cant_lint_filenames,
|
return (results, unlintable_filenames, cant_lint_filenames,
|
||||||
broken_linter_names, unfindable_filenames)
|
broken_linter_names, unfindable_filenames)
|
||||||
|
|
|
@ -2,8 +2,18 @@
|
||||||
# Author: Elf M. Sternberg
|
# Author: Elf M. Sternberg
|
||||||
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
from collections import namedtuple
|
||||||
import getopt
|
import getopt
|
||||||
|
|
||||||
|
try: # noqa: F401
|
||||||
|
from typing import Dict, List, Text, Any, Optional, Union, Callable, Tuple # noqa: F401
|
||||||
|
except: # noqa: F401
|
||||||
|
pass # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
Option = namedtuple('Option', ['short', 'long', 'takes', 'help', 'conflicts']) # type: str, str, str, str, List[str]
|
||||||
|
Arguments = namedtuple('Arguments', ['arguments', 'filenames', 'excluded']) # type: Dict[str, str], List[str], List[str]
|
||||||
|
|
||||||
# This was a lot shorter and smarter in Hy...
|
# This was a lot shorter and smarter in Hy...
|
||||||
|
|
||||||
# A lot of what you see here is separated from git_lint itself, since this will not be
|
# A lot of what you see here is separated from git_lint itself, since this will not be
|
||||||
|
@ -17,6 +27,7 @@ import getopt
|
||||||
|
|
||||||
|
|
||||||
def cleanup_options(options, commandline):
|
def cleanup_options(options, commandline):
|
||||||
|
# type: (List[Option], List[str]) -> Arguments
|
||||||
"""Takes a table of options and the commandline, and returns a
|
"""Takes a table of options and the commandline, and returns a
|
||||||
dictionary of those options that appear on the commandline
|
dictionary of those options that appear on the commandline
|
||||||
along with any extra arguments.
|
along with any extra arguments.
|
||||||
|
@ -24,52 +35,63 @@ def cleanup_options(options, commandline):
|
||||||
:param List(Tuple (string, string, boolean, string, List(string))) options,
|
:param List(Tuple (string, string, boolean, string, List(string))) options,
|
||||||
The table of options: One-letter option, long option, takes arguments,
|
The table of options: One-letter option, long option, takes arguments,
|
||||||
Help text, list of (long) options superseded by this one.
|
Help text, list of (long) options superseded by this one.
|
||||||
|
|
||||||
: param List(strings) commandline
|
: param List(strings) commandline
|
||||||
The arguments as received by the start-up process
|
The arguments as received by the start-up process
|
||||||
|
|
||||||
|
: returns List(strings), List(strings), List(strings)
|
||||||
|
The longopt dictionary of arguments and associated values (if any)
|
||||||
|
The list of filenames left after argument processing
|
||||||
|
The longopt list of arguments that were excluded by argument precedence
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def make_option_streamliner(options):
|
def make_option_streamliner(options):
|
||||||
|
# type: (List[Option]) -> Callable[[Dict[str, str], Option], Dict[str, str]]
|
||||||
|
|
||||||
"""Takes a list of option tuples, and returns a function that takes
|
"""Takes a list of option tuples, and returns a function that takes
|
||||||
the output of getopt and reduces it to the longopt key and
|
the output of getopt and reduces it to the longopt key and
|
||||||
associated values as a dictionary.
|
associated values as a dictionary.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fullset = {}
|
fullset = {} # type: Dict[str, str]
|
||||||
for option in options:
|
for option in options:
|
||||||
if option[1]:
|
if option.long:
|
||||||
fullset['--' + option[1]] = option[1]
|
fullset['--' + option.long] = option.long
|
||||||
if option[0]:
|
if option.short:
|
||||||
fullset['-' + option[0]] = option[1]
|
fullset['-' + option.short] = option.long
|
||||||
|
|
||||||
def streamliner(acc, it):
|
def streamliner(acc, it):
|
||||||
|
# type: (Dict[str, str], Option) -> Dict[str, str]
|
||||||
acc[fullset[it[0]]] = it[1]
|
acc[fullset[it[0]]] = it[1]
|
||||||
return acc
|
return acc
|
||||||
|
|
||||||
return streamliner
|
return streamliner
|
||||||
|
|
||||||
def remove_conflicted_options(options, request):
|
def remove_conflicted_options(options, request):
|
||||||
|
# type: (List[Option], Dict[str, str]) -> Tuple[List[str], List[str]]
|
||||||
"""Takes our list of option tuples, and a cleaned copy of what was
|
"""Takes our list of option tuples, and a cleaned copy of what was
|
||||||
requested from getopt, and returns a copy of the request
|
requested from getopt, and returns a copy of the request
|
||||||
without any options that are marked as superseded, along with
|
without any options that are marked as superseded, along with
|
||||||
the list of superseded options
|
the list of superseded options
|
||||||
"""
|
"""
|
||||||
def get_excluded_keys(memo, opt):
|
def get_excluded_keys(memo, option):
|
||||||
return memo + ((len(opt) > 4 and opt[4]) or [])
|
return memo + option.conflicts
|
||||||
|
|
||||||
keys = request.keys()
|
keys = request.keys()
|
||||||
marked = [option for option in options if option[1] in keys]
|
marked = [option for option in options if option.long in keys]
|
||||||
exclude = reduce(get_excluded_keys, marked, [])
|
exclude = reduce(get_excluded_keys, marked, [])
|
||||||
excluded = [key for key in keys if key in exclude]
|
excluded = [key for key in keys if key in exclude]
|
||||||
cleaned = {key: request[key] for key in keys
|
cleaned = {key: request[key] for key in keys
|
||||||
if key not in excluded}
|
if key not in excluded}
|
||||||
return (cleaned, excluded)
|
return (cleaned, excluded)
|
||||||
|
|
||||||
def shortoptstogo(i): return i[0] + ((i[2] and ':') or '')
|
def shortoptstogo(i):
|
||||||
|
return i.short + ((i.takes and ':') or '')
|
||||||
|
|
||||||
def longoptstogo(i): return i[1] + ((i[2] and '=') or '')
|
def longoptstogo(i):
|
||||||
|
return i.long + ((i.takes and '=') or '')
|
||||||
|
|
||||||
optstringsshort = ''.join([shortoptstogo(opt) for opt in options])
|
optstringsshort = ''.join([shortoptstogo(opt) for opt in options if opt.short])
|
||||||
optstringslong = [longoptstogo(opt) for opt in options]
|
optstringslong = [longoptstogo(opt) for opt in options]
|
||||||
(chosen_options, filenames) = getopt.getopt(commandline[1:],
|
(chosen_options, filenames) = getopt.getopt(commandline[1:],
|
||||||
optstringsshort,
|
optstringsshort,
|
||||||
|
@ -82,4 +104,4 @@ def cleanup_options(options, commandline):
|
||||||
(ret, excluded) = remove_conflicted_options(
|
(ret, excluded) = remove_conflicted_options(
|
||||||
options, reduce(streamline_options, chosen_options, {}))
|
options, reduce(streamline_options, chosen_options, {}))
|
||||||
|
|
||||||
return (ret, filenames, excluded)
|
return Arguments(ret, filenames, excluded)
|
||||||
|
|
|
@ -1,37 +1,45 @@
|
||||||
import gettext
|
import gettext
|
||||||
|
from option_handler import Option
|
||||||
|
|
||||||
_ = gettext.gettext
|
_ = gettext.gettext
|
||||||
|
|
||||||
OPTIONS = [
|
OPTIONS = [
|
||||||
('o', 'only', True,
|
Option('o', 'only', True,
|
||||||
_('A comma-separated list of only those linters to run'), ['exclude']),
|
_('A comma-separated list of only those linters to run'), ['exclude']),
|
||||||
('x', 'exclude', True,
|
Option('x', 'exclude', True,
|
||||||
_('A comma-separated list of linters to skip'), []),
|
_('A comma-separated list of linters to skip'), []),
|
||||||
('l', 'linters', False,
|
Option('l', 'linters', False,
|
||||||
_('Show the list of configured linters'), []),
|
_('Show the list of configured linters'), []),
|
||||||
('b', 'base', False,
|
Option('b', 'base', False,
|
||||||
_('Check all changed files in the repository, not just those in the current directory.'), []),
|
_('Check all changed files in the repository, not just those in the current directory.'), []),
|
||||||
('a', 'all', False,
|
Option('a', 'all', False,
|
||||||
_('Scan all files in the repository, not just those that have changed.'), []),
|
_('Scan all files in the repository, not just those that have changed.'), ['revision']),
|
||||||
('e', 'every', False,
|
Option('r', 'revision', True,
|
||||||
|
_('Scan all files changed between revisions'), []),
|
||||||
|
Option(None, 'pr', False,
|
||||||
|
_('Scan all files changed between head and previous check-in'), ['revision']),
|
||||||
|
Option('e', 'every', False,
|
||||||
_('Short for -b -a: scan everything'), []),
|
_('Short for -b -a: scan everything'), []),
|
||||||
('w', 'workspace', False,
|
Option('w', 'workspace', False,
|
||||||
_('Scan the workspace'), ['staging']),
|
_('Scan the workspace'), ['staging']),
|
||||||
('s', 'staging', False,
|
Option('s', 'staging', False,
|
||||||
_('Scan the staging area (useful for pre-commit).'), []),
|
_('Scan the staging area (useful for pre-commit).'), []),
|
||||||
('g', 'changes', False,
|
# ('g', 'changes', False,
|
||||||
_("Report lint failures only for diff'd sections"), ['complete']),
|
# _("Report lint failures only for diff'd sections"), ['complete']),
|
||||||
('p', 'complete', False,
|
# ('p', 'complete', False,
|
||||||
_('Report lint failures for all files'), []),
|
# _('Report lint failures for all files'), []),
|
||||||
('t', 'bylinter', False,
|
Option('t', 'bylinter', False,
|
||||||
_('Group the reports by linter first as they appear in the config file [default]'), []),
|
_('Group the reports by linter first as they appear in the config file [default]'), []),
|
||||||
('f', 'byfile', False,
|
Option('f', 'byfile', False,
|
||||||
_('Group the reports by file first'), []),
|
_('Group the reports by file first'), []),
|
||||||
('d', 'dryrun', False,
|
Option('d', 'dryrun', False,
|
||||||
_('Dry run - report what would be done, but do not run linters'), []),
|
_('Dry run - report what would be done, but do not run linters'), []),
|
||||||
('c', 'config', True,
|
Option('c', 'config', True,
|
||||||
_('Path to config file'), []),
|
_('Path to config file'), []),
|
||||||
('h', 'help', False,
|
Option('h', 'help', False,
|
||||||
_('This help message'), []),
|
_('This help message'), []),
|
||||||
('v', 'version', False,
|
Option('V', 'verbose', False,
|
||||||
|
_('A slightly more verbose output'), []),
|
||||||
|
Option('v', 'version', False,
|
||||||
_('Version information'), [])
|
_('Version information'), [])
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,10 +1,19 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
from functools import reduce
|
||||||
|
from .git_lint import load_config, run_linters, git_base
|
||||||
|
import operator
|
||||||
import gettext
|
import gettext
|
||||||
_ = gettext.gettext
|
_ = gettext.gettext
|
||||||
|
|
||||||
|
|
||||||
# ICK. Mutation, references, and hidden assignment.
|
def print_report(results, unlintable_filenames, cant_lint_filenames,
|
||||||
def group_by(iterable, field_id):
|
broken_linter_names, unfindable_filenames, options={'bylinter': True}):
|
||||||
|
|
||||||
|
def base_file_cleaner(files):
|
||||||
|
return [file.replace(git_base + '/', '', 1) for file in files]
|
||||||
|
|
||||||
|
# ICK. Mutation, references, and hidden assignment.
|
||||||
|
def group_by(iterable, field_id):
|
||||||
results = []
|
results = []
|
||||||
keys = {}
|
keys = {}
|
||||||
for obj in iterable:
|
for obj in iterable:
|
||||||
|
@ -16,37 +25,46 @@ def group_by(iterable, field_id):
|
||||||
results.append((key, keys[key]))
|
results.append((key, keys[key]))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
def print_report(results, unlintable_filenames, cant_lint_filenames,
|
|
||||||
broken_linter_names, unfindable_filenames, options={'bylinter': True}):
|
|
||||||
sort_position = 1
|
sort_position = 1
|
||||||
grouping = _('Linter: {}')
|
grouping = _('Linter: {}')
|
||||||
if 'byfile' in options:
|
if 'byfile' in options:
|
||||||
sort_position = 0
|
sort_position = 0
|
||||||
grouping = _('Filename: {}')
|
grouping = _('Filename: {}')
|
||||||
grouped_results = group_by(results, sort_position)
|
grouped_results = group_by(results, sort_position)
|
||||||
|
|
||||||
for group in grouped_results:
|
for group in grouped_results:
|
||||||
|
messages = reduce(operator.add, [item[3] for item in group[1]], [])
|
||||||
|
if len(messages) == 0:
|
||||||
|
continue
|
||||||
print(grouping.format(group[0]))
|
print(grouping.format(group[0]))
|
||||||
for (filename, lintername, returncode, text) in group[1]:
|
for (filename, lintername, returncode, text) in group[1]:
|
||||||
print('\n'.join(text))
|
if text:
|
||||||
|
print('\n'.join(base_file_cleaner(text)))
|
||||||
print('')
|
print('')
|
||||||
if len(broken_linter_names):
|
print ('')
|
||||||
print(_('These linters could not be run:'), ','.join(broken_linter_names))
|
|
||||||
|
if len(broken_linter_names) and (len(cant_lint_filenames) or ('verbose' in options)):
|
||||||
|
print(_('Linters not found:'), ','.join(broken_linter_names))
|
||||||
if len(cant_lint_filenames):
|
if len(cant_lint_filenames):
|
||||||
print(_('As a result, these files were not linted:'))
|
print(' ' + _('Files not linted:'))
|
||||||
print('\n'.join([' {}'.format(f) for f in cant_lint_filenames]))
|
print('\n'.join([' {}'.format(f) for f in cant_lint_filenames]))
|
||||||
if len(unlintable_filenames):
|
print('')
|
||||||
print(_('The following files had no recognizeable linters:'))
|
|
||||||
|
if len(unlintable_filenames) and ('verbose' in options):
|
||||||
|
print(_('No recognizeable linters for:'))
|
||||||
print('\n'.join([' {}'.format(f) for f in unlintable_filenames]))
|
print('\n'.join([' {}'.format(f) for f in unlintable_filenames]))
|
||||||
|
print('')
|
||||||
|
|
||||||
if len(unfindable_filenames):
|
if len(unfindable_filenames):
|
||||||
print(_('The following files could not be found:'))
|
print(_('Files not found:'))
|
||||||
print('\n'.join([' {}'.format(f) for f in unfindable_filenames]))
|
print('\n'.join([' {}'.format(f) for f in unfindable_filenames]))
|
||||||
|
print('')
|
||||||
|
|
||||||
|
|
||||||
def print_help(options, name):
|
def print_help(options, name):
|
||||||
print(_('Usage: {} [options] [filenames]').format(name))
|
print(_('Usage: {} [options] [filenames]').format(name))
|
||||||
for item in options:
|
for item in options:
|
||||||
print(' -{:<1} --{:<12} {}'.format(item[0], item[1], item[3]))
|
print(' {:<2} --{:<12} {}'.format((item[0] and ('-' + item[0])) or '', item[1], item[3]))
|
||||||
|
|
||||||
|
|
||||||
def print_version(name, version):
|
def print_version(name, version):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 0.0.4
|
current_version = 0.0.7
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
|
|
||||||
|
@ -16,3 +16,4 @@ universal = 1
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
exclude = docs
|
exclude = docs
|
||||||
|
|
||||||
|
|
21
setup.py
21
setup.py
|
@ -1,7 +1,22 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
import re
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
|
||||||
|
def get_data_files(prefix):
|
||||||
|
if prefix.startswith('/System/Library/Frameworks'):
|
||||||
|
return []
|
||||||
|
return [(os.path.join(prefix, 'share/man/man1'), ['docs/_build/man/git-lint.1'])]
|
||||||
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--prefix', default='',
|
||||||
|
help='prefix to install data files')
|
||||||
|
opts, _ = parser.parse_known_args(sys.argv)
|
||||||
|
prefix = opts.prefix or sys.prefix or '/usr'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -24,7 +39,7 @@ test_requirements = [
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='git_linter',
|
name='git_linter',
|
||||||
version='0.0.4',
|
version='0.0.7',
|
||||||
description="A git command to lint everything in your workspace (or stage) that was changed since the last commit.",
|
description="A git command to lint everything in your workspace (or stage) that was changed since the last commit.",
|
||||||
long_description=readme + '\n\n' + history,
|
long_description=readme + '\n\n' + history,
|
||||||
author='Kenneth M. "Elf" Sternberg',
|
author='Kenneth M. "Elf" Sternberg',
|
||||||
|
@ -36,9 +51,11 @@ setup(
|
||||||
package_dir={'git_lint':
|
package_dir={'git_lint':
|
||||||
'git_lint'},
|
'git_lint'},
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
|
data_files=get_data_files(prefix),
|
||||||
install_requires=requirements,
|
install_requires=requirements,
|
||||||
license="MIT",
|
license="MIT",
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
|
scripts=['bin/git-lint-style'],
|
||||||
keywords='git lint style syntaxt development',
|
keywords='git lint style syntaxt development',
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
|
|
|
@ -102,6 +102,17 @@ def test_02_empty_repository():
|
||||||
assert stderr.startswith('No configuration file found,')
|
assert stderr.startswith('No configuration file found,')
|
||||||
|
|
||||||
|
|
||||||
|
# It should behave well when the repository is empty and we're
|
||||||
|
# running against staging.
|
||||||
|
|
||||||
|
def test_02b_empty_repository():
|
||||||
|
with gittemp() as path:
|
||||||
|
os.chdir(path)
|
||||||
|
shell('git init')
|
||||||
|
(stdout, stderr, rc) = fullshell('git lint -s')
|
||||||
|
assert stderr.startswith('No configuration file found,')
|
||||||
|
|
||||||
|
|
||||||
def test_03_simple_repository():
|
def test_03_simple_repository():
|
||||||
with gittemp() as path:
|
with gittemp() as path:
|
||||||
os.chdir(path)
|
os.chdir(path)
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
#!/usr/bin/hy ; -*- mode: clojure -*-
|
|
||||||
(import subprocess os sys os.path)
|
|
||||||
|
|
||||||
(defn fmit [formatstring iterable]
|
|
||||||
(apply .format (+ [formatstring] (list iterable))))
|
|
||||||
|
|
||||||
(defmain [&rest args]
|
|
||||||
(let [[git_dir (subprocess.check_output ["git" "rev-parse" "--show-toplevel"])]]
|
|
||||||
(.append sys.path (os.path.join git_dir "git_lint"))
|
|
||||||
(import [git_lint [git_lint]])
|
|
||||||
(print (.join "\n" (map (fn [i] (if (get i 2)
|
|
||||||
(+ (fmit "\item[\oOptArg{{-{0}}}{{ names}} " i)
|
|
||||||
(fmit "\oOptArg{{--{1}}}={{ names}}] " i)
|
|
||||||
(fmit "{3}" i))
|
|
||||||
(+ (fmit "\item[\oOptArg{{-{0}}} " i)
|
|
||||||
(fmit "\oOptArg{{--{1}}}] " i)
|
|
||||||
(fmit "{3}" i))))
|
|
||||||
git_lint.OPTIONS_LIST)))))
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue