Merge pull request #6 from CalthorpeAnalytics/bobby-pull-from-master

Update to Latest upstream.
This commit is contained in:
bobbyrullo 2018-06-13 16:11:21 -07:00 committed by GitHub
commit 0931931b2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
81 changed files with 2170 additions and 677 deletions

View file

@ -5,11 +5,12 @@ source =
omit = omit =
.tox/* .tox/*
/usr/* /usr/*
*/tmp*
setup.py setup.py
get-git-lfs.py get-git-lfs.py
[report] [report]
show_missing = True
skip_covered = True
exclude_lines = exclude_lines =
# Have to re-enable the standard pragma # Have to re-enable the standard pragma
\#\s*pragma: no cover \#\s*pragma: no cover

3
.gitignore vendored
View file

@ -2,6 +2,7 @@
*.iml *.iml
*.py[co] *.py[co]
.*.sw[a-z] .*.sw[a-z]
.pytest_cache
.coverage .coverage
.idea .idea
.project .project
@ -11,3 +12,5 @@
/venv* /venv*
coverage-html coverage-html
dist dist
# SublimeText project/workspace files
*.sublime-*

View file

@ -1,5 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
sha: v0.7.0 rev: v1.3.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
@ -13,12 +14,19 @@
- id: requirements-txt-fixer - id: requirements-txt-fixer
- id: flake8 - id: flake8
- repo: https://github.com/pre-commit/pre-commit - repo: https://github.com/pre-commit/pre-commit
sha: v0.12.2 rev: v1.7.0
hooks: hooks:
- id: validate_config
- id: validate_manifest - id: validate_manifest
- repo: https://github.com/asottile/reorder_python_imports - repo: https://github.com/asottile/reorder_python_imports
sha: v0.3.1 rev: v1.0.1
hooks: hooks:
- id: reorder-python-imports - id: reorder-python-imports
language_version: python2.7 language_version: python2.7
- repo: https://github.com/asottile/pyupgrade
rev: v1.2.0
hooks:
- id: pyupgrade
- repo: https://github.com/asottile/add-trailing-comma
rev: v0.6.4
hooks:
- id: add-trailing-comma

View file

@ -3,147 +3,263 @@
description: "Runs autopep8 over python source. If you configure additional arguments you'll want to at least include -i." description: "Runs autopep8 over python source. If you configure additional arguments you'll want to at least include -i."
entry: autopep8-wrapper entry: autopep8-wrapper
language: python language: python
files: \.py$ types: [python]
args: [-i] args: [-i]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: check-added-large-files - id: check-added-large-files
name: Check for added large files name: Check for added large files
description: Prevent giant files from being committed description: Prevent giant files from being committed
entry: check-added-large-files entry: check-added-large-files
language: python language: python
# Match all files # for backward compatibility
files: '' files: ''
minimum_pre_commit_version: 0.15.0
- id: check-ast - id: check-ast
name: Check python ast name: Check python ast
description: Simply check whether the files parse as valid python. description: Simply check whether the files parse as valid python.
entry: check-ast entry: check-ast
language: python language: python
files: '\.py$' types: [python]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: check-byte-order-marker - id: check-byte-order-marker
name: Check for byte-order marker name: Check for byte-order marker
description: Forbid files which have a UTF-8 byte-order marker description: Forbid files which have a UTF-8 byte-order marker
entry: check-byte-order-marker entry: check-byte-order-marker
language: python language: python
files: '\.py$' types: [python]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: check-builtin-literals
name: Check builtin type constructor use
description: Require literal syntax when initializing empty or zero Python builtin types.
entry: check-builtin-literals
language: python
types: [python]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: check-case-conflict - id: check-case-conflict
name: Check for case conflicts name: Check for case conflicts
description: Check for files that would conflict in case-insensitive filesystems description: Check for files that would conflict in case-insensitive filesystems
entry: check-case-conflict entry: check-case-conflict
language: python language: python
# Match all files # for backward compatibility
files: '' files: ''
minimum_pre_commit_version: 0.15.0
- id: check-docstring-first - id: check-docstring-first
name: Check docstring is first name: Check docstring is first
description: Checks a common error of defining a docstring after code. description: Checks a common error of defining a docstring after code.
entry: check-docstring-first entry: check-docstring-first
language: python language: python
files: \.py$ types: [python]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: check-executables-have-shebangs
name: Check that executables have shebangs
description: Ensures that (non-binary) executables have a shebang.
entry: check-executables-have-shebangs
language: python
types: [text, executable]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: check-json - id: check-json
name: Check JSON name: Check JSON
description: This hook checks json files for parseable syntax. description: This hook checks json files for parseable syntax.
entry: check-json entry: check-json
language: python language: python
files: \.json$ types: [json]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: pretty-format-json - id: pretty-format-json
name: Pretty format JSON name: Pretty format JSON
description: This hook sets a standard for formatting JSON files. description: This hook sets a standard for formatting JSON files.
entry: pretty-format-json entry: pretty-format-json
language: python language: python
files: \.json$ types: [json]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: check-merge-conflict - id: check-merge-conflict
name: Check for merge conflicts name: Check for merge conflicts
description: Check for files that contain merge conflict strings. description: Check for files that contain merge conflict strings.
entry: check-merge-conflict entry: check-merge-conflict
language: python language: python
# Match all files types: [text]
# for backward compatibility
files: '' files: ''
minimum_pre_commit_version: 0.15.0
- id: check-symlinks - id: check-symlinks
name: Check for broken symlinks name: Check for broken symlinks
description: Checks for symlinks which do not point to anything. description: Checks for symlinks which do not point to anything.
entry: check-symlinks entry: check-symlinks
language: python language: python
# Match all files types: [symlink]
# for backward compatibility
files: '' files: ''
minimum_pre_commit_version: 0.15.0
- id: check-vcs-permalinks
name: Check vcs permalinks
description: Ensures that links to vcs websites are permalinks.
entry: check-vcs-permalinks
language: python
types: [text]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: check-xml - id: check-xml
name: Check Xml name: Check Xml
description: This hook checks xml files for parseable syntax. description: This hook checks xml files for parseable syntax.
entry: check-xml entry: check-xml
language: python language: python
files: \.xml$ types: [xml]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: check-yaml - id: check-yaml
name: Check Yaml name: Check Yaml
description: This hook checks yaml files for parseable syntax. description: This hook checks yaml files for parseable syntax.
entry: check-yaml entry: check-yaml
language: python language: python
files: \.(yaml|yml|eyaml)$ types: [yaml]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: debug-statements - id: debug-statements
name: Debug Statements (Python) name: Debug Statements (Python)
description: This hook checks that debug statements (pdb, ipdb, pudb) are not imported on commit. description: Check for debugger imports and py37+ `breakpoint()` calls in python source.
entry: debug-statement-hook entry: debug-statement-hook
language: python language: python
files: \.py$ types: [python]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: detect-aws-credentials - id: detect-aws-credentials
name: Detect AWS Credentials name: Detect AWS Credentials
description: Detects *your* aws credentials from the aws cli credentials file description: Detects *your* aws credentials from the aws cli credentials file
entry: detect-aws-credentials entry: detect-aws-credentials
language: python language: python
types: [text]
# for backward compatibility
files: '' files: ''
minimum_pre_commit_version: 0.15.0
- id: detect-private-key - id: detect-private-key
name: Detect Private Key name: Detect Private Key
description: Detects the presence of private keys description: Detects the presence of private keys
entry: detect-private-key entry: detect-private-key
language: python language: python
types: [text]
# for backward compatibility
files: '' files: ''
minimum_pre_commit_version: 0.15.0
- id: double-quote-string-fixer - id: double-quote-string-fixer
name: Fix double quoted strings name: Fix double quoted strings
description: This hook replaces double quoted strings with single quoted strings description: This hook replaces double quoted strings with single quoted strings
entry: double-quote-string-fixer entry: double-quote-string-fixer
language: python language: python
files: \.py$ types: [python]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: end-of-file-fixer - id: end-of-file-fixer
name: Fix End of Files name: Fix End of Files
description: Ensures that a file is either empty, or ends with one newline. description: Ensures that a file is either empty, or ends with one newline.
entry: end-of-file-fixer entry: end-of-file-fixer
language: python language: python
files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|groovy|h|haml|hh|hpp|hxx|html|in|j2|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scala|scss|sh|slim|tex|tmpl|ts|txt|yaml|yml)$ types: [text]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: file-contents-sorter
name: File Contents Sorter
description: Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input in your .pre-commit-config.yaml file.
entry: file-contents-sorter
language: python
files: '^$'
- id: fix-encoding-pragma - id: fix-encoding-pragma
name: Fix python encoding pragma name: Fix python encoding pragma
language: python language: python
entry: fix-encoding-pragma entry: fix-encoding-pragma
description: 'Add # -*- coding: utf-8 -*- to the top of python files' description: 'Add # -*- coding: utf-8 -*- to the top of python files'
files: \.py$ types: [python]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: flake8 - id: flake8
name: Flake8 name: Flake8
description: This hook runs flake8. description: This hook runs flake8.
entry: flake8 entry: flake8
language: python language: python
files: \.py$ types: [python]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: forbid-new-submodules - id: forbid-new-submodules
name: Forbid new submodules name: Forbid new submodules
language: python language: python
entry: forbid-new-submodules entry: forbid-new-submodules
description: Prevent addition of new git submodules description: Prevent addition of new git submodules
# for backward compatibility
files: '' files: ''
minimum_pre_commit_version: 0.15.0
- id: mixed-line-ending
name: Mixed line ending
description: Replaces or checks mixed line ending
entry: mixed-line-ending
language: python
types: [text]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: name-tests-test - id: name-tests-test
name: Tests should end in _test.py name: Tests should end in _test.py
description: This verifies that test files are named correctly description: This verifies that test files are named correctly
entry: name-tests-test entry: name-tests-test
language: python language: python
files: tests/.+\.py$ files: (^|/)tests/.+\.py$
- id: no-commit-to-branch
name: "Don't commit to branch"
entry: no-commit-to-branch
language: python
pass_filenames: false
always_run: true
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: pyflakes - id: pyflakes
name: Pyflakes (DEPRECATED, use flake8) name: Pyflakes (DEPRECATED, use flake8)
description: This hook runs pyflakes. (This is deprecated, use flake8). description: This hook runs pyflakes. (This is deprecated, use flake8).
entry: pyflakes entry: pyflakes
language: python language: python
files: \.py$ types: [python]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0
- id: requirements-txt-fixer - id: requirements-txt-fixer
name: Fix requirements.txt name: Fix requirements.txt
description: Sorts entries in requirements.txt description: Sorts entries in requirements.txt
entry: requirements-txt-fixer entry: requirements-txt-fixer
language: python language: python
files: requirements.*\.txt$ files: requirements.*\.txt$
- id: sort-simple-yaml
name: Sort simple YAML files
language: python
entry: sort-simple-yaml
description: Sorts simple YAML files which consist only of top-level keys, preserving comments and blocks.
files: '^$'
- id: trailing-whitespace - id: trailing-whitespace
name: Trim Trailing Whitespace name: Trim Trailing Whitespace
description: This hook trims trailing whitespace. description: This hook trims trailing whitespace.
entry: trailing-whitespace-fixer entry: trailing-whitespace-fixer
language: python language: python
files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|groovy|h|haml|hh|hpp|hxx|html|in|j2|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scala|scss|sh|slim|tex|tmpl|ts|txt|yaml|yml)$ types: [text]
# for backward compatibility
files: ''
minimum_pre_commit_version: 0.15.0

View file

@ -8,8 +8,8 @@ matrix:
- env: TOXENV=py36 - env: TOXENV=py36
python: 3.6 python: 3.6
- env: TOXENV=pypy - env: TOXENV=pypy
#install: pip install coveralls tox python: pypy-5.7.1
install: pip install tox install: pip install coveralls tox
script: tox script: tox
before_install: before_install:
# Install git-lfs for a test # Install git-lfs for a test
@ -18,5 +18,5 @@ after_success: coveralls
cache: cache:
directories: directories:
- $HOME/.cache/pip - $HOME/.cache/pip
- $HOME/.pre-commit - $HOME/.cache/pre-commit
- /tmp/git-lfs - /tmp/git-lfs

View file

@ -1,86 +0,0 @@
0.7.1
=====
- Don't false positive on files where trailing whitespace isn't changed.
0.7.0
=====
- Improve search for detecting aws keys
- Add .pre-commit-hooks.yaml for forward compatibility
0.6.1
=====
- trailing-whitespace-hook: restore original file on catastrophic failure
- trailing-whitespace-hook: support crlf
- check-yaml: Use safe_load
- check-json: allow custom key sort
- check-json: display filename for non-utf8 files
- New hook: forbid-new-submodules
0.6.0
=====
- Merge conflict detection no longer crashes on binary files
- Indentation in json may be an arbitrary separator
- Editable requirements are properly sorted
- Encoding pragma fixer pragma is configurable
0.5.1
=====
- Add a --no-sort-keys to json pretty formatter
- Add a --remove to fix-encoding-pragma
0.5.0
=====
- Add check-byte-order-marker
- Add check-synlinks
- check-large-files-added understands git-lfs
- Support older git
- Fix regex for --django in test name checker
- Add fix-encoding-pragma hook
- requirements-txt-fixer now sorts like latest pip
- Add check-ast hook
- Add detect-aws-credentials hook
- Allow binary files to pass private key hook
- Add pretty-format-json hook
0.4.2
=====
- Add --django to test name checker
- Add check-merge-conflict hook
- Remove dependency on plumbum
- Add q as a debug statement
- Don't detect markup titles as conflicts
- Teach trailing-whitespace about markdown
- Quickfix for pyflakes - flake8 version conflict
0.4.1
=====
- Respect configuration when running autopep8
- Quickfix for pep8 version conflicts
0.4.0
=====
- Fix trailing-whitespace on OS X
- Add check-added-large-files hook
- Add check-docstring-first hook
- Add requirements-txt-fixer hook
- Add check-case-conflict hook
- Use yaml's CLoader when available in check-yaml for more speed
- Add check-xml hook
- Fix end-of-file-fixer for windows
- Add double-quote-string-fixer hook
0.3.0
=====
- Add autopep8-wrapper hook
0.2.0
=====
- Add check-json hook
0.1.1
=====
- Don't crash on non-parseable files for debug-statement-hook
0.1.0
=====
- Initial Release

223
CHANGELOG.md Normal file
View file

@ -0,0 +1,223 @@
1.3.0
=====
### Features
- Add an `--unsafe` argument to `check-yaml` to allow custom yaml tags
- #273 issue by @blackillzone.
- #274 PR by @asottile.
- Automatically remove `pkg-resources==0.0.0` in `requirements-txt-fixer`
- #275 PR by @nvtkaszpir.
- Detect `breakpoint()` (python3.7+) in `debug-statements` hook.
- #283 PR by @asottile.
- Detect sshcom and putty hooks in `detect-private-key`
- #287 PR by @vin01.
### Fixes
- Open files as UTF-8 (`autopep8-wrapper`, `check-docstring-first`,
`double-quote-string-fixer`)
- #279 PR by @nvtkaszpir.
- Fix `AttributeError` in `check-builtin-literals` for some functions
- #285 issue by @EgoWumpus.
- #286 PR by @asottile.
1.2.3
=====
### Fixes
- `trailing-whitespace` entrypoint was incorrect.
- f6780b9 by @asottile.
1.2.2
=====
### Fixes
- `trailing-whitespace` no longer adds a missing newline at end-of-file
- #270 issue by @fractos.
- #271 PR by @asottile.
1.2.1-1
=======
(Note: this is a tag-only release as no code changes occurred)
### Fixes:
- Don't pass filenames for `no-commit-to-branch`
- #268 issue by @dongyuzheng.
- #269 PR by @asottile.
1.2.1
=====
### Fixes:
- `detect-aws-credentials` false positive when key was empty
- #258 issue by @PVSec.
- #260 PR by @PVSec.
- `no-commit-to-branch` no longer crashes when not on a branch
- #265 issue by @hectorv.
- #266 PR by @asottile.
1.2.0
=====
### Features:
- Add new `check-builtin-literals` hook.
- #249 #251 PR by @benwebber.
- `pretty-format-json` no longer depends on `simplejson`.
- #254 PR by @cas--.
- `detect-private-key` now detects gcp keys.
- #255 issue by @SaMnCo @nicain.
- #256 PR by @nicain.
1.1.1
=====
### Fixes:
- Fix output interleaving in `check-vcs-permalinks` under python3.
- #245 PR by @asottile.
1.1.0
=====
### Features:
- `check-yaml` gains a `--allow-multiple-documents` (`-m`) argument to allow
linting of files using the
[multi document syntax](http://www.yaml.org/spec/1.2/spec.html#YAML)
- pre-commit/pre-commit#635 issue by @geekobi.
- #244 PR by @asottile.
1.0.0
=====
### Features:
- New hook: `check-vcs-permalinks` for ensuring permalinked github urls.
- #241 PR by @asottile.
### Fixes:
- Fix `trailing-whitespace` for non-utf8 files on macos
- #242 PR by @asottile.
- Fix `requirements-txt-fixer` for files ending in comments
- #243 PR by @asottile.
0.9.5
=====
- Fix mixed-line-endings `--fix=...` when whole file is a different ending
0.9.4
=====
- Fix entry point for `mixed-line-ending`
0.9.3
=====
- New hook: `mixed-line-ending`
0.9.2
=====
- Report full python version in `check-ast`.
- Apply a more strict regular expression for `name-tests-test`
- Upgrade binding for `git-lfs` for `check-added-large-files`. The oldest
version that is supported is 2.2.1 (2.2.0 will incorrectly refer to all
files as "lfs" (false negative) and earlier versions will crash.
- `debug-statements` now works for non-utf-8 files.
0.9.1
=====
- Add `check-executables-have-shebangs` hook.
0.9.0
=====
- Add `sort-simple-yaml` hook
- Fix `requirements-txt-fixer` for empty files
- Add `file-contents-sorter` hook for sorting flat files
- `check-merge-conflict` now recognizes rebase conflicts
- Metadata now uses `types` (and therefore requires pre-commit 0.15.0). This
allows the text processing hooks to match *all* text files (and to match
files which would only be classifiable by their shebangs).
0.8.0
=====
- Add flag allowing missing keys to `detect-aws-credentials`
- Handle django default `tests.py` in `name-tests-test`
- Add `--no-ensure-ascii` option to `pretty-format-json`
- Add `no-commit-to-branch` hook
0.7.1
=====
- Don't false positive on files where trailing whitespace isn't changed.
0.7.0
=====
- Improve search for detecting aws keys
- Add .pre-commit-hooks.yaml for forward compatibility
0.6.1
=====
- trailing-whitespace-hook: restore original file on catastrophic failure
- trailing-whitespace-hook: support crlf
- check-yaml: Use safe_load
- check-json: allow custom key sort
- check-json: display filename for non-utf8 files
- New hook: forbid-new-submodules
0.6.0
=====
- Merge conflict detection no longer crashes on binary files
- Indentation in json may be an arbitrary separator
- Editable requirements are properly sorted
- Encoding pragma fixer pragma is configurable
0.5.1
=====
- Add a --no-sort-keys to json pretty formatter
- Add a --remove to fix-encoding-pragma
0.5.0
=====
- Add check-byte-order-marker
- Add check-synlinks
- check-large-files-added understands git-lfs
- Support older git
- Fix regex for --django in test name checker
- Add fix-encoding-pragma hook
- requirements-txt-fixer now sorts like latest pip
- Add check-ast hook
- Add detect-aws-credentials hook
- Allow binary files to pass private key hook
- Add pretty-format-json hook
0.4.2
=====
- Add --django to test name checker
- Add check-merge-conflict hook
- Remove dependency on plumbum
- Add q as a debug statement
- Don't detect markup titles as conflicts
- Teach trailing-whitespace about markdown
- Quickfix for pyflakes - flake8 version conflict
0.4.1
=====
- Respect configuration when running autopep8
- Quickfix for pep8 version conflicts
0.4.0
=====
- Fix trailing-whitespace on OS X
- Add check-added-large-files hook
- Add check-docstring-first hook
- Add requirements-txt-fixer hook
- Add check-case-conflict hook
- Use yaml's CLoader when available in check-yaml for more speed
- Add check-xml hook
- Fix end-of-file-fixer for windows
- Add double-quote-string-fixer hook
0.3.0
=====
- Add autopep8-wrapper hook
0.2.0
=====
- Add check-json hook
0.1.1
=====
- Don't crash on non-parseable files for debug-statement-hook
0.1.0
=====
- Initial Release

View file

@ -1,24 +0,0 @@
REBUILD_FLAG =
.PHONY: all
all: venv test
.PHONY: venv
venv: .venv.touch
tox -e venv $(REBUILD_FLAG)
.PHONY: tests test
tests: test
test: .venv.touch
tox $(REBUILD_FLAG)
.venv.touch: setup.py requirements-dev.txt
$(eval REBUILD_FLAG := --recreate)
touch .venv.touch
.PHONY: clean
clean:
find . -name '*.pyc' -delete
rm -rf .tox
rm -rf ./venv-*
rm -f .venv.touch

View file

@ -14,8 +14,8 @@ See also: https://github.com/pre-commit/pre-commit
Add this to your `.pre-commit-config.yaml` Add this to your `.pre-commit-config.yaml`
- repo: git://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
sha: v0.7.1 # Use the ref you want to point at rev: v1.3.0 # Use the ref you want to point at
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
# - id: ... # - id: ...
@ -25,21 +25,40 @@ Add this to your `.pre-commit-config.yaml`
- `autopep8-wrapper` - Runs autopep8 over python source. - `autopep8-wrapper` - Runs autopep8 over python source.
- Ignore PEP 8 violation types with `args: ['-i', '--ignore=E000,...']` or - Ignore PEP 8 violation types with `args: ['-i', '--ignore=E000,...']` or
through configuration of the `[pep8]` section in setup.cfg / tox.ini. through configuration of the `[pycodestyle]` section in
setup.cfg / tox.ini.
- `check-added-large-files` - Prevent giant files from being committed. - `check-added-large-files` - Prevent giant files from being committed.
- Specify what is "too large" with `args: ['--maxkb=123']` (default=500kB). - Specify what is "too large" with `args: ['--maxkb=123']` (default=500kB).
- If `git-lfs` is installed, lfs files will be skipped
(requires `git-lfs>=2.2.1`)
- `check-ast` - Simply check whether files parse as valid python. - `check-ast` - Simply check whether files parse as valid python.
- `check-builtin-literals` - Require literal syntax when initializing empty or zero Python builtin types.
- Allows calling constructors with positional arguments (e.g., `list('abc')`).
- Allows calling constructors from the `builtins` (`__builtin__`) namespace (`builtins.list()`).
- Ignore this requirement for specific builtin types with `--ignore=type1,type2,…`.
- Forbid `dict` keyword syntax with `--no-allow-dict-kwargs`.
- `check-byte-order-marker` - Forbid files which have a UTF-8 byte-order marker - `check-byte-order-marker` - Forbid files which have a UTF-8 byte-order marker
- `check-case-conflict` - Check for files with names that would conflict on a - `check-case-conflict` - Check for files with names that would conflict on a
case-insensitive filesystem like MacOS HFS+ or Windows FAT. case-insensitive filesystem like MacOS HFS+ or Windows FAT.
- `check-docstring-first` - Checks for a common error of placing code before - `check-docstring-first` - Checks for a common error of placing code before
the docstring. the docstring.
- `check-executables-have-shebangs` - Checks that non-binary executables have a
proper shebang.
- `check-json` - Attempts to load all json files to verify syntax. - `check-json` - Attempts to load all json files to verify syntax.
- `check-merge-conflict` - Check for files that contain merge conflict strings. - `check-merge-conflict` - Check for files that contain merge conflict strings.
- `check-symlinks` - Checks for symlinks which do not point to anything. - `check-symlinks` - Checks for symlinks which do not point to anything.
- `check-vcs-permalinks` - Ensures that links to vcs websites are permalinks.
- `check-xml` - Attempts to load all xml files to verify syntax. - `check-xml` - Attempts to load all xml files to verify syntax.
- `check-yaml` - Attempts to load all yaml files to verify syntax. - `check-yaml` - Attempts to load all yaml files to verify syntax.
- `debug-statements` - Check for pdb / ipdb / pudb statements in code. - `--allow-multiple-documents` - allow yaml files which use the
[multi-document syntax](http://www.yaml.org/spec/1.2/spec.html#YAML)
- `--unsafe` - Instead of loading the files, simply parse them for syntax.
A syntax-only check enables extensions and unsafe constructs which would
otherwise be forbidden. Using this option removes all guarantees of
portability to other yaml implementations.
Implies `--allow-multiple-documents`.
- `debug-statements` - Check for debugger imports and py37+ `breakpoint()`
calls in python source.
- `detect-aws-credentials` - Checks for the existence of AWS secrets that you - `detect-aws-credentials` - Checks for the existence of AWS secrets that you
have set up with the AWS CLI. have set up with the AWS CLI.
The following arguments are available: The following arguments are available:
@ -52,10 +71,21 @@ Add this to your `.pre-commit-config.yaml`
- `end-of-file-fixer` - Makes sure files end in a newline and only a newline. - `end-of-file-fixer` - Makes sure files end in a newline and only a newline.
- `fix-encoding-pragma` - Add `# -*- coding: utf-8 -*-` to the top of python files. - `fix-encoding-pragma` - Add `# -*- coding: utf-8 -*-` to the top of python files.
- To remove the coding pragma pass `--remove` (useful in a python3-only codebase) - To remove the coding pragma pass `--remove` (useful in a python3-only codebase)
- `file-contents-sorter` - Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input to it. Note that this hook WILL remove blank lines and does NOT respect any comments.
- `flake8` - Run flake8 on your python files. - `flake8` - Run flake8 on your python files.
- `forbid-new-submodules` - Prevent addition of new git submodules. - `forbid-new-submodules` - Prevent addition of new git submodules.
- `mixed-line-ending` - Replaces or checks mixed line ending.
- `--fix={auto,crlf,lf,no}`
- `auto` - Replaces automatically the most frequent line ending. This is the default argument.
- `crlf`, `lf` - Forces to replace line ending by respectively CRLF and LF.
- `no` - Checks if there is any mixed line ending without modifying any file.
- `name-tests-test` - Assert that files in tests/ end in `_test.py`. - `name-tests-test` - Assert that files in tests/ end in `_test.py`.
- Use `args: ['--django']` to match `test*.py` instead. - Use `args: ['--django']` to match `test*.py` instead.
- `no-commit-to-branch` - Protect specific branches from direct checkins.
- Use `args: [--branch <branch>]` to set the branch. `master` is the
default if no argument is set.
- `-b` / `--branch` may be specified multiple times to protect multiple
branches.
- `pyflakes` - Run pyflakes on your python files. - `pyflakes` - Run pyflakes on your python files.
- `pretty-format-json` - Checks that all your JSON files are pretty. "Pretty" - `pretty-format-json` - Checks that all your JSON files are pretty. "Pretty"
here means that keys are sorted and indented. You can configure this with here means that keys are sorted and indented. You can configure this with
@ -64,7 +94,8 @@ Add this to your `.pre-commit-config.yaml`
- `--indent ...` - Control the indentation (either a number for a number of spaces or a string of whitespace). Defaults to 4 spaces. - `--indent ...` - Control the indentation (either a number for a number of spaces or a string of whitespace). Defaults to 4 spaces.
- `--no-sort-keys` - when autofixing, retain the original key ordering (instead of sorting the keys) - `--no-sort-keys` - when autofixing, retain the original key ordering (instead of sorting the keys)
- `--top-keys comma,separated,keys` - Keys to keep at the top of mappings. - `--top-keys comma,separated,keys` - Keys to keep at the top of mappings.
- `requirements-txt-fixer` - Sorts entries in requirements.txt - `requirements-txt-fixer` - Sorts entries in requirements.txt and removes incorrect entry for `pkg-resources==0.0.0`
- `sort-simple-yaml` - Sorts simple YAML files which consist only of top-level keys, preserving comments and blocks.
- `trailing-whitespace` - Trims trailing whitespace. - `trailing-whitespace` - Trims trailing whitespace.
- Markdown linebreak trailing spaces preserved for `.md` and`.markdown`; - Markdown linebreak trailing spaces preserved for `.md` and`.markdown`;
use `args: ['--markdown-linebreak-ext=txt,text']` to add other extensions, use `args: ['--markdown-linebreak-ext=txt,text']` to add other extensions,

View file

@ -14,4 +14,4 @@ test_script: tox
cache: cache:
- '%LOCALAPPDATA%\pip\cache' - '%LOCALAPPDATA%\pip\cache'
- '%USERPROFILE%\.pre-commit' - '%USERPROFILE%\.cache\pre-commit'

View file

@ -8,9 +8,9 @@ from urllib.request import urlopen
DOWNLOAD_PATH = ( DOWNLOAD_PATH = (
'https://github.com/github/git-lfs/releases/download/' 'https://github.com/github/git-lfs/releases/download/'
'v1.1.0/git-lfs-linux-amd64-1.1.0.tar.gz' 'v2.2.1/git-lfs-linux-amd64-2.2.1.tar.gz'
) )
PATH_IN_TAR = 'git-lfs-1.1.0/git-lfs' PATH_IN_TAR = 'git-lfs-2.2.1/git-lfs'
DEST_PATH = '/tmp/git-lfs/git-lfs' DEST_PATH = '/tmp/git-lfs/git-lfs'
DEST_DIR = os.path.dirname(DEST_PATH) DEST_DIR = os.path.dirname(DEST_PATH)

View file

@ -1,149 +1,186 @@
- id: autopep8-wrapper - id: autopep8-wrapper
name: autopep8 wrapper language: system
description: "Runs autopep8 over python source. If you configure additional arguments you'll want to at least include -i." name: upgrade-your-pre-commit-version
entry: autopep8-wrapper entry: upgrade-your-pre-commit-version
language: python files: ''
files: \.py$ minimum_pre_commit_version: 0.15.0
args: [-i]
- id: check-added-large-files - id: check-added-large-files
name: Check for added large files language: system
description: Prevent giant files from being committed name: upgrade-your-pre-commit-version
entry: check-added-large-files entry: upgrade-your-pre-commit-version
language: python
# Match all files
files: '' files: ''
minimum_pre_commit_version: 0.15.0
- id: check-ast - id: check-ast
name: Check python ast language: system
description: Simply check whether the files parse as valid python. name: upgrade-your-pre-commit-version
entry: check-ast entry: upgrade-your-pre-commit-version
language: python files: ''
files: '\.py$' minimum_pre_commit_version: 0.15.0
- id: check-builtin-literals
language: system
name: upgrade-your-pre-commit-version
entry: upgrade-your-pre-commit-version
files: ''
minimum_pre_commit_version: 0.15.0
- id: check-byte-order-marker - id: check-byte-order-marker
name: Check for byte-order marker language: system
description: Forbid files which have a UTF-8 byte-order marker name: upgrade-your-pre-commit-version
entry: check-byte-order-marker entry: upgrade-your-pre-commit-version
language: python files: ''
files: '\.py$' minimum_pre_commit_version: 0.15.0
- id: check-case-conflict - id: check-case-conflict
name: Check for case conflicts language: system
description: Check for files that would conflict in case-insensitive filesystems name: upgrade-your-pre-commit-version
entry: check-case-conflict entry: upgrade-your-pre-commit-version
language: python
# Match all files
files: '' files: ''
minimum_pre_commit_version: 0.15.0
- id: check-docstring-first - id: check-docstring-first
name: Check docstring is first language: system
description: Checks a common error of defining a docstring after code. name: upgrade-your-pre-commit-version
entry: check-docstring-first entry: upgrade-your-pre-commit-version
language: python files: ''
files: \.py$ minimum_pre_commit_version: 0.15.0
- id: check-executables-have-shebangs
language: system
name: upgrade-your-pre-commit-version
entry: upgrade-your-pre-commit-version
files: ''
minimum_pre_commit_version: 0.15.0
- id: check-json - id: check-json
name: Check JSON language: system
description: This hook checks json files for parseable syntax. name: upgrade-your-pre-commit-version
entry: check-json entry: upgrade-your-pre-commit-version
language: python files: ''
files: \.json$ minimum_pre_commit_version: 0.15.0
- id: pretty-format-json - id: pretty-format-json
name: Pretty format JSON language: system
description: This hook sets a standard for formatting JSON files. name: upgrade-your-pre-commit-version
entry: pretty-format-json entry: upgrade-your-pre-commit-version
language: python files: ''
files: \.json$ minimum_pre_commit_version: 0.15.0
- id: check-merge-conflict - id: check-merge-conflict
name: Check for merge conflicts language: system
description: Check for files that contain merge conflict strings. name: upgrade-your-pre-commit-version
entry: check-merge-conflict entry: upgrade-your-pre-commit-version
language: python
# Match all files
files: '' files: ''
minimum_pre_commit_version: 0.15.0
- id: check-symlinks - id: check-symlinks
name: Check for broken symlinks language: system
description: Checks for symlinks which do not point to anything. name: upgrade-your-pre-commit-version
entry: check-symlinks entry: upgrade-your-pre-commit-version
language: python
# Match all files
files: '' files: ''
minimum_pre_commit_version: 0.15.0
- id: check-vcs-permalinks
language: system
name: upgrade-your-pre-commit-version
entry: upgrade-your-pre-commit-version
files: ''
minimum_pre_commit_version: 0.15.0
- id: check-xml - id: check-xml
name: Check Xml language: system
description: This hook checks xml files for parseable syntax. name: upgrade-your-pre-commit-version
entry: check-xml entry: upgrade-your-pre-commit-version
language: python files: ''
files: \.xml$ minimum_pre_commit_version: 0.15.0
- id: check-yaml - id: check-yaml
name: Check Yaml language: system
description: This hook checks yaml files for parseable syntax. name: upgrade-your-pre-commit-version
entry: check-yaml entry: upgrade-your-pre-commit-version
language: python files: ''
files: \.(yaml|yml|eyaml)$ minimum_pre_commit_version: 0.15.0
- id: debug-statements - id: debug-statements
name: Debug Statements (Python) language: system
description: This hook checks that debug statements (pdb, ipdb, pudb) are not imported on commit. name: upgrade-your-pre-commit-version
entry: debug-statement-hook entry: upgrade-your-pre-commit-version
language: python files: ''
files: \.py$ minimum_pre_commit_version: 0.15.0
- id: detect-aws-credentials - id: detect-aws-credentials
name: Detect AWS Credentials language: system
description: Detects *your* aws credentials from the aws cli credentials file name: upgrade-your-pre-commit-version
entry: detect-aws-credentials entry: upgrade-your-pre-commit-version
language: python
files: '' files: ''
minimum_pre_commit_version: 0.15.0
- id: detect-private-key - id: detect-private-key
name: Detect Private Key language: system
description: Detects the presence of private keys name: upgrade-your-pre-commit-version
entry: detect-private-key entry: upgrade-your-pre-commit-version
language: python
files: '' files: ''
minimum_pre_commit_version: 0.15.0
- id: double-quote-string-fixer - id: double-quote-string-fixer
name: Fix double quoted strings language: system
description: This hook replaces double quoted strings with single quoted strings name: upgrade-your-pre-commit-version
entry: double-quote-string-fixer entry: upgrade-your-pre-commit-version
language: python
files: \.py$
- id: end-of-file-fixer
name: Fix End of Files
description: Ensures that a file is either empty, or ends with one newline.
entry: end-of-file-fixer
language: python
files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|groovy|h|haml|hh|hpp|hxx|html|in|j2|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scala|scss|sh|slim|tex|tmpl|ts|txt|yaml|yml)$
- id: fix-encoding-pragma
name: Fix python encoding pragma
language: python
entry: fix-encoding-pragma
description: 'Add # -*- coding: utf-8 -*- to the top of python files'
files: \.py$
- id: flake8
name: Flake8
description: This hook runs flake8.
entry: flake8
language: python
files: \.py$
- id: forbid-new-submodules
name: Forbid new submodules
language: python
entry: forbid-new-submodules
description: Prevent addition of new git submodules
files: '' files: ''
minimum_pre_commit_version: 0.15.0
- id: end-of-file-fixer
language: system
name: upgrade-your-pre-commit-version
entry: upgrade-your-pre-commit-version
files: ''
minimum_pre_commit_version: 0.15.0
- id: file-contents-sorter
language: system
name: upgrade-your-pre-commit-version
entry: upgrade-your-pre-commit-version
files: ''
minimum_pre_commit_version: 0.15.0
- id: fix-encoding-pragma
language: system
name: upgrade-your-pre-commit-version
entry: upgrade-your-pre-commit-version
files: ''
minimum_pre_commit_version: 0.15.0
- id: flake8
language: system
name: upgrade-your-pre-commit-version
entry: upgrade-your-pre-commit-version
files: ''
minimum_pre_commit_version: 0.15.0
- id: forbid-new-submodules
language: system
name: upgrade-your-pre-commit-version
entry: upgrade-your-pre-commit-version
files: ''
minimum_pre_commit_version: 0.15.0
- id: mixed-line-ending
language: system
name: upgrade-your-pre-commit-version
entry: upgrade-your-pre-commit-version
files: ''
minimum_pre_commit_version: 0.15.0
- id: name-tests-test - id: name-tests-test
name: Tests should end in _test.py language: system
description: This verifies that test files are named correctly name: upgrade-your-pre-commit-version
entry: name-tests-test entry: upgrade-your-pre-commit-version
language: python files: ''
files: tests/.+\.py$ minimum_pre_commit_version: 0.15.0
- id: no-commit-to-branch
language: system
name: upgrade-your-pre-commit-version
entry: upgrade-your-pre-commit-version
files: ''
minimum_pre_commit_version: 0.15.0
- id: pyflakes - id: pyflakes
name: Pyflakes (DEPRECATED, use flake8) language: system
description: This hook runs pyflakes. (This is deprecated, use flake8). name: upgrade-your-pre-commit-version
entry: pyflakes entry: upgrade-your-pre-commit-version
language: python files: ''
files: \.py$ minimum_pre_commit_version: 0.15.0
- id: requirements-txt-fixer - id: requirements-txt-fixer
name: Fix requirements.txt language: system
description: Sorts entries in requirements.txt name: upgrade-your-pre-commit-version
entry: requirements-txt-fixer entry: upgrade-your-pre-commit-version
language: python files: ''
files: requirements.*\.txt$ minimum_pre_commit_version: 0.15.0
- id: sort-simple-yaml
language: system
name: upgrade-your-pre-commit-version
entry: upgrade-your-pre-commit-version
files: ''
minimum_pre_commit_version: 0.15.0
- id: trailing-whitespace - id: trailing-whitespace
name: Trim Trailing Whitespace language: system
description: This hook trims trailing whitespace. name: upgrade-your-pre-commit-version
entry: trailing-whitespace-fixer entry: upgrade-your-pre-commit-version
language: python files: ''
files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|groovy|h|haml|hh|hpp|hxx|html|in|j2|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scala|scss|sh|slim|tex|tmpl|ts|txt|yaml|yml)$ minimum_pre_commit_version: 0.15.0

View file

@ -14,12 +14,12 @@ def main(argv=None):
retv = 0 retv = 0
for filename in args.files: for filename in args.files:
original_contents = io.open(filename).read() original_contents = io.open(filename, encoding='UTF-8').read()
new_contents = autopep8.fix_code(original_contents, args) new_contents = autopep8.fix_code(original_contents, args)
if original_contents != new_contents: if original_contents != new_contents:
print('Fixing {0}'.format(filename)) print('Fixing {}'.format(filename))
retv = 1 retv = 1
with io.open(filename, 'w') as output_file: with io.open(filename, 'w', encoding='UTF-8') as output_file:
output_file.write(new_contents) output_file.write(new_contents)
return retv return retv

View file

@ -4,6 +4,7 @@ from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import argparse import argparse
import json
import math import math
import os import os
@ -13,23 +14,13 @@ from pre_commit_hooks.util import cmd_output
def lfs_files(): def lfs_files():
try: # pragma: no cover (no git-lfs) try:
lines = cmd_output('git', 'lfs', 'status', '--porcelain').splitlines() # Introduced in git-lfs 2.2.0, first working in 2.2.1
lfs_ret = cmd_output('git', 'lfs', 'status', '--json')
except CalledProcessError: # pragma: no cover (with git-lfs) except CalledProcessError: # pragma: no cover (with git-lfs)
lines = [] lfs_ret = '{"files":{}}'
modes_and_fileparts = [ return set(json.loads(lfs_ret)['files'])
(line[:3].strip(), line[3:].rpartition(' ')[0]) for line in lines
]
def to_file_part(mode, filepart): # pragma: no cover (no git-lfs)
assert mode in ('A', 'R')
return filepart if mode == 'A' else filepart.split(' -> ')[1]
return set(
to_file_part(mode, filepart) for mode, filepart in modes_and_fileparts
if mode in ('A', 'R')
)
def find_large_added_files(filenames, maxkb): def find_large_added_files(filenames, maxkb):
@ -41,7 +32,7 @@ def find_large_added_files(filenames, maxkb):
for filename in filenames: for filename in filenames:
kb = int(math.ceil(os.stat(filename).st_size / 1024)) kb = int(math.ceil(os.stat(filename).st_size / 1024))
if kb > maxkb: if kb > maxkb:
print('{0} ({1} KB) exceeds {2} KB.'.format(filename, kb, maxkb)) print('{} ({} KB) exceeds {} KB.'.format(filename, kb, maxkb))
retv = 1 retv = 1
return retv return retv
@ -51,7 +42,7 @@ def main(argv=None):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument( parser.add_argument(
'filenames', nargs='*', 'filenames', nargs='*',
help='Filenames pre-commit believes are changed.' help='Filenames pre-commit believes are changed.',
) )
parser.add_argument( parser.add_argument(
'--maxkb', type=int, default=500, '--maxkb', type=int, default=500,

View file

@ -4,7 +4,7 @@ from __future__ import unicode_literals
import argparse import argparse
import ast import ast
import os.path import platform
import sys import sys
import traceback import traceback
@ -14,19 +14,19 @@ def check_ast(argv=None):
parser.add_argument('filenames', nargs='*') parser.add_argument('filenames', nargs='*')
args = parser.parse_args(argv) args = parser.parse_args(argv)
_, interpreter = os.path.split(sys.executable)
retval = 0 retval = 0
for filename in args.filenames: for filename in args.filenames:
try: try:
ast.parse(open(filename, 'rb').read(), filename=filename) ast.parse(open(filename, 'rb').read(), filename=filename)
except SyntaxError: except SyntaxError:
print('{0}: failed parsing with {1}:'.format( print('{}: failed parsing with {} {}:'.format(
filename, interpreter, filename,
platform.python_implementation(),
sys.version.partition(' ')[0],
)) ))
print('\n{0}'.format( print('\n{}'.format(
' ' + traceback.format_exc().replace('\n', '\n ') ' ' + traceback.format_exc().replace('\n', '\n '),
)) ))
retval = 1 retval = 1
return retval return retval

View file

@ -0,0 +1,95 @@
from __future__ import unicode_literals
import argparse
import ast
import collections
import sys
BUILTIN_TYPES = {
'complex': '0j',
'dict': '{}',
'float': '0.0',
'int': '0',
'list': '[]',
'str': "''",
'tuple': '()',
}
BuiltinTypeCall = collections.namedtuple('BuiltinTypeCall', ['name', 'line', 'column'])
class BuiltinTypeVisitor(ast.NodeVisitor):
def __init__(self, ignore=None, allow_dict_kwargs=True):
self.builtin_type_calls = []
self.ignore = set(ignore) if ignore else set()
self.allow_dict_kwargs = allow_dict_kwargs
def _check_dict_call(self, node):
return self.allow_dict_kwargs and (getattr(node, 'kwargs', None) or getattr(node, 'keywords', None))
def visit_Call(self, node):
if not isinstance(node.func, ast.Name):
# Ignore functions that are object attributes (`foo.bar()`).
# Assume that if the user calls `builtins.list()`, they know what
# they're doing.
return
if node.func.id not in set(BUILTIN_TYPES).difference(self.ignore):
return
if node.func.id == 'dict' and self._check_dict_call(node):
return
elif node.args:
return
self.builtin_type_calls.append(
BuiltinTypeCall(node.func.id, node.lineno, node.col_offset),
)
def check_file_for_builtin_type_constructors(filename, ignore=None, allow_dict_kwargs=True):
tree = ast.parse(open(filename, 'rb').read(), filename=filename)
visitor = BuiltinTypeVisitor(ignore=ignore, allow_dict_kwargs=allow_dict_kwargs)
visitor.visit(tree)
return visitor.builtin_type_calls
def parse_args(argv):
def parse_ignore(value):
return set(value.split(','))
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
parser.add_argument('--ignore', type=parse_ignore, default=set())
allow_dict_kwargs = parser.add_mutually_exclusive_group(required=False)
allow_dict_kwargs.add_argument('--allow-dict-kwargs', action='store_true')
allow_dict_kwargs.add_argument('--no-allow-dict-kwargs', dest='allow_dict_kwargs', action='store_false')
allow_dict_kwargs.set_defaults(allow_dict_kwargs=True)
return parser.parse_args(argv)
def main(argv=None):
args = parse_args(argv)
rc = 0
for filename in args.filenames:
calls = check_file_for_builtin_type_constructors(
filename,
ignore=args.ignore,
allow_dict_kwargs=args.allow_dict_kwargs,
)
if calls:
rc = rc or 1
for call in calls:
print(
'{filename}:{call.line}:{call.column} - Replace {call.name}() with {replacement}'.format(
filename=filename,
call=call,
replacement=BUILTIN_TYPES[call.name],
),
)
return rc
if __name__ == '__main__':
sys.exit(main())

View file

@ -16,7 +16,7 @@ def main(argv=None):
with open(filename, 'rb') as f: with open(filename, 'rb') as f:
if f.read(3) == b'\xef\xbb\xbf': if f.read(3) == b'\xef\xbb\xbf':
retv = 1 retv = 1
print('{0}: Has a byte-order marker'.format(filename)) print('{}: Has a byte-order marker'.format(filename))
return retv return retv

View file

@ -9,7 +9,7 @@ from pre_commit_hooks.util import cmd_output
def lower_set(iterable): def lower_set(iterable):
return set(x.lower() for x in iterable) return {x.lower() for x in iterable}
def find_conflicting_filenames(filenames): def find_conflicting_filenames(filenames):
@ -35,7 +35,7 @@ def find_conflicting_filenames(filenames):
if x.lower() in conflicts if x.lower() in conflicts
] ]
for filename in sorted(conflicting_files): for filename in sorted(conflicting_files):
print('Case-insensitivity conflict found: {0}'.format(filename)) print('Case-insensitivity conflict found: {}'.format(filename))
retv = 1 retv = 1
return retv return retv
@ -45,7 +45,7 @@ def main(argv=None):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument( parser.add_argument(
'filenames', nargs='*', 'filenames', nargs='*',
help='Filenames pre-commit believes are changed.' help='Filenames pre-commit believes are changed.',
) )
args = parser.parse_args(argv) args = parser.parse_args(argv)

View file

@ -1,4 +1,5 @@
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import argparse import argparse
@ -27,18 +28,18 @@ def check_docstring_first(src, filename='<unknown>'):
if tok_type == tokenize.STRING and scol == 0: if tok_type == tokenize.STRING and scol == 0:
if found_docstring_line is not None: if found_docstring_line is not None:
print( print(
'{0}:{1} Multiple module docstrings ' '{}:{} Multiple module docstrings '
'(first docstring on line {2}).'.format( '(first docstring on line {}).'.format(
filename, sline, found_docstring_line, filename, sline, found_docstring_line,
) ),
) )
return 1 return 1
elif found_code_line is not None: elif found_code_line is not None:
print( print(
'{0}:{1} Module docstring appears after code ' '{}:{} Module docstring appears after code '
'(code seen on line {2}).'.format( '(code seen on line {}).'.format(
filename, sline, found_code_line, filename, sline, found_code_line,
) ),
) )
return 1 return 1
else: else:
@ -57,7 +58,7 @@ def main(argv=None):
retv = 0 retv = 0
for filename in args.filenames: for filename in args.filenames:
contents = io.open(filename).read() contents = io.open(filename, encoding='UTF-8').read()
retv |= check_docstring_first(contents, filename=filename) retv |= check_docstring_first(contents, filename=filename)
return retv return retv

View file

@ -0,0 +1,40 @@
"""Check that executable text files have a shebang."""
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals
import argparse
import pipes
import sys
def check_has_shebang(path):
with open(path, 'rb') as f:
first_bytes = f.read(2)
if first_bytes != b'#!':
print(
'{path}: marked executable but has no (or invalid) shebang!\n'
" If it isn't supposed to be executable, try: chmod -x {quoted}\n"
' If it is supposed to be executable, double-check its shebang.'.format(
path=path,
quoted=pipes.quote(path),
),
file=sys.stderr,
)
return 1
else:
return 0
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('filenames', nargs='*')
args = parser.parse_args(argv)
retv = 0
for filename in args.filenames:
retv |= check_has_shebang(filename)
return retv

View file

@ -1,10 +1,10 @@
from __future__ import print_function from __future__ import print_function
import argparse import argparse
import io
import json
import sys import sys
import simplejson
def check_json(argv=None): def check_json(argv=None):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@ -14,9 +14,9 @@ def check_json(argv=None):
retval = 0 retval = 0
for filename in args.filenames: for filename in args.filenames:
try: try:
simplejson.load(open(filename)) json.load(io.open(filename, encoding='UTF-8'))
except (simplejson.JSONDecodeError, UnicodeDecodeError) as exc: except (ValueError, UnicodeDecodeError) as exc:
print('{0}: Failed to json decode ({1})'.format(filename, exc)) print('{}: Failed to json decode ({})'.format(filename, exc))
retval = 1 retval = 1
return retval return retval

View file

@ -7,7 +7,7 @@ CONFLICT_PATTERNS = [
b'<<<<<<< ', b'<<<<<<< ',
b'======= ', b'======= ',
b'=======\n', b'=======\n',
b'>>>>>>> ' b'>>>>>>> ',
] ]
WARNING_MSG = 'Merge conflict string "{0}" found in {1}:{2}' WARNING_MSG = 'Merge conflict string "{0}" found in {1}:{2}'
@ -15,7 +15,11 @@ WARNING_MSG = 'Merge conflict string "{0}" found in {1}:{2}'
def is_in_merge(): def is_in_merge():
return ( return (
os.path.exists(os.path.join('.git', 'MERGE_MSG')) and os.path.exists(os.path.join('.git', 'MERGE_MSG')) and
os.path.exists(os.path.join('.git', 'MERGE_HEAD')) (
os.path.exists(os.path.join('.git', 'MERGE_HEAD')) or
os.path.exists(os.path.join('.git', 'rebase-apply')) or
os.path.exists(os.path.join('.git', 'rebase-merge'))
)
) )

View file

@ -19,7 +19,7 @@ def check_symlinks(argv=None):
os.path.islink(filename) and os.path.islink(filename) and
not os.path.exists(filename) not os.path.exists(filename)
): # pragma: no cover (symlink support required) ): # pragma: no cover (symlink support required)
print('{0}: Broken symlink'.format(filename)) print('{}: Broken symlink'.format(filename))
retv = 1 retv = 1
return retv return retv

View file

@ -0,0 +1,44 @@
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals
import argparse
import re
import sys
GITHUB_NON_PERMALINK = re.compile(
b'https://github.com/[^/ ]+/[^/ ]+/blob/master/[^# ]+#L\d+',
)
def _check_filename(filename):
retv = 0
with open(filename, 'rb') as f:
for i, line in enumerate(f, 1):
if GITHUB_NON_PERMALINK.search(line):
sys.stdout.write('{}:{}:'.format(filename, i))
sys.stdout.flush()
getattr(sys.stdout, 'buffer', sys.stdout).write(line)
retv = 1
return retv
def main(argv=None):
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
args = parser.parse_args(argv)
retv = 0
for filename in args.filenames:
retv |= _check_filename(filename)
if retv:
print()
print('Non-permanent github link detected.')
print('On any page on github press [y] to load a permalink.')
return retv
if __name__ == '__main__':
exit(main())

View file

@ -19,7 +19,7 @@ def check_xml(argv=None):
with io.open(filename, 'rb') as xml_file: with io.open(filename, 'rb') as xml_file:
xml.sax.parse(xml_file, xml.sax.ContentHandler()) xml.sax.parse(xml_file, xml.sax.ContentHandler())
except xml.sax.SAXException as exc: except xml.sax.SAXException as exc:
print('{0}: Failed to xml parse ({1})'.format(filename, exc)) print('{}: Failed to xml parse ({})'.format(filename, exc))
retval = 1 retval = 1
return retval return retval

View file

@ -1,6 +1,7 @@
from __future__ import print_function from __future__ import print_function
import argparse import argparse
import collections
import sys import sys
import yaml import yaml
@ -11,21 +12,57 @@ except ImportError: # pragma: no cover (no libyaml-dev / pypy)
Loader = yaml.SafeLoader Loader = yaml.SafeLoader
def _exhaust(gen):
for _ in gen:
pass
def _parse_unsafe(*args, **kwargs):
_exhaust(yaml.parse(*args, **kwargs))
def _load_all(*args, **kwargs):
_exhaust(yaml.load_all(*args, **kwargs))
Key = collections.namedtuple('Key', ('multi', 'unsafe'))
LOAD_FNS = {
Key(multi=False, unsafe=False): yaml.load,
Key(multi=False, unsafe=True): _parse_unsafe,
Key(multi=True, unsafe=False): _load_all,
Key(multi=True, unsafe=True): _parse_unsafe,
}
def check_yaml(argv=None): def check_yaml(argv=None):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument(
'-m', '--multi', '--allow-multiple-documents', action='store_true',
)
parser.add_argument(
'--unsafe', action='store_true',
help=(
'Instead of loading the files, simply parse them for syntax. '
'A syntax-only check enables extensions and unsafe contstructs '
'which would otherwise be forbidden. Using this option removes '
'all guarantees of portability to other yaml implementations. '
'Implies --allow-multiple-documents'
),
)
parser.add_argument('--ignore-tags', type=lambda s: s.split(','), default=[], parser.add_argument('--ignore-tags', type=lambda s: s.split(','), default=[],
help='Custom tags to ignore.') help='Custom tags to ignore.')
parser.add_argument('filenames', nargs='*', help='Yaml filenames to check.') parser.add_argument('filenames', nargs='*', help='Yaml filenames to check.')
args = parser.parse_args(argv) args = parser.parse_args(argv)
# Ignore custom tags by returning None
for tag in args.ignore_tags: for tag in args.ignore_tags:
Loader.add_constructor(tag, lambda *a, **k: None) Loader.add_constructor(tag, lambda *a, **k: None)
load_fn = LOAD_FNS[Key(multi=args.multi, unsafe=args.unsafe)]
retval = 0 retval = 0
for filename in args.filenames: for filename in args.filenames:
try: try:
yaml.load(open(filename), Loader=Loader) load_fn(open(filename), Loader=Loader)
except yaml.YAMLError as exc: except yaml.YAMLError as exc:
print(exc) print(exc)
retval = 1 retval = 1

View file

@ -7,69 +7,66 @@ import collections
import traceback import traceback
DEBUG_STATEMENTS = set(['pdb', 'ipdb', 'pudb', 'q', 'rdb']) DEBUG_STATEMENTS = {'pdb', 'ipdb', 'pudb', 'q', 'rdb'}
Debug = collections.namedtuple('Debug', ('line', 'col', 'name', 'reason'))
DebugStatement = collections.namedtuple( class DebugStatementParser(ast.NodeVisitor):
'DebugStatement', ['name', 'line', 'col'],
)
class ImportStatementParser(ast.NodeVisitor):
def __init__(self): def __init__(self):
self.debug_import_statements = [] self.breakpoints = []
def visit_Import(self, node): def visit_Import(self, node):
for node_name in node.names: for name in node.names:
if node_name.name in DEBUG_STATEMENTS: if name.name in DEBUG_STATEMENTS:
self.debug_import_statements.append( st = Debug(node.lineno, node.col_offset, name.name, 'imported')
DebugStatement(node_name.name, node.lineno, node.col_offset), self.breakpoints.append(st)
)
def visit_ImportFrom(self, node): def visit_ImportFrom(self, node):
if node.module in DEBUG_STATEMENTS: if node.module in DEBUG_STATEMENTS:
self.debug_import_statements.append( st = Debug(node.lineno, node.col_offset, node.module, 'imported')
DebugStatement(node.module, node.lineno, node.col_offset) self.breakpoints.append(st)
)
def visit_Call(self, node):
"""python3.7+ breakpoint()"""
if isinstance(node.func, ast.Name) and node.func.id == 'breakpoint':
st = Debug(node.lineno, node.col_offset, node.func.id, 'called')
self.breakpoints.append(st)
self.generic_visit(node)
def check_file_for_debug_statements(filename): def check_file(filename):
try: try:
ast_obj = ast.parse(open(filename).read(), filename=filename) ast_obj = ast.parse(open(filename, 'rb').read(), filename=filename)
except SyntaxError: except SyntaxError:
print('{0} - Could not parse ast'.format(filename)) print('{} - Could not parse ast'.format(filename))
print() print()
print('\t' + traceback.format_exc().replace('\n', '\n\t')) print('\t' + traceback.format_exc().replace('\n', '\n\t'))
print() print()
return 1 return 1
visitor = ImportStatementParser()
visitor = DebugStatementParser()
visitor.visit(ast_obj) visitor.visit(ast_obj)
if visitor.debug_import_statements:
for debug_statement in visitor.debug_import_statements: for bp in visitor.breakpoints:
print( print(
'{0}:{1}:{2} - {3} imported'.format( '{}:{}:{} - {} {}'.format(
filename, filename, bp.line, bp.col, bp.name, bp.reason,
debug_statement.line, ),
debug_statement.col, )
debug_statement.name,
) return int(bool(visitor.breakpoints))
)
return 1
else:
return 0
def debug_statement_hook(argv=None): def main(argv=None):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to run') parser.add_argument('filenames', nargs='*', help='Filenames to run')
args = parser.parse_args(argv) args = parser.parse_args(argv)
retv = 0 retv = 0
for filename in args.filenames: for filename in args.filenames:
retv |= check_file_for_debug_statements(filename) retv |= check_file(filename)
return retv return retv
if __name__ == '__main__': if __name__ == '__main__':
exit(debug_statement_hook()) exit(main())

View file

@ -12,7 +12,7 @@ def get_aws_credential_files_from_env():
files = set() files = set()
for env_var in ( for env_var in (
'AWS_CONFIG_FILE', 'AWS_CREDENTIAL_FILE', 'AWS_SHARED_CREDENTIALS_FILE', 'AWS_CONFIG_FILE', 'AWS_CREDENTIAL_FILE', 'AWS_SHARED_CREDENTIALS_FILE',
'BOTO_CONFIG' 'BOTO_CONFIG',
): ):
if env_var in os.environ: if env_var in os.environ:
files.add(os.environ[env_var]) files.add(os.environ[env_var])
@ -23,7 +23,7 @@ def get_aws_secrets_from_env():
"""Extract AWS secrets from environment variables.""" """Extract AWS secrets from environment variables."""
keys = set() keys = set()
for env_var in ( for env_var in (
'AWS_SECRET_ACCESS_KEY', 'AWS_SECURITY_TOKEN', 'AWS_SESSION_TOKEN' 'AWS_SECRET_ACCESS_KEY', 'AWS_SECURITY_TOKEN', 'AWS_SESSION_TOKEN',
): ):
if env_var in os.environ: if env_var in os.environ:
keys.add(os.environ[env_var]) keys.add(os.environ[env_var])
@ -50,10 +50,12 @@ def get_aws_secrets_from_file(credentials_file):
for section in parser.sections(): for section in parser.sections():
for var in ( for var in (
'aws_secret_access_key', 'aws_security_token', 'aws_secret_access_key', 'aws_security_token',
'aws_session_token' 'aws_session_token',
): ):
try: try:
keys.add(parser.get(section, var)) key = parser.get(section, var).strip()
if key:
keys.add(key)
except configparser.NoOptionError: except configparser.NoOptionError:
pass pass
return keys return keys
@ -93,13 +95,13 @@ def main(argv=None):
help=( help=(
'Location of additional AWS credential files from which to get ' 'Location of additional AWS credential files from which to get '
'secret keys from' 'secret keys from'
) ),
) )
parser.add_argument( parser.add_argument(
'--allow-missing-credentials', '--allow-missing-credentials',
dest='allow_missing_credentials', dest='allow_missing_credentials',
action='store_true', action='store_true',
help='Allow hook to pass when no credentials are detected.' help='Allow hook to pass when no credentials are detected.',
) )
args = parser.parse_args(argv) args = parser.parse_args(argv)
@ -124,7 +126,7 @@ def main(argv=None):
print( print(
'No AWS keys were found in the configured credential files and ' 'No AWS keys were found in the configured credential files and '
'environment variables.\nPlease ensure you have the correct ' 'environment variables.\nPlease ensure you have the correct '
'setting for --credentials-file' 'setting for --credentials-file',
) )
return 2 return 2

View file

@ -8,6 +8,9 @@ BLACKLIST = [
b'BEGIN DSA PRIVATE KEY', b'BEGIN DSA PRIVATE KEY',
b'BEGIN EC PRIVATE KEY', b'BEGIN EC PRIVATE KEY',
b'BEGIN OPENSSH PRIVATE KEY', b'BEGIN OPENSSH PRIVATE KEY',
b'BEGIN PRIVATE KEY',
b'PuTTY-User-Key-File-2',
b'BEGIN SSH2 ENCRYPTED PRIVATE KEY',
] ]
@ -26,7 +29,7 @@ def detect_private_key(argv=None):
if private_key_files: if private_key_files:
for private_key_file in private_key_files: for private_key_file in private_key_files:
print('Private key found: {0}'.format(private_key_file)) print('Private key found: {}'.format(private_key_file))
return 1 return 1
else: else:
return 0 return 0

View file

@ -58,7 +58,7 @@ def end_of_file_fixer(argv=None):
with open(filename, 'rb+') as file_obj: with open(filename, 'rb+') as file_obj:
ret_for_file = fix_file(file_obj) ret_for_file = fix_file(file_obj)
if ret_for_file: if ret_for_file:
print('Fixing {0}'.format(filename)) print('Fixing {}'.format(filename))
retv |= ret_for_file retv |= ret_for_file
return retv return retv

View file

@ -0,0 +1,52 @@
"""
A very simple pre-commit hook that, when passed one or more filenames
as arguments, will sort the lines in those files.
An example use case for this: you have a deploy-whitelist.txt file
in a repo that contains a list of filenames that is used to specify
files to be included in a docker container. This file has one filename
per line. Various users are adding/removing lines from this file; using
this hook on that file should reduce the instances of git merge
conflicts and keep the file nicely ordered.
"""
from __future__ import print_function
import argparse
PASS = 0
FAIL = 1
def sort_file_contents(f):
before = list(f)
after = sorted([line.strip(b'\n\r') for line in before if line.strip()])
before_string = b''.join(before)
after_string = b'\n'.join(after) + b'\n'
if before_string == after_string:
return PASS
else:
f.seek(0)
f.write(after_string)
f.truncate()
return FAIL
def main(argv=None):
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='+', help='Files to sort')
args = parser.parse_args(argv)
retv = PASS
for arg in args.filenames:
with open(arg, 'rb+') as file_obj:
ret_for_file = sort_file_contents(file_obj)
if ret_for_file:
print('Sorting {}'.format(arg))
retv |= ret_for_file
return retv

View file

@ -0,0 +1,84 @@
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals
import argparse
import collections
CRLF = b'\r\n'
LF = b'\n'
CR = b'\r'
# Prefer LF to CRLF to CR, but detect CRLF before LF
ALL_ENDINGS = (CR, CRLF, LF)
FIX_TO_LINE_ENDING = {'cr': CR, 'crlf': CRLF, 'lf': LF}
def _fix(filename, contents, ending):
new_contents = b''.join(
line.rstrip(b'\r\n') + ending for line in contents.splitlines(True)
)
with open(filename, 'wb') as f:
f.write(new_contents)
def fix_filename(filename, fix):
with open(filename, 'rb') as f:
contents = f.read()
counts = collections.defaultdict(int)
for line in contents.splitlines(True):
for ending in ALL_ENDINGS:
if line.endswith(ending):
counts[ending] += 1
break
# Some amount of mixed line endings
mixed = sum(bool(x) for x in counts.values()) > 1
if fix == 'no' or (fix == 'auto' and not mixed):
return mixed
if fix == 'auto':
max_ending = LF
max_lines = 0
# ordering is important here such that lf > crlf > cr
for ending_type in ALL_ENDINGS:
# also important, using >= to find a max that prefers the last
if counts[ending_type] >= max_lines:
max_ending = ending_type
max_lines = counts[ending_type]
_fix(filename, contents, max_ending)
return 1
else:
target_ending = FIX_TO_LINE_ENDING[fix]
# find if there are lines with *other* endings
# It's possible there's no line endings of the target type
counts.pop(target_ending, None)
other_endings = bool(sum(counts.values()))
if other_endings:
_fix(filename, contents, target_ending)
return other_endings
def main(argv=None):
parser = argparse.ArgumentParser()
parser.add_argument(
'-f', '--fix',
choices=('auto', 'no') + tuple(FIX_TO_LINE_ENDING),
default='auto',
help='Replace line ending with the specified. Default is "auto"',
)
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
args = parser.parse_args(argv)
retv = 0
for filename in args.filenames:
retv |= fix_filename(filename, args.fix)
return retv
if __name__ == '__main__':
exit(main())

View file

@ -0,0 +1,31 @@
from __future__ import print_function
import argparse
from pre_commit_hooks.util import CalledProcessError
from pre_commit_hooks.util import cmd_output
def is_on_branch(protected):
try:
branch = cmd_output('git', 'symbolic-ref', 'HEAD')
except CalledProcessError:
return False
chunks = branch.strip().split('/')
return '/'.join(chunks[2:]) in protected
def main(argv=None):
parser = argparse.ArgumentParser()
parser.add_argument(
'-b', '--branch', action='append',
help='branch to disallow commits to, may be specified multiple times',
)
args = parser.parse_args(argv)
protected = set(args.branch or ('master',))
return int(is_on_branch(protected))
if __name__ == '__main__':
exit(main())

View file

@ -1,13 +1,15 @@
from __future__ import print_function from __future__ import print_function
import argparse import argparse
import io
import json
import sys import sys
from collections import OrderedDict from collections import OrderedDict
import simplejson from six import text_type
def _get_pretty_format(contents, indent, sort_keys=True, top_keys=[]): def _get_pretty_format(contents, indent, ensure_ascii=True, sort_keys=True, top_keys=[]):
def pairs_first(pairs): def pairs_first(pairs):
before = [pair for pair in pairs if pair[0] in top_keys] before = [pair for pair in pairs if pair[0] in top_keys]
before = sorted(before, key=lambda x: top_keys.index(x[0])) before = sorted(before, key=lambda x: top_keys.index(x[0]))
@ -15,39 +17,28 @@ def _get_pretty_format(contents, indent, sort_keys=True, top_keys=[]):
if sort_keys: if sort_keys:
after = sorted(after, key=lambda x: x[0]) after = sorted(after, key=lambda x: x[0])
return OrderedDict(before + after) return OrderedDict(before + after)
return simplejson.dumps( json_pretty = json.dumps(
simplejson.loads( json.loads(contents, object_pairs_hook=pairs_first),
contents, indent=indent,
object_pairs_hook=pairs_first, ensure_ascii=ensure_ascii,
), separators=(',', ': '), # Workaround for https://bugs.python.org/issue16333
indent=indent )
) + "\n" # dumps don't end with a newline # Ensure unicode (Py2) and add the newline that dumps does not end with.
return text_type(json_pretty) + '\n'
def _autofix(filename, new_contents): def _autofix(filename, new_contents):
print("Fixing file {0}".format(filename)) print('Fixing file {}'.format(filename))
with open(filename, 'w') as f: with io.open(filename, 'w', encoding='UTF-8') as f:
f.write(new_contents) f.write(new_contents)
def parse_indent(s): def parse_num_to_int(s):
# type: (str) -> str """Convert string numbers to int, leaving strings as is."""
try: try:
int_indentation_spec = int(s) return int(s)
except ValueError: except ValueError:
if not s.strip(): return s
return s
else:
raise ValueError(
'Non-whitespace JSON indentation delimiter supplied. ',
)
else:
if int_indentation_spec >= 0:
return int_indentation_spec * ' '
else:
raise ValueError(
'Negative integer supplied to construct JSON indentation delimiter. ',
)
def parse_topkeys(s): def parse_topkeys(s):
@ -65,9 +56,19 @@ def pretty_format_json(argv=None):
) )
parser.add_argument( parser.add_argument(
'--indent', '--indent',
type=parse_indent, type=parse_num_to_int,
default=' ', default='2',
help='String used as delimiter for one indentation level', help=(
'The number of indent spaces or a string to be used as delimiter'
' for indentation level e.g. 4 or "\t" (Default: 2)'
),
)
parser.add_argument(
'--no-ensure-ascii',
action='store_true',
dest='no_ensure_ascii',
default=False,
help='Do NOT convert non-ASCII characters to Unicode escape sequences (\\uXXXX)',
) )
parser.add_argument( parser.add_argument(
'--no-sort-keys', '--no-sort-keys',
@ -90,27 +91,26 @@ def pretty_format_json(argv=None):
status = 0 status = 0
for json_file in args.filenames: for json_file in args.filenames:
with open(json_file) as f: with io.open(json_file, encoding='UTF-8') as f:
contents = f.read() contents = f.read()
try: try:
pretty_contents = _get_pretty_format( pretty_contents = _get_pretty_format(
contents, args.indent, sort_keys=not args.no_sort_keys, contents, args.indent, ensure_ascii=not args.no_ensure_ascii,
top_keys=args.top_keys sort_keys=not args.no_sort_keys, top_keys=args.top_keys,
) )
if contents != pretty_contents: if contents != pretty_contents:
print("File {0} is not pretty-formatted".format(json_file)) print('File {} is not pretty-formatted'.format(json_file))
if args.autofix: if args.autofix:
_autofix(json_file, pretty_contents) _autofix(json_file, pretty_contents)
status = 1 status = 1
except ValueError:
except simplejson.JSONDecodeError:
print( print(
"Input File {0} is not a valid JSON, consider using check-json" 'Input File {} is not a valid JSON, consider using check-json'
.format(json_file) .format(json_file),
) )
return 1 return 1

View file

@ -3,6 +3,10 @@ from __future__ import print_function
import argparse import argparse
PASS = 0
FAIL = 1
class Requirement(object): class Requirement(object):
def __init__(self): def __init__(self):
@ -30,21 +34,25 @@ class Requirement(object):
def fix_requirements(f): def fix_requirements(f):
requirements = [] requirements = []
before = [] before = tuple(f)
after = [] after = []
for line in f: before_string = b''.join(before)
before.append(line)
# If the most recent requirement object has a value, then it's time to # If the file is empty (i.e. only whitespace/newlines) exit early
# start building the next requirement object. if before_string.strip() == b'':
return PASS
for line in before:
# If the most recent requirement object has a value, then it's
# time to start building the next requirement object.
if not len(requirements) or requirements[-1].value is not None: if not len(requirements) or requirements[-1].value is not None:
requirements.append(Requirement()) requirements.append(Requirement())
requirement = requirements[-1] requirement = requirements[-1]
# If we see a newline before any requirements, then this is a top of # If we see a newline before any requirements, then this is a
# file comment. # top of file comment.
if len(requirements) == 1 and line.strip() == b'': if len(requirements) == 1 and line.strip() == b'':
if len(requirement.comments) and requirement.comments[0].startswith(b'#'): if len(requirement.comments) and requirement.comments[0].startswith(b'#'):
requirement.value = b'\n' requirement.value = b'\n'
@ -55,21 +63,33 @@ def fix_requirements(f):
else: else:
requirement.value = line requirement.value = line
for requirement in sorted(requirements): # if a file ends in a comment, preserve it at the end
for comment in requirement.comments: if requirements[-1].value is None:
after.append(comment) rest = requirements.pop().comments
after.append(requirement.value) else:
rest = []
# find and remove pkg-resources==0.0.0
# which is automatically added by broken pip package under Debian
requirements = [
req for req in requirements
if req.value != b'pkg-resources==0.0.0\n'
]
for requirement in sorted(requirements):
after.extend(requirement.comments)
after.append(requirement.value)
after.extend(rest)
before_string = b''.join(before)
after_string = b''.join(after) after_string = b''.join(after)
if before_string == after_string: if before_string == after_string:
return 0 return PASS
else: else:
f.seek(0) f.seek(0)
f.write(after_string) f.write(after_string)
f.truncate() f.truncate()
return 1 return FAIL
def fix_requirements_txt(argv=None): def fix_requirements_txt(argv=None):
@ -77,14 +97,14 @@ def fix_requirements_txt(argv=None):
parser.add_argument('filenames', nargs='*', help='Filenames to fix') parser.add_argument('filenames', nargs='*', help='Filenames to fix')
args = parser.parse_args(argv) args = parser.parse_args(argv)
retv = 0 retv = PASS
for arg in args.filenames: for arg in args.filenames:
with open(arg, 'rb+') as file_obj: with open(arg, 'rb+') as file_obj:
ret_for_file = fix_requirements(file_obj) ret_for_file = fix_requirements(file_obj)
if ret_for_file: if ret_for_file:
print('Sorting {0}'.format(arg)) print('Sorting {}'.format(arg))
retv |= ret_for_file retv |= ret_for_file

View file

@ -0,0 +1,123 @@
#!/usr/bin/env python
"""Sort a simple YAML file, keeping blocks of comments and definitions
together.
We assume a strict subset of YAML that looks like:
# block of header comments
# here that should always
# be at the top of the file
# optional comments
# can go here
key: value
key: value
key: value
In other words, we don't sort deeper than the top layer, and might corrupt
complicated YAML files.
"""
from __future__ import print_function
import argparse
QUOTES = ["'", '"']
def sort(lines):
"""Sort a YAML file in alphabetical order, keeping blocks together.
:param lines: array of strings (without newlines)
:return: sorted array of strings
"""
# make a copy of lines since we will clobber it
lines = list(lines)
new_lines = parse_block(lines, header=True)
for block in sorted(parse_blocks(lines), key=first_key):
if new_lines:
new_lines.append('')
new_lines.extend(block)
return new_lines
def parse_block(lines, header=False):
"""Parse and return a single block, popping off the start of `lines`.
If parsing a header block, we stop after we reach a line that is not a
comment. Otherwise, we stop after reaching an empty line.
:param lines: list of lines
:param header: whether we are parsing a header block
:return: list of lines that form the single block
"""
block_lines = []
while lines and lines[0] and (not header or lines[0].startswith('#')):
block_lines.append(lines.pop(0))
return block_lines
def parse_blocks(lines):
"""Parse and return all possible blocks, popping off the start of `lines`.
:param lines: list of lines
:return: list of blocks, where each block is a list of lines
"""
blocks = []
while lines:
if lines[0] == '':
lines.pop(0)
else:
blocks.append(parse_block(lines))
return blocks
def first_key(lines):
"""Returns a string representing the sort key of a block.
The sort key is the first YAML key we encounter, ignoring comments, and
stripping leading quotes.
>>> print(test)
# some comment
'foo': true
>>> first_key(test)
'foo'
"""
for line in lines:
if line.startswith('#'):
continue
if any(line.startswith(quote) for quote in QUOTES):
return line[1:]
return line
def main(argv=None):
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
args = parser.parse_args(argv)
retval = 0
for filename in args.filenames:
with open(filename, 'r+') as f:
lines = [line.rstrip() for line in f.readlines()]
new_lines = sort(lines)
if lines != new_lines:
print("Fixing file `{filename}`".format(filename=filename))
f.seek(0)
f.write("\n".join(new_lines) + "\n")
f.truncate()
retval = 1
return retval
if __name__ == '__main__':
exit(main())

View file

@ -32,7 +32,7 @@ def get_line_offsets_by_line_no(src):
def fix_strings(filename): def fix_strings(filename):
contents = io.open(filename).read() contents = io.open(filename, encoding='UTF-8').read()
line_offsets = get_line_offsets_by_line_no(contents) line_offsets = get_line_offsets_by_line_no(contents)
# Basically a mutable string # Basically a mutable string
@ -52,7 +52,7 @@ def fix_strings(filename):
new_contents = ''.join(splitcontents) new_contents = ''.join(splitcontents)
if contents != new_contents: if contents != new_contents:
with io.open(filename, 'w') as write_handle: with io.open(filename, 'w', encoding='UTF-8') as write_handle:
write_handle.write(new_contents) write_handle.write(new_contents)
return 1 return 1
else: else:
@ -69,7 +69,7 @@ def main(argv=None):
for filename in args.filenames: for filename in args.filenames:
return_value = fix_strings(filename) return_value = fix_strings(filename)
if return_value != 0: if return_value != 0:
print('Fixing strings in {0}'.format(filename)) print('Fixing strings in {}'.format(filename))
retv |= return_value retv |= return_value
return retv return retv

View file

@ -11,12 +11,12 @@ def validate_files(argv=None):
parser.add_argument('filenames', nargs='*') parser.add_argument('filenames', nargs='*')
parser.add_argument( parser.add_argument(
'--django', default=False, action='store_true', '--django', default=False, action='store_true',
help='Use Django-style test naming pattern (test*.py)' help='Use Django-style test naming pattern (test*.py)',
) )
args = parser.parse_args(argv) args = parser.parse_args(argv)
retcode = 0 retcode = 0
test_name_pattern = 'test_.*.py' if args.django else '.*_test.py' test_name_pattern = 'test.*.py' if args.django else '.*_test.py'
for filename in args.filenames: for filename in args.filenames:
base = basename(filename) base = basename(filename)
if ( if (
@ -26,9 +26,9 @@ def validate_files(argv=None):
): ):
retcode = 1 retcode = 1
print( print(
'{0} does not match pattern "{1}"'.format( '{} does not match pattern "{}"'.format(
filename, test_name_pattern filename, test_name_pattern,
) ),
) )
return retcode return retcode

View file

@ -4,8 +4,6 @@ import argparse
import os import os
import sys import sys
from pre_commit_hooks.util import cmd_output
def _fix_file(filename, is_markdown): def _fix_file(filename, is_markdown):
with open(filename, mode='rb') as file_processed: with open(filename, mode='rb') as file_processed:
@ -21,14 +19,19 @@ def _fix_file(filename, is_markdown):
def _process_line(line, is_markdown): def _process_line(line, is_markdown):
if line[-2:] == b'\r\n':
eol = b'\r\n'
elif line[-1:] == b'\n':
eol = b'\n'
else:
eol = b''
# preserve trailing two-space for non-blank lines in markdown files # preserve trailing two-space for non-blank lines in markdown files
eol = b'\r\n' if line[-2:] == b'\r\n' else b'\n'
if is_markdown and (not line.isspace()) and line.endswith(b' ' + eol): if is_markdown and (not line.isspace()) and line.endswith(b' ' + eol):
return line.rstrip() + b' ' + eol return line.rstrip() + b' ' + eol
return line.rstrip() + eol return line.rstrip() + eol
def fix_trailing_whitespace(argv=None): def main(argv=None):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument( parser.add_argument(
'--no-markdown-linebreak-ext', '--no-markdown-linebreak-ext',
@ -36,7 +39,7 @@ def fix_trailing_whitespace(argv=None):
const=[], const=[],
default=argparse.SUPPRESS, default=argparse.SUPPRESS,
dest='markdown_linebreak_ext', dest='markdown_linebreak_ext',
help='Do not preserve linebreak spaces in Markdown' help='Do not preserve linebreak spaces in Markdown',
) )
parser.add_argument( parser.add_argument(
'--markdown-linebreak-ext', '--markdown-linebreak-ext',
@ -45,15 +48,11 @@ def fix_trailing_whitespace(argv=None):
default=['md,markdown'], default=['md,markdown'],
metavar='*|EXT[,EXT,...]', metavar='*|EXT[,EXT,...]',
nargs='?', nargs='?',
help='Markdown extensions (or *) for linebreak spaces' help='Markdown extensions (or *) for linebreak spaces',
) )
parser.add_argument('filenames', nargs='*', help='Filenames to fix') parser.add_argument('filenames', nargs='*', help='Filenames to fix')
args = parser.parse_args(argv) args = parser.parse_args(argv)
bad_whitespace_files = cmd_output(
'grep', '-l', '[[:space:]]$', *args.filenames, retcode=None
).strip().splitlines()
md_args = args.markdown_linebreak_ext md_args = args.markdown_linebreak_ext
if '' in md_args: if '' in md_args:
parser.error('--markdown-linebreak-ext requires a non-empty argument') parser.error('--markdown-linebreak-ext requires a non-empty argument')
@ -67,20 +66,20 @@ def fix_trailing_whitespace(argv=None):
for ext in md_exts: for ext in md_exts:
if any(c in ext[1:] for c in r'./\:'): if any(c in ext[1:] for c in r'./\:'):
parser.error( parser.error(
"bad --markdown-linebreak-ext extension '{0}' (has . / \\ :)\n" "bad --markdown-linebreak-ext extension '{}' (has . / \\ :)\n"
" (probably filename; use '--markdown-linebreak-ext=EXT')" " (probably filename; use '--markdown-linebreak-ext=EXT')"
.format(ext) .format(ext),
) )
return_code = 0 return_code = 0
for bad_whitespace_file in bad_whitespace_files: for filename in args.filenames:
_, extension = os.path.splitext(bad_whitespace_file.lower()) _, extension = os.path.splitext(filename.lower())
md = all_markdown or extension in md_exts md = all_markdown or extension in md_exts
if _fix_file(bad_whitespace_file, md): if _fix_file(filename, md):
print('Fixing {}'.format(bad_whitespace_file)) print('Fixing {}'.format(filename))
return_code = 1 return_code = 1
return return_code return return_code
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(fix_trailing_whitespace()) sys.exit(main())

View file

@ -1,2 +1,2 @@
[wheel] [bdist_wheel]
universal = True universal = True

View file

@ -6,7 +6,7 @@ setup(
name='pre_commit_hooks', name='pre_commit_hooks',
description='Some out-of-the-box hooks for pre-commit.', description='Some out-of-the-box hooks for pre-commit.',
url='https://github.com/pre-commit/pre-commit-hooks', url='https://github.com/pre-commit/pre-commit-hooks',
version='0.7.1', version='1.3.0',
author='Anthony Sottile', author='Anthony Sottile',
author_email='asottile@umich.edu', author_email='asottile@umich.edu',
@ -24,37 +24,44 @@ setup(
packages=find_packages(exclude=('tests*', 'testing*')), packages=find_packages(exclude=('tests*', 'testing*')),
install_requires=[ install_requires=[
# quickfix to prevent pep8 conflicts # quickfix to prevent pycodestyle conflicts
'flake8==3.3.0', 'flake8==3.5.0',
'autopep8==1.2.4', 'autopep8==1.3.5',
'pycodestyle==2.3.1',
'pyyaml==3.12', 'pyyaml==3.12',
'simplejson==3.10.0', 'six==1.11.0',
'six==1.10.0',
], ],
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'autopep8-wrapper = pre_commit_hooks.autopep8_wrapper:main', 'autopep8-wrapper = pre_commit_hooks.autopep8_wrapper:main',
'check-added-large-files = pre_commit_hooks.check_added_large_files:main', 'check-added-large-files = pre_commit_hooks.check_added_large_files:main',
'check-ast = pre_commit_hooks.check_ast:check_ast', 'check-ast = pre_commit_hooks.check_ast:check_ast',
'check-builtin-literals = pre_commit_hooks.check_builtin_literals:main',
'check-byte-order-marker = pre_commit_hooks.check_byte_order_marker:main', 'check-byte-order-marker = pre_commit_hooks.check_byte_order_marker:main',
'check-case-conflict = pre_commit_hooks.check_case_conflict:main', 'check-case-conflict = pre_commit_hooks.check_case_conflict:main',
'check-docstring-first = pre_commit_hooks.check_docstring_first:main', 'check-docstring-first = pre_commit_hooks.check_docstring_first:main',
'check-json = pre_commit_hooks.check_json:check_json', 'check-executables-have-shebangs = pre_commit_hooks.check_executables_have_shebangs:main',
'check-json = pre_commit_check.hooks_json:check_json',
'check-merge-conflict = pre_commit_hooks.check_merge_conflict:detect_merge_conflict', 'check-merge-conflict = pre_commit_hooks.check_merge_conflict:detect_merge_conflict',
'check-symlinks = pre_commit_hooks.check_symlinks:check_symlinks', 'check-symlinks = pre_commit_hooks.check_symlinks:check_symlinks',
'check-vcs-permalinks = pre_commit_hooks.check_vcs_permalinks:main',
'check-xml = pre_commit_hooks.check_xml:check_xml', 'check-xml = pre_commit_hooks.check_xml:check_xml',
'check-yaml = pre_commit_hooks.check_yaml:check_yaml', 'check-yaml = pre_commit_hooks.check_yaml:check_yaml',
'debug-statement-hook = pre_commit_hooks.debug_statement_hook:debug_statement_hook', 'debug-statement-hook = pre_commit_hooks.debug_statement_hook:main',
'detect-aws-credentials = pre_commit_hooks.detect_aws_credentials:main', 'detect-aws-credentials = pre_commit_hooks.detect_aws_credentials:main',
'detect-private-key = pre_commit_hooks.detect_private_key:detect_private_key', 'detect-private-key = pre_commit_hooks.detect_private_key:detect_private_key',
'double-quote-string-fixer = pre_commit_hooks.string_fixer:main', 'double-quote-string-fixer = pre_commit_hooks.string_fixer:main',
'end-of-file-fixer = pre_commit_hooks.end_of_file_fixer:end_of_file_fixer', 'end-of-file-fixer = pre_commit_hooks.end_of_file_fixer:end_of_file_fixer',
'file-contents-sorter = pre_commit_hooks.file_contents_sorter:main',
'fix-encoding-pragma = pre_commit_hooks.fix_encoding_pragma:main', 'fix-encoding-pragma = pre_commit_hooks.fix_encoding_pragma:main',
'forbid-new-submodules = pre_commit_hooks.forbid_new_submodules:main', 'forbid-new-submodules = pre_commit_hooks.forbid_new_submodules:main',
'mixed-line-ending = pre_commit_hooks.mixed_line_ending:main',
'name-tests-test = pre_commit_hooks.tests_should_end_in_test:validate_files', 'name-tests-test = pre_commit_hooks.tests_should_end_in_test:validate_files',
'no-commit-to-branch = pre_commit_hooks.no_commit_to_branch:main',
'pretty-format-json = pre_commit_hooks.pretty_format_json:pretty_format_json', 'pretty-format-json = pre_commit_hooks.pretty_format_json:pretty_format_json',
'requirements-txt-fixer = pre_commit_hooks.requirements_txt_fixer:fix_requirements_txt', 'requirements-txt-fixer = pre_commit_hooks.requirements_txt_fixer:fix_requirements_txt',
'trailing-whitespace-fixer = pre_commit_hooks.trailing_whitespace_fixer:fix_trailing_whitespace', 'sort-simple-yaml = pre_commit_hooks.sort_simple_yaml:main',
'trailing-whitespace-fixer = pre_commit_hooks.trailing_whitespace_fixer:main',
], ],
}, },
) )

View file

@ -0,0 +1,4 @@
# file with an AWS access key id but no valid AWS secret access key only space characters
[production]
aws_access_key_id = AKIASLARTARGENTINA86
aws_secret_access_key =

View file

@ -1 +0,0 @@
does_not_exist

View file

@ -0,0 +1,17 @@
from six.moves import builtins
c1 = complex()
d1 = dict()
f1 = float()
i1 = int()
l1 = list()
s1 = str()
t1 = tuple()
c2 = builtins.complex()
d2 = builtins.dict()
f2 = builtins.float()
i2 = builtins.int()
l2 = builtins.list()
s2 = builtins.str()
t2 = builtins.tuple()

View file

@ -0,0 +1,7 @@
c1 = 0j
d1 = {}
f1 = 0.0
i1 = 0
l1 = []
s1 = ''
t1 = ()

View file

@ -1,5 +0,0 @@
def foo(obj):
import pdb; pdb.set_trace()
return 5

View file

@ -0,0 +1,10 @@
{
"alist": [
2,
34,
234
],
"blah": null,
"foo": "bar",
"non_ascii": "中文にほんご한국어"
}

View file

@ -1 +0,0 @@
does_exist

View file

@ -1,7 +1,6 @@
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
import io
import os.path import os.path
@ -10,9 +9,3 @@ TESTING_DIR = os.path.abspath(os.path.dirname(__file__))
def get_resource_path(path): def get_resource_path(path):
return os.path.join(TESTING_DIR, 'resources', path) return os.path.join(TESTING_DIR, 'resources', path)
def write_file(filename, contents):
"""Hax because coveragepy chokes on nested context managers."""
with io.open(filename, 'w', newline='') as file_obj:
file_obj.write(contents)

View file

@ -23,6 +23,6 @@ def test_main_failing(tmpdir, input_src, expected_ret, output_src):
def test_respects_config_file(tmpdir): def test_respects_config_file(tmpdir):
with tmpdir.as_cwd(): with tmpdir.as_cwd():
tmpdir.join('setup.cfg').write('[pep8]\nignore=E221') tmpdir.join('setup.cfg').write('[pycodestyle]\nignore=E221')
tmpdir.join('test.py').write('print(1 + 2)\n') tmpdir.join('test.py').write('print(1 + 2)\n')
assert main(['test.py', '-i', '-v']) == 0 assert main(['test.py', '-i', '-v']) == 0

View file

@ -79,15 +79,6 @@ xfailif_no_gitlfs = pytest.mark.xfail(
@xfailif_no_gitlfs @xfailif_no_gitlfs
def test_allows_gitlfs(temp_git_dir): # pragma: no cover def test_allows_gitlfs(temp_git_dir): # pragma: no cover
with temp_git_dir.as_cwd(): with temp_git_dir.as_cwd():
# Work around https://github.com/github/git-lfs/issues/913
cmd_output(
'git',
'commit',
'--no-gpg-sign',
'--allow-empty',
'-m',
'foo',
)
cmd_output('git', 'lfs', 'install') cmd_output('git', 'lfs', 'install')
temp_git_dir.join('f.py').write('a' * 10000) temp_git_dir.join('f.py').write('a' * 10000)
cmd_output('git', 'lfs', 'track', 'f.py') cmd_output('git', 'lfs', 'track', 'f.py')

View file

@ -0,0 +1,116 @@
import ast
import pytest
from pre_commit_hooks.check_builtin_literals import BuiltinTypeCall
from pre_commit_hooks.check_builtin_literals import BuiltinTypeVisitor
from pre_commit_hooks.check_builtin_literals import main
from testing.util import get_resource_path
@pytest.fixture
def visitor():
return BuiltinTypeVisitor()
@pytest.mark.parametrize(
('expression', 'calls'),
[
# see #285
('x[0]()', []),
# complex
("0j", []),
("complex()", [BuiltinTypeCall('complex', 1, 0)]),
("complex(0, 0)", []),
("complex('0+0j')", []),
('builtins.complex()', []),
# float
("0.0", []),
("float()", [BuiltinTypeCall('float', 1, 0)]),
("float('0.0')", []),
('builtins.float()', []),
# int
("0", []),
("int()", [BuiltinTypeCall('int', 1, 0)]),
("int('0')", []),
('builtins.int()', []),
# list
("[]", []),
("list()", [BuiltinTypeCall('list', 1, 0)]),
("list('abc')", []),
("list([c for c in 'abc'])", []),
("list(c for c in 'abc')", []),
('builtins.list()', []),
# str
("''", []),
("str()", [BuiltinTypeCall('str', 1, 0)]),
("str('0')", []),
('builtins.str()', []),
# tuple
("()", []),
("tuple()", [BuiltinTypeCall('tuple', 1, 0)]),
("tuple('abc')", []),
("tuple([c for c in 'abc'])", []),
("tuple(c for c in 'abc')", []),
('builtins.tuple()', []),
],
)
def test_non_dict_exprs(visitor, expression, calls):
visitor.visit(ast.parse(expression))
assert visitor.builtin_type_calls == calls
@pytest.mark.parametrize(
('expression', 'calls'),
[
("{}", []),
("dict()", [BuiltinTypeCall('dict', 1, 0)]),
("dict(a=1, b=2, c=3)", []),
("dict(**{'a': 1, 'b': 2, 'c': 3})", []),
("dict([(k, v) for k, v in [('a', 1), ('b', 2), ('c', 3)]])", []),
("dict((k, v) for k, v in [('a', 1), ('b', 2), ('c', 3)])", []),
('builtins.dict()', []),
],
)
def test_dict_allow_kwargs_exprs(visitor, expression, calls):
visitor.visit(ast.parse(expression))
assert visitor.builtin_type_calls == calls
@pytest.mark.parametrize(
('expression', 'calls'),
[
("dict()", [BuiltinTypeCall('dict', 1, 0)]),
("dict(a=1, b=2, c=3)", [BuiltinTypeCall('dict', 1, 0)]),
("dict(**{'a': 1, 'b': 2, 'c': 3})", [BuiltinTypeCall('dict', 1, 0)]),
('builtins.dict()', []),
],
)
def test_dict_no_allow_kwargs_exprs(expression, calls):
visitor = BuiltinTypeVisitor(allow_dict_kwargs=False)
visitor.visit(ast.parse(expression))
assert visitor.builtin_type_calls == calls
def test_ignore_constructors():
visitor = BuiltinTypeVisitor(ignore=('complex', 'dict', 'float', 'int', 'list', 'str', 'tuple'))
visitor.visit(ast.parse(open(get_resource_path('builtin_constructors.py'), 'rb').read(), 'builtin_constructors.py'))
assert visitor.builtin_type_calls == []
def test_failing_file():
rc = main([get_resource_path('builtin_constructors.py')])
assert rc == 1
def test_passing_file():
rc = main([get_resource_path('builtin_literals.py')])
assert rc == 0
def test_failing_file_ignore_all():
rc = main([
'--ignore=complex,dict,float,int,list,str,tuple',
get_resource_path('builtin_constructors.py'),
])
assert rc == 0

View file

@ -19,7 +19,7 @@ TESTS = (
'"foo"\n', '"foo"\n',
1, 1,
'{filename}:2 Module docstring appears after code ' '{filename}:2 Module docstring appears after code '
'(code seen on line 1).\n' '(code seen on line 1).\n',
), ),
# Test double docstring # Test double docstring
( (
@ -28,7 +28,7 @@ TESTS = (
'"fake docstring"\n', '"fake docstring"\n',
1, 1,
'{filename}:3 Multiple module docstrings ' '{filename}:3 Multiple module docstrings '
'(first docstring on line 1).\n' '(first docstring on line 1).\n',
), ),
# Test multiple lines of code above # Test multiple lines of code above
( (

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
import pytest
from pre_commit_hooks.check_executables_have_shebangs import main
@pytest.mark.parametrize(
'content', (
b'#!/bin/bash\nhello world\n',
b'#!/usr/bin/env python3.6',
b'#!python',
'#!☃'.encode('UTF-8'),
),
)
def test_has_shebang(content, tmpdir):
path = tmpdir.join('path')
path.write(content, 'wb')
assert main((path.strpath,)) == 0
@pytest.mark.parametrize(
'content', (
b'',
b' #!python\n',
b'\n#!python\n',
b'python\n',
''.encode('UTF-8'),
),
)
def test_bad_shebang(content, tmpdir, capsys):
path = tmpdir.join('path')
path.write(content, 'wb')
assert main((path.strpath,)) == 1
_, stderr = capsys.readouterr()
assert stderr.startswith('{}: marked executable but'.format(path.strpath))

View file

@ -4,11 +4,13 @@ from pre_commit_hooks.check_json import check_json
from testing.util import get_resource_path from testing.util import get_resource_path
@pytest.mark.parametrize(('filename', 'expected_retval'), ( @pytest.mark.parametrize(
('bad_json.notjson', 1), ('filename', 'expected_retval'), (
('bad_json_latin1.nonjson', 1), ('bad_json.notjson', 1),
('ok_json.json', 0), ('bad_json_latin1.nonjson', 1),
)) ('ok_json.json', 0),
),
)
def test_check_json(capsys, filename, expected_retval): def test_check_json(capsys, filename, expected_retval):
ret = check_json([get_resource_path(filename)]) ret = check_json([get_resource_path(filename)])
assert ret == expected_retval assert ret == expected_retval

View file

@ -9,10 +9,9 @@ import pytest
from pre_commit_hooks.check_merge_conflict import detect_merge_conflict from pre_commit_hooks.check_merge_conflict import detect_merge_conflict
from pre_commit_hooks.util import cmd_output from pre_commit_hooks.util import cmd_output
from testing.util import get_resource_path from testing.util import get_resource_path
from testing.util import write_file
@pytest.yield_fixture @pytest.fixture
def f1_is_a_conflict_file(tmpdir): def f1_is_a_conflict_file(tmpdir):
# Make a merge conflict # Make a merge conflict
repo1 = tmpdir.join('repo1') repo1 = tmpdir.join('repo1')
@ -37,7 +36,7 @@ def f1_is_a_conflict_file(tmpdir):
with repo2.as_cwd(): with repo2.as_cwd():
repo2_f1.write('child\n') repo2_f1.write('child\n')
cmd_output('git', 'commit', '--no-gpg-sign', '-am', 'clone commit2') cmd_output('git', 'commit', '--no-gpg-sign', '-am', 'clone commit2')
cmd_output('git', 'pull', retcode=None) cmd_output('git', 'pull', '--no-rebase', retcode=None)
# We should end up in a merge conflict! # We should end up in a merge conflict!
f1 = repo2_f1.read() f1 = repo2_f1.read()
assert f1.startswith( assert f1.startswith(
@ -45,7 +44,7 @@ def f1_is_a_conflict_file(tmpdir):
'child\n' 'child\n'
'=======\n' '=======\n'
'parent\n' 'parent\n'
'>>>>>>>' '>>>>>>>',
) or f1.startswith( ) or f1.startswith(
'<<<<<<< HEAD\n' '<<<<<<< HEAD\n'
'child\n' 'child\n'
@ -53,14 +52,22 @@ def f1_is_a_conflict_file(tmpdir):
'||||||| merged common ancestors\n' '||||||| merged common ancestors\n'
'=======\n' '=======\n'
'parent\n' 'parent\n'
'>>>>>>>' '>>>>>>>',
) or f1.startswith(
# .gitconfig with [pull] rebase = preserve causes a rebase which
# flips parent / child
'<<<<<<< HEAD\n'
'parent\n'
'=======\n'
'child\n'
'>>>>>>>',
) )
assert os.path.exists(os.path.join('.git', 'MERGE_MSG')) assert os.path.exists(os.path.join('.git', 'MERGE_MSG'))
yield yield repo2
@pytest.yield_fixture @pytest.fixture
def repository_is_pending_merge(tmpdir): def repository_pending_merge(tmpdir):
# Make a (non-conflicting) merge # Make a (non-conflicting) merge
repo1 = tmpdir.join('repo1') repo1 = tmpdir.join('repo1')
repo1_f1 = repo1.join('f1') repo1_f1 = repo1.join('f1')
@ -85,12 +92,12 @@ def repository_is_pending_merge(tmpdir):
repo2_f2.write('child\n') repo2_f2.write('child\n')
cmd_output('git', 'add', '--', repo2_f2.strpath) cmd_output('git', 'add', '--', repo2_f2.strpath)
cmd_output('git', 'commit', '--no-gpg-sign', '-m', 'clone commit2') cmd_output('git', 'commit', '--no-gpg-sign', '-m', 'clone commit2')
cmd_output('git', 'pull', '--no-commit') cmd_output('git', 'pull', '--no-commit', '--no-rebase')
# We should end up in a pending merge # We should end up in a pending merge
assert repo2_f1.read() == 'parent\n' assert repo2_f1.read() == 'parent\n'
assert repo2_f2.read() == 'child\n' assert repo2_f2.read() == 'child\n'
assert os.path.exists(os.path.join('.git', 'MERGE_HEAD')) assert os.path.exists(os.path.join('.git', 'MERGE_HEAD'))
yield yield repo2
@pytest.mark.usefixtures('f1_is_a_conflict_file') @pytest.mark.usefixtures('f1_is_a_conflict_file')
@ -99,20 +106,18 @@ def test_merge_conflicts_git():
@pytest.mark.parametrize( @pytest.mark.parametrize(
'failing_contents', ('<<<<<<< HEAD\n', '=======\n', '>>>>>>> master\n'), 'contents', (b'<<<<<<< HEAD\n', b'=======\n', b'>>>>>>> master\n'),
) )
@pytest.mark.usefixtures('repository_is_pending_merge') def test_merge_conflicts_failing(contents, repository_pending_merge):
def test_merge_conflicts_failing(failing_contents): repository_pending_merge.join('f2').write_binary(contents)
write_file('f2', failing_contents)
assert detect_merge_conflict(['f2']) == 1 assert detect_merge_conflict(['f2']) == 1
@pytest.mark.parametrize( @pytest.mark.parametrize(
'ok_contents', ('# <<<<<<< HEAD\n', '# =======\n', 'import my_module', ''), 'contents', (b'# <<<<<<< HEAD\n', b'# =======\n', b'import mod', b''),
) )
@pytest.mark.usefixtures('f1_is_a_conflict_file') def test_merge_conflicts_ok(contents, f1_is_a_conflict_file):
def test_merge_conflicts_ok(ok_contents): f1_is_a_conflict_file.join('f1').write_binary(contents)
write_file('f1', ok_contents)
assert detect_merge_conflict(['f1']) == 0 assert detect_merge_conflict(['f1']) == 0

View file

@ -3,14 +3,21 @@ import os
import pytest import pytest
from pre_commit_hooks.check_symlinks import check_symlinks from pre_commit_hooks.check_symlinks import check_symlinks
from testing.util import get_resource_path
@pytest.mark.xfail(os.name == 'nt', reason='No symlink support on windows') xfail_symlink = pytest.mark.xfail(os.name == 'nt', reason='No symlink support')
@pytest.mark.parametrize(('filename', 'expected_retval'), (
('broken_symlink', 1),
('working_symlink', 0), @xfail_symlink
)) @pytest.mark.parametrize(
def test_check_symlinks(filename, expected_retval): ('dest', 'expected'), (('exists', 0), ('does-not-exist', 1)),
ret = check_symlinks([get_resource_path(filename)]) )
assert ret == expected_retval def test_check_symlinks(tmpdir, dest, expected): # pragma: no cover (symlinks)
tmpdir.join('exists').ensure()
symlink = tmpdir.join('symlink')
symlink.mksymlinkto(tmpdir.join(dest))
assert check_symlinks((symlink.strpath,)) == expected
def test_check_symlinks_normal_file(tmpdir):
assert check_symlinks((tmpdir.join('f').ensure().strpath,)) == 0

View file

@ -0,0 +1,38 @@
from __future__ import absolute_import
from __future__ import unicode_literals
from pre_commit_hooks.check_vcs_permalinks import main
def test_trivial(tmpdir):
f = tmpdir.join('f.txt').ensure()
assert not main((f.strpath,))
def test_passing(tmpdir):
f = tmpdir.join('f.txt')
f.write_binary(
# permalinks are ok
b'https://github.com/asottile/test/blob/649e6/foo%20bar#L1\n'
# links to files but not line numbers are ok
b'https://github.com/asottile/test/blob/master/foo%20bar\n'
# regression test for overly-greedy regex
b'https://github.com/ yes / no ? /blob/master/foo#L1\n',
)
assert not main((f.strpath,))
def test_failing(tmpdir, capsys):
with tmpdir.as_cwd():
tmpdir.join('f.txt').write_binary(
b'https://github.com/asottile/test/blob/master/foo#L1\n',
)
assert main(('f.txt',))
out, _ = capsys.readouterr()
assert out == (
'f.txt:1:https://github.com/asottile/test/blob/master/foo#L1\n'
'\n'
'Non-permanent github link detected.\n'
'On any page on github press [y] to load a permalink.\n'
)

View file

@ -4,10 +4,12 @@ from pre_commit_hooks.check_xml import check_xml
from testing.util import get_resource_path from testing.util import get_resource_path
@pytest.mark.parametrize(('filename', 'expected_retval'), ( @pytest.mark.parametrize(
('bad_xml.notxml', 1), ('filename', 'expected_retval'), (
('ok_xml.xml', 0), ('bad_xml.notxml', 1),
)) ('ok_xml.xml', 0),
),
)
def test_check_xml(filename, expected_retval): def test_check_xml(filename, expected_retval):
ret = check_xml([get_resource_path(filename)]) ret = check_xml([get_resource_path(filename)])
assert ret == expected_retval assert ret == expected_retval

View file

@ -1,13 +1,54 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import pytest import pytest
from pre_commit_hooks.check_yaml import check_yaml from pre_commit_hooks.check_yaml import check_yaml
from testing.util import get_resource_path from testing.util import get_resource_path
@pytest.mark.parametrize(('filename', 'expected_retval'), ( @pytest.mark.parametrize(
('bad_yaml.notyaml', 1), ('filename', 'expected_retval'), (
('ok_yaml.yaml', 0), ('bad_yaml.notyaml', 1),
)) ('ok_yaml.yaml', 0),
),
)
def test_check_yaml(filename, expected_retval): def test_check_yaml(filename, expected_retval):
ret = check_yaml([get_resource_path(filename)]) ret = check_yaml([get_resource_path(filename)])
assert ret == expected_retval assert ret == expected_retval
def test_check_yaml_allow_multiple_documents(tmpdir):
f = tmpdir.join('test.yaml')
f.write('---\nfoo\n---\nbar\n')
# should fail without the setting
assert check_yaml((f.strpath,))
# should pass when we allow multiple documents
assert not check_yaml(('--allow-multiple-documents', f.strpath))
def test_fails_even_with_allow_multiple_documents(tmpdir):
f = tmpdir.join('test.yaml')
f.write('[')
assert check_yaml(('--allow-multiple-documents', f.strpath))
def test_check_yaml_unsafe(tmpdir):
f = tmpdir.join('test.yaml')
f.write(
'some_foo: !vault |\n'
' $ANSIBLE_VAULT;1.1;AES256\n'
' deadbeefdeadbeefdeadbeef\n',
)
# should fail "safe" check
assert check_yaml((f.strpath,))
# should pass when we allow unsafe documents
assert not check_yaml(('--unsafe', f.strpath))
def test_check_yaml_unsafe_still_fails_on_syntax_errors(tmpdir):
f = tmpdir.join('test.yaml')
f.write('[')
assert check_yaml(('--unsafe', f.strpath))

View file

@ -7,7 +7,7 @@ import pytest
from pre_commit_hooks.util import cmd_output from pre_commit_hooks.util import cmd_output
@pytest.yield_fixture @pytest.fixture
def temp_git_dir(tmpdir): def temp_git_dir(tmpdir):
git_dir = tmpdir.join('gits') git_dir = tmpdir.join('gits')
cmd_output('git', 'init', '--', git_dir.strpath) cmd_output('git', 'init', '--', git_dir.strpath)

View file

@ -1,73 +1,63 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
import ast import ast
import pytest from pre_commit_hooks.debug_statement_hook import Debug
from pre_commit_hooks.debug_statement_hook import DebugStatementParser
from pre_commit_hooks.debug_statement_hook import debug_statement_hook from pre_commit_hooks.debug_statement_hook import main
from pre_commit_hooks.debug_statement_hook import DebugStatement
from pre_commit_hooks.debug_statement_hook import ImportStatementParser
from testing.util import get_resource_path from testing.util import get_resource_path
@pytest.fixture def test_no_breakpoints():
def ast_with_no_debug_imports(): visitor = DebugStatementParser()
return ast.parse(""" visitor.visit(ast.parse('import os\nfrom foo import bar\n'))
import foo assert visitor.breakpoints == []
import bar
import baz
from foo import bar
""")
@pytest.fixture def test_finds_debug_import_attribute_access():
def ast_with_debug_import_form_1(): visitor = DebugStatementParser()
return ast.parse(""" visitor.visit(ast.parse('import ipdb; ipdb.set_trace()'))
assert visitor.breakpoints == [Debug(1, 0, 'ipdb', 'imported')]
import ipdb; ipdb.set_trace()
""")
@pytest.fixture def test_finds_debug_import_from_import():
def ast_with_debug_import_form_2(): visitor = DebugStatementParser()
return ast.parse(""" visitor.visit(ast.parse('from pudb import set_trace; set_trace()'))
assert visitor.breakpoints == [Debug(1, 0, 'pudb', 'imported')]
from pudb import set_trace; set_trace()
""")
def test_returns_no_debug_statements(ast_with_no_debug_imports): def test_finds_breakpoint():
visitor = ImportStatementParser() visitor = DebugStatementParser()
visitor.visit(ast_with_no_debug_imports) visitor.visit(ast.parse('breakpoint()'))
assert visitor.debug_import_statements == [] assert visitor.breakpoints == [Debug(1, 0, 'breakpoint', 'called')]
def test_returns_one_form_1(ast_with_debug_import_form_1): def test_returns_one_for_failing_file(tmpdir):
visitor = ImportStatementParser() f_py = tmpdir.join('f.py')
visitor.visit(ast_with_debug_import_form_1) f_py.write('def f():\n import pdb; pdb.set_trace()')
assert visitor.debug_import_statements == [ ret = main([f_py.strpath])
DebugStatement('ipdb', 3, 0)
]
def test_returns_one_form_2(ast_with_debug_import_form_2):
visitor = ImportStatementParser()
visitor.visit(ast_with_debug_import_form_2)
assert visitor.debug_import_statements == [
DebugStatement('pudb', 3, 0)
]
def test_returns_one_for_failing_file():
ret = debug_statement_hook([get_resource_path('file_with_debug.notpy')])
assert ret == 1 assert ret == 1
def test_returns_zero_for_passing_file(): def test_returns_zero_for_passing_file():
ret = debug_statement_hook([__file__]) ret = main([__file__])
assert ret == 0 assert ret == 0
def test_syntaxerror_file(): def test_syntaxerror_file():
ret = debug_statement_hook([get_resource_path('cannot_parse_ast.notpy')]) ret = main([get_resource_path('cannot_parse_ast.notpy')])
assert ret == 1 assert ret == 1
def test_non_utf8_file(tmpdir):
f_py = tmpdir.join('f.py')
f_py.write_binary('# -*- coding: cp1252 -*-\nx = ""\n'.encode('cp1252'))
assert main((f_py.strpath,)) == 0
def test_py37_breakpoint(tmpdir):
f_py = tmpdir.join('f.py')
f_py.write('def f():\n breakpoint()\n')
assert main((f_py.strpath,)) == 1

View file

@ -21,16 +21,16 @@ from testing.util import get_resource_path
( (
{ {
'AWS_DUMMY_KEY': '/foo', 'AWS_CONFIG_FILE': '/bar', 'AWS_DUMMY_KEY': '/foo', 'AWS_CONFIG_FILE': '/bar',
'AWS_CREDENTIAL_FILE': '/baz' 'AWS_CREDENTIAL_FILE': '/baz',
}, },
{'/bar', '/baz'} {'/bar', '/baz'},
), ),
( (
{ {
'AWS_CONFIG_FILE': '/foo', 'AWS_CREDENTIAL_FILE': '/bar', 'AWS_CONFIG_FILE': '/foo', 'AWS_CREDENTIAL_FILE': '/bar',
'AWS_SHARED_CREDENTIALS_FILE': '/baz' 'AWS_SHARED_CREDENTIALS_FILE': '/baz',
}, },
{'/foo', '/bar', '/baz'} {'/foo', '/bar', '/baz'},
), ),
), ),
) )
@ -51,7 +51,7 @@ def test_get_aws_credentials_file_from_env(env_vars, values):
({'AWS_DUMMY_KEY': 'foo', 'AWS_SECRET_ACCESS_KEY': 'bar'}, {'bar'}), ({'AWS_DUMMY_KEY': 'foo', 'AWS_SECRET_ACCESS_KEY': 'bar'}, {'bar'}),
( (
{'AWS_SECRET_ACCESS_KEY': 'foo', 'AWS_SECURITY_TOKEN': 'bar'}, {'AWS_SECRET_ACCESS_KEY': 'foo', 'AWS_SECURITY_TOKEN': 'bar'},
{'foo', 'bar'} {'foo', 'bar'},
), ),
), ),
) )
@ -66,21 +66,24 @@ def test_get_aws_secrets_from_env(env_vars, values):
( (
( (
'aws_config_with_secret.ini', 'aws_config_with_secret.ini',
{'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb'} {'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb'},
), ),
('aws_config_with_session_token.ini', {'foo'}), ('aws_config_with_session_token.ini', {'foo'}),
('aws_config_with_secret_and_session_token.ini', (
{'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb', 'foo'}), 'aws_config_with_secret_and_session_token.ini',
{'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb', 'foo'},
),
( (
'aws_config_with_multiple_sections.ini', 'aws_config_with_multiple_sections.ini',
{ {
'7xebzorgm5143ouge9gvepxb2z70bsb2rtrh099e', '7xebzorgm5143ouge9gvepxb2z70bsb2rtrh099e',
'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb', 'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb',
'ixswosj8gz3wuik405jl9k3vdajsnxfhnpui38ez', 'ixswosj8gz3wuik405jl9k3vdajsnxfhnpui38ez',
'foo' 'foo',
} },
), ),
('aws_config_without_secrets.ini', set()), ('aws_config_without_secrets.ini', set()),
('aws_config_without_secrets_with_spaces.ini', set()),
('nonsense.txt', set()), ('nonsense.txt', set()),
('ok_json.json', set()), ('ok_json.json', set()),
), ),
@ -98,6 +101,7 @@ def test_get_aws_secrets_from_file(filename, expected_keys):
('aws_config_with_session_token.ini', 1), ('aws_config_with_session_token.ini', 1),
('aws_config_with_multiple_sections.ini', 1), ('aws_config_with_multiple_sections.ini', 1),
('aws_config_without_secrets.ini', 0), ('aws_config_without_secrets.ini', 0),
('aws_config_without_secrets_with_spaces.ini', 0),
('nonsense.txt', 0), ('nonsense.txt', 0),
('ok_json.json', 0), ('ok_json.json', 0),
), ),
@ -121,7 +125,7 @@ def test_non_existent_credentials(mock_secrets_env, mock_secrets_file, capsys):
mock_secrets_file.return_value = set() mock_secrets_file.return_value = set()
ret = main(( ret = main((
get_resource_path('aws_config_without_secrets.ini'), get_resource_path('aws_config_without_secrets.ini'),
"--credentials-file=testing/resources/credentailsfilethatdoesntexist" "--credentials-file=testing/resources/credentailsfilethatdoesntexist",
)) ))
assert ret == 2 assert ret == 2
out, _ = capsys.readouterr() out, _ = capsys.readouterr()
@ -141,6 +145,6 @@ def test_non_existent_credentials_with_allow_flag(mock_secrets_env, mock_secrets
ret = main(( ret = main((
get_resource_path('aws_config_without_secrets.ini'), get_resource_path('aws_config_without_secrets.ini'),
"--credentials-file=testing/resources/credentailsfilethatdoesntexist", "--credentials-file=testing/resources/credentailsfilethatdoesntexist",
"--allow-missing-credentials" "--allow-missing-credentials",
)) ))
assert ret == 0 assert ret == 0

View file

@ -8,6 +8,8 @@ TESTS = (
(b'-----BEGIN DSA PRIVATE KEY-----', 1), (b'-----BEGIN DSA PRIVATE KEY-----', 1),
(b'-----BEGIN EC PRIVATE KEY-----', 1), (b'-----BEGIN EC PRIVATE KEY-----', 1),
(b'-----BEGIN OPENSSH PRIVATE KEY-----', 1), (b'-----BEGIN OPENSSH PRIVATE KEY-----', 1),
(b'PuTTY-User-Key-File-2: ssh-rsa', 1),
(b'---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----', 1),
(b'ssh-rsa DATA', 0), (b'ssh-rsa DATA', 0),
(b'ssh-dsa DATA', 0), (b'ssh-dsa DATA', 0),
# Some arbitrary binary data # Some arbitrary binary data

View file

@ -0,0 +1,33 @@
import pytest
from pre_commit_hooks.file_contents_sorter import FAIL
from pre_commit_hooks.file_contents_sorter import main
from pre_commit_hooks.file_contents_sorter import PASS
@pytest.mark.parametrize(
('input_s', 'expected_retval', 'output'),
(
(b'', FAIL, b'\n'),
(b'lonesome\n', PASS, b'lonesome\n'),
(b'missing_newline', FAIL, b'missing_newline\n'),
(b'newline\nmissing', FAIL, b'missing\nnewline\n'),
(b'missing\nnewline', FAIL, b'missing\nnewline\n'),
(b'alpha\nbeta\n', PASS, b'alpha\nbeta\n'),
(b'beta\nalpha\n', FAIL, b'alpha\nbeta\n'),
(b'C\nc\n', PASS, b'C\nc\n'),
(b'c\nC\n', FAIL, b'C\nc\n'),
(b'mag ical \n tre vor\n', FAIL, b' tre vor\nmag ical \n'),
(b'@\n-\n_\n#\n', FAIL, b'#\n-\n@\n_\n'),
(b'extra\n\n\nwhitespace\n', FAIL, b'extra\nwhitespace\n'),
(b'whitespace\n\n\nextra\n', FAIL, b'extra\nwhitespace\n'),
),
)
def test_integration(input_s, expected_retval, output, tmpdir):
path = tmpdir.join('file.txt')
path.write_binary(input_s)
output_retval = main([path.strpath])
assert path.read_binary() == output
assert output_retval == expected_retval

View file

@ -56,7 +56,7 @@ def test_integration_remove_ok(tmpdir):
b'# -*- coding: utf-8 -*-\n' b'# -*- coding: utf-8 -*-\n'
b'foo = "bar"\n' b'foo = "bar"\n'
), ),
) ),
) )
def test_ok_inputs(input_str): def test_ok_inputs(input_str):
bytesio = io.BytesIO(input_str) bytesio = io.BytesIO(input_str)
@ -100,7 +100,7 @@ def test_ok_inputs(input_str):
(b'#!/usr/bin/env python\n', b''), (b'#!/usr/bin/env python\n', b''),
(b'#!/usr/bin/env python\n#coding: utf8\n', b''), (b'#!/usr/bin/env python\n#coding: utf8\n', b''),
(b'#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n', b''), (b'#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n', b''),
) ),
) )
def test_not_ok_inputs(input_str, output): def test_not_ok_inputs(input_str, output):
bytesio = io.BytesIO(input_str) bytesio = io.BytesIO(input_str)

View file

@ -6,14 +6,18 @@ from pre_commit.util import cmd_output
from pre_commit_hooks.forbid_new_submodules import main from pre_commit_hooks.forbid_new_submodules import main
@pytest.yield_fixture @pytest.fixture
def git_dir_with_git_dir(tmpdir): def git_dir_with_git_dir(tmpdir):
with tmpdir.as_cwd(): with tmpdir.as_cwd():
cmd_output('git', 'init', '.') cmd_output('git', 'init', '.')
cmd_output('git', 'commit', '-m', 'init', '--allow-empty') cmd_output(
'git', 'commit', '-m', 'init', '--allow-empty', '--no-gpg-sign',
)
cmd_output('git', 'init', 'foo') cmd_output('git', 'init', 'foo')
with tmpdir.join('foo').as_cwd(): cmd_output(
cmd_output('git', 'commit', '-m', 'init', '--allow-empty') 'git', 'commit', '-m', 'init', '--allow-empty', '--no-gpg-sign',
cwd=tmpdir.join('foo').strpath,
)
yield yield

View file

@ -1,7 +1,43 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import io import io
import yaml
def test_hooks_yaml_same_contents():
legacy_contents = io.open('hooks.yaml').read() def _assert_parseable_in_old_pre_commit(hooks):
contents = io.open('.pre-commit-hooks.yaml').read() for hook in hooks:
assert legacy_contents == contents assert {'id', 'name', 'entry', 'files', 'language'} <= set(hook)
def test_legacy_hooks():
with io.open('hooks.yaml', encoding='UTF-8') as legacy_file:
legacy = yaml.load(legacy_file.read())
with io.open('.pre-commit-hooks.yaml', encoding='UTF-8') as hooks_file:
hooks = yaml.load(hooks_file.read())
# The same set of hooks should be defined in both files
new_hook_ids = {hook['id'] for hook in hooks}
legacy_hook_ids = {hook['id'] for hook in legacy}
assert new_hook_ids == legacy_hook_ids
# Both files should be parseable by pre-commit<0.15.0
_assert_parseable_in_old_pre_commit(legacy)
_assert_parseable_in_old_pre_commit(hooks)
# The legacy file should force upgrading
for hook in legacy:
del hook['id']
assert hook == {
'language': 'system',
'name': 'upgrade-your-pre-commit-version',
'entry': 'upgrade-your-pre-commit-version',
'files': '',
'minimum_pre_commit_version': '0.15.0',
}
# Each hook should require a new version if it uses types
for hook in hooks:
if 'types' in hook:
assert hook['minimum_pre_commit_version'] == '0.15.0'

View file

@ -0,0 +1,113 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import pytest
from pre_commit_hooks.mixed_line_ending import main
@pytest.mark.parametrize(
('input_s', 'output'),
(
# mixed with majority of 'LF'
(b'foo\r\nbar\nbaz\n', b'foo\nbar\nbaz\n'),
# mixed with majority of 'CRLF'
(b'foo\r\nbar\nbaz\r\n', b'foo\r\nbar\r\nbaz\r\n'),
# mixed with majority of 'CR'
(b'foo\rbar\nbaz\r', b'foo\rbar\rbaz\r'),
# mixed with as much 'LF' as 'CRLF'
(b'foo\r\nbar\n', b'foo\nbar\n'),
# mixed with as much 'LF' as 'CR'
(b'foo\rbar\n', b'foo\nbar\n'),
# mixed with as much 'CRLF' as 'CR'
(b'foo\r\nbar\r', b'foo\r\nbar\r\n'),
# mixed with as much 'CRLF' as 'LF' as 'CR'
(b'foo\r\nbar\nbaz\r', b'foo\nbar\nbaz\n'),
),
)
def test_mixed_line_ending_fixes_auto(input_s, output, tmpdir):
path = tmpdir.join('file.txt')
path.write_binary(input_s)
ret = main((path.strpath,))
assert ret == 1
assert path.read_binary() == output
def test_non_mixed_no_newline_end_of_file(tmpdir):
path = tmpdir.join('f.txt')
path.write_binary(b'foo\nbar\nbaz')
assert not main((path.strpath,))
# the hook *could* fix the end of the file, but leaves it alone
# this is mostly to document the current behaviour
assert path.read_binary() == b'foo\nbar\nbaz'
def test_mixed_no_newline_end_of_file(tmpdir):
path = tmpdir.join('f.txt')
path.write_binary(b'foo\r\nbar\nbaz')
assert main((path.strpath,))
# the hook rewrites the end of the file, this is slightly inconsistent
# with the non-mixed case but I think this is the better behaviour
# this is mostly to document the current behaviour
assert path.read_binary() == b'foo\nbar\nbaz\n'
@pytest.mark.parametrize(
('fix_option', 'input_s'),
(
# All --fix=auto with uniform line endings should be ok
('--fix=auto', b'foo\r\nbar\r\nbaz\r\n'),
('--fix=auto', b'foo\rbar\rbaz\r'),
('--fix=auto', b'foo\nbar\nbaz\n'),
# --fix=crlf with crlf endings
('--fix=crlf', b'foo\r\nbar\r\nbaz\r\n'),
# --fix=lf with lf endings
('--fix=lf', b'foo\nbar\nbaz\n'),
),
)
def test_line_endings_ok(fix_option, input_s, tmpdir):
path = tmpdir.join('input.txt')
path.write_binary(input_s)
ret = main((fix_option, path.strpath))
assert ret == 0
assert path.read_binary() == input_s
def test_no_fix_does_not_modify(tmpdir):
path = tmpdir.join('input.txt')
contents = b'foo\r\nbar\rbaz\nwomp\n'
path.write_binary(contents)
ret = main(('--fix=no', path.strpath))
assert ret == 1
assert path.read_binary() == contents
def test_fix_lf(tmpdir):
path = tmpdir.join('input.txt')
path.write_binary(b'foo\r\nbar\rbaz\n')
ret = main(('--fix=lf', path.strpath))
assert ret == 1
assert path.read_binary() == b'foo\nbar\nbaz\n'
def test_fix_crlf(tmpdir):
path = tmpdir.join('input.txt')
path.write_binary(b'foo\r\nbar\rbaz\n')
ret = main(('--fix=crlf', path.strpath))
assert ret == 1
assert path.read_binary() == b'foo\r\nbar\r\nbaz\r\n'
def test_fix_lf_all_crlf(tmpdir):
"""Regression test for #239"""
path = tmpdir.join('input.txt')
path.write_binary(b'foo\r\nbar\r\n')
ret = main(('--fix=lf', path.strpath))
assert ret == 1
assert path.read_binary() == b'foo\nbar\n'

View file

@ -0,0 +1,59 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import pytest
from pre_commit_hooks.no_commit_to_branch import is_on_branch
from pre_commit_hooks.no_commit_to_branch import main
from pre_commit_hooks.util import cmd_output
def test_other_branch(temp_git_dir):
with temp_git_dir.as_cwd():
cmd_output('git', 'checkout', '-b', 'anotherbranch')
assert is_on_branch(('master',)) is False
def test_multi_branch(temp_git_dir):
with temp_git_dir.as_cwd():
cmd_output('git', 'checkout', '-b', 'another/branch')
assert is_on_branch(('master',)) is False
def test_multi_branch_fail(temp_git_dir):
with temp_git_dir.as_cwd():
cmd_output('git', 'checkout', '-b', 'another/branch')
assert is_on_branch(('another/branch',)) is True
def test_master_branch(temp_git_dir):
with temp_git_dir.as_cwd():
assert is_on_branch(('master',)) is True
def test_main_branch_call(temp_git_dir):
with temp_git_dir.as_cwd():
cmd_output('git', 'checkout', '-b', 'other')
assert main(('--branch', 'other')) == 1
@pytest.mark.parametrize('branch_name', ('b1', 'b2'))
def test_forbid_multiple_branches(temp_git_dir, branch_name):
with temp_git_dir.as_cwd():
cmd_output('git', 'checkout', '-b', branch_name)
assert main(('--branch', 'b1', '--branch', 'b2'))
def test_main_default_call(temp_git_dir):
with temp_git_dir.as_cwd():
cmd_output('git', 'checkout', '-b', 'anotherbranch')
assert main(()) == 0
def test_not_on_a_branch(temp_git_dir):
with temp_git_dir.as_cwd():
cmd_output('git', 'commit', '--no-gpg-sign', '--allow-empty', '-m1')
head = cmd_output('git', 'rev-parse', 'HEAD').strip()
cmd_output('git', 'checkout', head)
# we're not on a branch!
assert main(()) == 0

View file

@ -1,53 +1,66 @@
import shutil import shutil
import pytest import pytest
from six import PY2
from pre_commit_hooks.pretty_format_json import parse_indent from pre_commit_hooks.pretty_format_json import parse_num_to_int
from pre_commit_hooks.pretty_format_json import pretty_format_json from pre_commit_hooks.pretty_format_json import pretty_format_json
from testing.util import get_resource_path from testing.util import get_resource_path
def test_parse_indent(): def test_parse_num_to_int():
assert parse_indent('0') == '' assert parse_num_to_int('0') == 0
assert parse_indent('2') == ' ' assert parse_num_to_int('2') == 2
assert parse_indent('\t') == '\t' assert parse_num_to_int('\t') == '\t'
with pytest.raises(ValueError): assert parse_num_to_int(' ') == ' '
parse_indent('a')
with pytest.raises(ValueError):
parse_indent('-2')
@pytest.mark.parametrize(('filename', 'expected_retval'), ( @pytest.mark.parametrize(
('not_pretty_formatted_json.json', 1), ('filename', 'expected_retval'), (
('unsorted_pretty_formatted_json.json', 1), ('not_pretty_formatted_json.json', 1),
('pretty_formatted_json.json', 0), ('unsorted_pretty_formatted_json.json', 1),
)) ('non_ascii_pretty_formatted_json.json', 1),
('pretty_formatted_json.json', 0),
),
)
def test_pretty_format_json(filename, expected_retval): def test_pretty_format_json(filename, expected_retval):
ret = pretty_format_json([get_resource_path(filename)]) ret = pretty_format_json([get_resource_path(filename)])
assert ret == expected_retval assert ret == expected_retval
@pytest.mark.parametrize(('filename', 'expected_retval'), ( @pytest.mark.parametrize(
('not_pretty_formatted_json.json', 1), ('filename', 'expected_retval'), (
('unsorted_pretty_formatted_json.json', 0), ('not_pretty_formatted_json.json', 1),
('pretty_formatted_json.json', 0), ('unsorted_pretty_formatted_json.json', 0),
)) ('non_ascii_pretty_formatted_json.json', 1),
('pretty_formatted_json.json', 0),
),
)
def test_unsorted_pretty_format_json(filename, expected_retval): def test_unsorted_pretty_format_json(filename, expected_retval):
ret = pretty_format_json(['--no-sort-keys', get_resource_path(filename)]) ret = pretty_format_json(['--no-sort-keys', get_resource_path(filename)])
assert ret == expected_retval assert ret == expected_retval
@pytest.mark.parametrize(('filename', 'expected_retval'), ( @pytest.mark.skipif(PY2, reason="Requires Python3")
('not_pretty_formatted_json.json', 1), @pytest.mark.parametrize(
('unsorted_pretty_formatted_json.json', 1), ('filename', 'expected_retval'), (
('pretty_formatted_json.json', 1), ('not_pretty_formatted_json.json', 1),
('tab_pretty_formatted_json.json', 0), ('unsorted_pretty_formatted_json.json', 1),
)) ('non_ascii_pretty_formatted_json.json', 1),
def test_tab_pretty_format_json(filename, expected_retval): ('pretty_formatted_json.json', 1),
('tab_pretty_formatted_json.json', 0),
),
)
def test_tab_pretty_format_json(filename, expected_retval): # pragma: no cover
ret = pretty_format_json(['--indent', '\t', get_resource_path(filename)]) ret = pretty_format_json(['--indent', '\t', get_resource_path(filename)])
assert ret == expected_retval assert ret == expected_retval
def test_non_ascii_pretty_format_json():
ret = pretty_format_json(['--no-ensure-ascii', get_resource_path('non_ascii_pretty_formatted_json.json')])
assert ret == 0
def test_autofix_pretty_format_json(tmpdir): def test_autofix_pretty_format_json(tmpdir):
srcfile = tmpdir.join('to_be_json_formatted.json') srcfile = tmpdir.join('to_be_json_formatted.json')
shutil.copyfile( shutil.copyfile(

View file

@ -7,7 +7,9 @@ import yaml
def test_readme_contains_all_hooks(): def test_readme_contains_all_hooks():
readme_contents = io.open('README.md').read() with io.open('README.md', encoding='UTF-8') as f:
hooks = yaml.load(io.open('hooks.yaml').read()) readme_contents = f.read()
with io.open('.pre-commit-hooks.yaml', encoding='UTF-8') as f:
hooks = yaml.load(f)
for hook in hooks: for hook in hooks:
assert '`{0}`'.format(hook['id']) in readme_contents assert '`{}`'.format(hook['id']) in readme_contents

View file

@ -1,31 +1,45 @@
import pytest import pytest
from pre_commit_hooks.requirements_txt_fixer import FAIL
from pre_commit_hooks.requirements_txt_fixer import fix_requirements_txt from pre_commit_hooks.requirements_txt_fixer import fix_requirements_txt
from pre_commit_hooks.requirements_txt_fixer import PASS
from pre_commit_hooks.requirements_txt_fixer import Requirement from pre_commit_hooks.requirements_txt_fixer import Requirement
# Input, expected return value, expected output
TESTS = ( @pytest.mark.parametrize(
(b'foo\nbar\n', 1, b'bar\nfoo\n'), ('input_s', 'expected_retval', 'output'),
(b'bar\nfoo\n', 0, b'bar\nfoo\n'), (
(b'#comment1\nfoo\n#comment2\nbar\n', 1, b'#comment2\nbar\n#comment1\nfoo\n'), (b'', PASS, b''),
(b'#comment1\nbar\n#comment2\nfoo\n', 0, b'#comment1\nbar\n#comment2\nfoo\n'), (b'\n', PASS, b'\n'),
(b'#comment\n\nfoo\nbar\n', 1, b'#comment\n\nbar\nfoo\n'), (b'# intentionally empty\n', PASS, b'# intentionally empty\n'),
(b'#comment\n\nbar\nfoo\n', 0, b'#comment\n\nbar\nfoo\n'), (b'foo\n# comment at end\n', PASS, b'foo\n# comment at end\n'),
(b'\nfoo\nbar\n', 1, b'bar\n\nfoo\n'), (b'foo\nbar\n', FAIL, b'bar\nfoo\n'),
(b'\nbar\nfoo\n', 0, b'\nbar\nfoo\n'), (b'bar\nfoo\n', PASS, b'bar\nfoo\n'),
(b'pyramid==1\npyramid-foo==2\n', 0, b'pyramid==1\npyramid-foo==2\n'), (b'#comment1\nfoo\n#comment2\nbar\n', FAIL, b'#comment2\nbar\n#comment1\nfoo\n'),
(b'ocflib\nDjango\nPyMySQL\n', 1, b'Django\nocflib\nPyMySQL\n'), (b'#comment1\nbar\n#comment2\nfoo\n', PASS, b'#comment1\nbar\n#comment2\nfoo\n'),
(b'-e git+ssh://git_url@tag#egg=ocflib\nDjango\nPyMySQL\n', 1, b'Django\n-e git+ssh://git_url@tag#egg=ocflib\nPyMySQL\n'), (b'#comment\n\nfoo\nbar\n', FAIL, b'#comment\n\nbar\nfoo\n'),
(b'#comment\n\nbar\nfoo\n', PASS, b'#comment\n\nbar\nfoo\n'),
(b'\nfoo\nbar\n', FAIL, b'bar\n\nfoo\n'),
(b'\nbar\nfoo\n', PASS, b'\nbar\nfoo\n'),
(b'pyramid==1\npyramid-foo==2\n', PASS, b'pyramid==1\npyramid-foo==2\n'),
(b'ocflib\nDjango\nPyMySQL\n', FAIL, b'Django\nocflib\nPyMySQL\n'),
(
b'-e git+ssh://git_url@tag#egg=ocflib\nDjango\nPyMySQL\n',
FAIL,
b'Django\n-e git+ssh://git_url@tag#egg=ocflib\nPyMySQL\n',
),
(b'bar\npkg-resources==0.0.0\nfoo\n', FAIL, b'bar\nfoo\n'),
(b'foo\npkg-resources==0.0.0\nbar\n', FAIL, b'bar\nfoo\n'),
),
) )
@pytest.mark.parametrize(('input_s', 'expected_retval', 'output'), TESTS)
def test_integration(input_s, expected_retval, output, tmpdir): def test_integration(input_s, expected_retval, output, tmpdir):
path = tmpdir.join('file.txt') path = tmpdir.join('file.txt')
path.write_binary(input_s) path.write_binary(input_s)
assert fix_requirements_txt([path.strpath]) == expected_retval output_retval = fix_requirements_txt([path.strpath])
assert path.read_binary() == output assert path.read_binary() == output
assert output_retval == expected_retval
def test_requirement_object(): def test_requirement_object():

View file

@ -0,0 +1,120 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import os
import pytest
from pre_commit_hooks.sort_simple_yaml import first_key
from pre_commit_hooks.sort_simple_yaml import main
from pre_commit_hooks.sort_simple_yaml import parse_block
from pre_commit_hooks.sort_simple_yaml import parse_blocks
from pre_commit_hooks.sort_simple_yaml import sort
RETVAL_GOOD = 0
RETVAL_BAD = 1
TEST_SORTS = [
(
['c: true', '', 'b: 42', 'a: 19'],
['b: 42', 'a: 19', '', 'c: true'],
RETVAL_BAD,
),
(
['# i am', '# a header', '', 'c: true', '', 'b: 42', 'a: 19'],
['# i am', '# a header', '', 'b: 42', 'a: 19', '', 'c: true'],
RETVAL_BAD,
),
(
['# i am', '# a header', '', 'already: sorted', '', 'yup: i am'],
['# i am', '# a header', '', 'already: sorted', '', 'yup: i am'],
RETVAL_GOOD,
),
(
['# i am', '# a header'],
['# i am', '# a header'],
RETVAL_GOOD,
),
]
@pytest.mark.parametrize('bad_lines,good_lines,retval', TEST_SORTS)
def test_integration_good_bad_lines(tmpdir, bad_lines, good_lines, retval):
file_path = os.path.join(tmpdir.strpath, 'foo.yaml')
with open(file_path, 'w') as f:
f.write("\n".join(bad_lines) + "\n")
assert main([file_path]) == retval
with open(file_path, 'r') as f:
assert [line.rstrip() for line in f.readlines()] == good_lines
def test_parse_header():
lines = ['# some header', '# is here', '', 'this is not a header']
assert parse_block(lines, header=True) == ['# some header', '# is here']
assert lines == ['', 'this is not a header']
lines = ['this is not a header']
assert parse_block(lines, header=True) == []
assert lines == ['this is not a header']
def test_parse_block():
# a normal block
lines = ['a: 42', 'b: 17', '', 'c: 19']
assert parse_block(lines) == ['a: 42', 'b: 17']
assert lines == ['', 'c: 19']
# a block at the end
lines = ['c: 19']
assert parse_block(lines) == ['c: 19']
assert lines == []
# no block
lines = []
assert parse_block(lines) == []
assert lines == []
def test_parse_blocks():
# normal blocks
lines = ['a: 42', 'b: 17', '', 'c: 19']
assert parse_blocks(lines) == [['a: 42', 'b: 17'], ['c: 19']]
assert lines == []
# a single block
lines = ['a: 42', 'b: 17']
assert parse_blocks(lines) == [['a: 42', 'b: 17']]
assert lines == []
# no blocks
lines = []
assert parse_blocks(lines) == []
assert lines == []
def test_first_key():
# first line
lines = ['a: 42', 'b: 17', '', 'c: 19']
assert first_key(lines) == 'a: 42'
# second line
lines = ['# some comment', 'a: 42', 'b: 17', '', 'c: 19']
assert first_key(lines) == 'a: 42'
# second line with quotes
lines = ['# some comment', '"a": 42', 'b: 17', '', 'c: 19']
assert first_key(lines) == 'a": 42'
# no lines
lines = []
assert first_key(lines) is None
@pytest.mark.parametrize('bad_lines,good_lines,_', TEST_SORTS)
def test_sort(bad_lines, good_lines, _):
assert sort(bad_lines) == good_lines

View file

@ -22,16 +22,20 @@ TESTS = (
# Docstring # Docstring
('""" Foo """', '""" Foo """', 0), ('""" Foo """', '""" Foo """', 0),
( (
textwrap.dedent(""" textwrap.dedent(
"""
x = " \\ x = " \\
foo \\ foo \\
"\n "\n
"""), """,
textwrap.dedent(""" ),
textwrap.dedent(
"""
x = ' \\ x = ' \\
foo \\ foo \\
'\n '\n
"""), """,
),
1, 1,
), ),
('"foo""bar"', "'foo''bar'", 1), ('"foo""bar"', "'foo''bar'", 1),

View file

@ -12,7 +12,7 @@ def test_validate_files_one_fails():
def test_validate_files_django_all_pass(): def test_validate_files_django_all_pass():
ret = validate_files(['--django', 'test_foo.py', 'test_bar.py', 'tests/test_baz.py']) ret = validate_files(['--django', 'tests.py', 'test_foo.py', 'test_bar.py', 'tests/test_baz.py'])
assert ret == 0 assert ret == 0

View file

@ -3,7 +3,7 @@ from __future__ import unicode_literals
import pytest import pytest
from pre_commit_hooks.trailing_whitespace_fixer import fix_trailing_whitespace from pre_commit_hooks.trailing_whitespace_fixer import main
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -16,14 +16,22 @@ from pre_commit_hooks.trailing_whitespace_fixer import fix_trailing_whitespace
def test_fixes_trailing_whitespace(input_s, expected, tmpdir): def test_fixes_trailing_whitespace(input_s, expected, tmpdir):
path = tmpdir.join('file.txt') path = tmpdir.join('file.txt')
path.write(input_s) path.write(input_s)
assert fix_trailing_whitespace((path.strpath,)) == 1 assert main((path.strpath,)) == 1
assert path.read() == expected assert path.read() == expected
def test_ok_no_newline_end_of_file(tmpdir):
filename = tmpdir.join('f')
filename.write_binary(b'foo\nbar')
ret = main((filename.strpath,))
assert filename.read_binary() == b'foo\nbar'
assert ret == 0
def test_ok_with_dos_line_endings(tmpdir): def test_ok_with_dos_line_endings(tmpdir):
filename = tmpdir.join('f') filename = tmpdir.join('f')
filename.write_binary(b'foo\r\nbar\r\nbaz\r\n') filename.write_binary(b'foo\r\nbar\r\nbaz\r\n')
ret = fix_trailing_whitespace((filename.strpath,)) ret = main((filename.strpath,))
assert filename.read_binary() == b'foo\r\nbar\r\nbaz\r\n' assert filename.read_binary() == b'foo\r\nbar\r\nbaz\r\n'
assert ret == 0 assert ret == 0
@ -31,14 +39,14 @@ def test_ok_with_dos_line_endings(tmpdir):
def test_markdown_ok(tmpdir): def test_markdown_ok(tmpdir):
filename = tmpdir.join('foo.md') filename = tmpdir.join('foo.md')
filename.write_binary(b'foo \n') filename.write_binary(b'foo \n')
ret = fix_trailing_whitespace((filename.strpath,)) ret = main((filename.strpath,))
assert filename.read_binary() == b'foo \n' assert filename.read_binary() == b'foo \n'
assert ret == 0 assert ret == 0
# filename, expected input, expected output # filename, expected input, expected output
MD_TESTS_1 = ( MD_TESTS_1 = (
('foo.md', 'foo \nbar \n ', 'foo \nbar\n\n'), ('foo.md', 'foo \nbar \n ', 'foo \nbar\n'),
('bar.Markdown', 'bar \nbaz\t\n\t\n', 'bar \nbaz\n\n'), ('bar.Markdown', 'bar \nbaz\t\n\t\n', 'bar \nbaz\n\n'),
('.md', 'baz \nquux \t\n\t\n', 'baz\nquux\n\n'), ('.md', 'baz \nquux \t\n\t\n', 'baz\nquux\n\n'),
('txt', 'foo \nbaz \n\t\n', 'foo\nbaz\n\n'), ('txt', 'foo \nbaz \n\t\n', 'foo\nbaz\n\n'),
@ -49,7 +57,7 @@ MD_TESTS_1 = (
def test_fixes_trailing_markdown_whitespace(filename, input_s, output, tmpdir): def test_fixes_trailing_markdown_whitespace(filename, input_s, output, tmpdir):
path = tmpdir.join(filename) path = tmpdir.join(filename)
path.write(input_s) path.write(input_s)
ret = fix_trailing_whitespace([path.strpath]) ret = main([path.strpath])
assert ret == 1 assert ret == 1
assert path.read() == output assert path.read() == output
@ -68,16 +76,14 @@ MD_TESTS_2 = (
def test_markdown_linebreak_ext_opt(filename, input_s, output, tmpdir): def test_markdown_linebreak_ext_opt(filename, input_s, output, tmpdir):
path = tmpdir.join(filename) path = tmpdir.join(filename)
path.write(input_s) path.write(input_s)
ret = fix_trailing_whitespace(( ret = main(('--markdown-linebreak-ext=TxT', path.strpath))
'--markdown-linebreak-ext=TxT', path.strpath
))
assert ret == 1 assert ret == 1
assert path.read() == output assert path.read() == output
# filename, expected input, expected output # filename, expected input, expected output
MD_TESTS_3 = ( MD_TESTS_3 = (
('foo.baz', 'foo \nbar \n ', 'foo \nbar\n\n'), ('foo.baz', 'foo \nbar \n ', 'foo \nbar\n'),
('bar', 'bar \nbaz\t\n\t\n', 'bar \nbaz\n\n'), ('bar', 'bar \nbaz\t\n\t\n', 'bar \nbaz\n\n'),
) )
@ -87,9 +93,7 @@ def test_markdown_linebreak_ext_opt_all(filename, input_s, output, tmpdir):
path = tmpdir.join(filename) path = tmpdir.join(filename)
path.write(input_s) path.write(input_s)
# need to make sure filename is not treated as argument to option # need to make sure filename is not treated as argument to option
ret = fix_trailing_whitespace([ ret = main(('--markdown-linebreak-ext=*', path.strpath))
'--markdown-linebreak-ext=*', path.strpath,
])
assert ret == 1 assert ret == 1
assert path.read() == output assert path.read() == output
@ -97,7 +101,7 @@ def test_markdown_linebreak_ext_opt_all(filename, input_s, output, tmpdir):
@pytest.mark.parametrize(('arg'), ('--', 'a.b', 'a/b')) @pytest.mark.parametrize(('arg'), ('--', 'a.b', 'a/b'))
def test_markdown_linebreak_ext_badopt(arg): def test_markdown_linebreak_ext_badopt(arg):
with pytest.raises(SystemExit) as excinfo: with pytest.raises(SystemExit) as excinfo:
fix_trailing_whitespace(['--markdown-linebreak-ext', arg]) main(['--markdown-linebreak-ext', arg])
assert excinfo.value.code == 2 assert excinfo.value.code == 2
@ -112,19 +116,15 @@ MD_TESTS_4 = (
def test_no_markdown_linebreak_ext_opt(filename, input_s, output, tmpdir): def test_no_markdown_linebreak_ext_opt(filename, input_s, output, tmpdir):
path = tmpdir.join(filename) path = tmpdir.join(filename)
path.write(input_s) path.write(input_s)
ret = fix_trailing_whitespace(['--no-markdown-linebreak-ext', path.strpath]) ret = main(['--no-markdown-linebreak-ext', path.strpath])
assert ret == 1 assert ret == 1
assert path.read() == output assert path.read() == output
def test_returns_zero_for_no_changes():
assert fix_trailing_whitespace([__file__]) == 0
def test_preserve_non_utf8_file(tmpdir): def test_preserve_non_utf8_file(tmpdir):
non_utf8_bytes_content = b'<a>\xe9 \n</a>\n' non_utf8_bytes_content = b'<a>\xe9 \n</a>\n'
path = tmpdir.join('file.txt') path = tmpdir.join('file.txt')
path.write_binary(non_utf8_bytes_content) path.write_binary(non_utf8_bytes_content)
ret = fix_trailing_whitespace([path.strpath]) ret = main([path.strpath])
assert ret == 1 assert ret == 1
assert path.size() == (len(non_utf8_bytes_content) - 1) assert path.size() == (len(non_utf8_bytes_content) - 1)

15
tox.ini
View file

@ -1,7 +1,6 @@
[tox] [tox]
project = pre_commit_hooks
# These should match the travis env list # These should match the travis env list
envlist = py27,py34,py35,pypy envlist = py27,py35,py36,pypy
[testenv] [testenv]
deps = -rrequirements-dev.txt deps = -rrequirements-dev.txt
@ -12,18 +11,14 @@ setenv =
GIT_AUTHOR_EMAIL = "test@example.com" GIT_AUTHOR_EMAIL = "test@example.com"
GIT_COMMITTER_EMAIL = "test@example.com" GIT_COMMITTER_EMAIL = "test@example.com"
commands = commands =
# coverage erase coverage erase
# coverage run -m pytest {posargs:tests} coverage run -m pytest {posargs:tests}
# coverage report --show-missing --fail-under 100 coverage report --fail-under 100
pre-commit install -f --install-hooks pre-commit install -f --install-hooks
pre-commit run --all-files pre-commit run --all-files
[testenv:venv]
envdir = venv-{[tox]project}
commands =
[flake8] [flake8]
max-line-length=131 max-line-length=131
[pep8] [pep8]
ignore=E265,E309,E501 ignore=E265,E501