diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 9396d55..c5c56ee 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -173,6 +173,12 @@ entry: requirements-txt-fixer language: python files: requirements.*\.txt$ +- id: remove-tabs + name: Check docstring is first + description: Remove tabs from files and replaces them with spaces. + entry: remove-tabs + language: python + types: [text] - id: sort-simple-yaml name: Sort simple YAML files language: python diff --git a/README.md b/README.md index 1a152de..ef09d5e 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ Add this to your `.pre-commit-config.yaml` - `--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. - `requirements-txt-fixer` - Sorts entries in requirements.txt and removes incorrect entry for `pkg-resources==0.0.0` +- `remove-tabs` - Replace tabs by whitespaces before committing - `sort-simple-yaml` - Sorts simple YAML files which consist only of top-level keys, preserving comments and blocks. - `trailing-whitespace` - Trims trailing whitespace. - To preserve Markdown [hard linebreaks](https://github.github.com/gfm/#hard-line-break) diff --git a/pre_commit_hooks/remove_tabs.py b/pre_commit_hooks/remove_tabs.py new file mode 100644 index 0000000..342dc2b --- /dev/null +++ b/pre_commit_hooks/remove_tabs.py @@ -0,0 +1,53 @@ +from __future__ import print_function + +import argparse +import sys +from typing import Any + + +def contains_tabs(filename): # type: (str) -> bool + with open(filename, mode='rb') as file_checked: + return b'\t' in file_checked.read() + + +def removes_tabs_in_file(filename, whitespaces_count): + # type: (str, int) -> None + with open(filename, mode='rb') as file_processed: + lines = file_processed.readlines() + lines = [line.replace(b'\t', b' ' * whitespaces_count) for line in lines] + with open(filename, mode='wb') as file_processed: + for line in lines: + file_processed.write(line) + + +def main(argv=None): # type: (Any) -> int + parser = argparse.ArgumentParser() + parser.add_argument( + '--whitespaces-count', type=int, required=True, + help='number of whitespaces to substitute tabs with', + ) + parser.add_argument('filenames', nargs='*', help='Filenames to fix') + + args = parser.parse_args(argv) + + return_code = 0 + for filename in args.filenames: + if contains_tabs(filename): + print('Substituting tabs in: {} by {} whitespaces'.format( + filename, args.whitespaces_count, + )) + removes_tabs_in_file(filename, args.whitespaces_count) + return_code = 1 + + if return_code: + print('') + print('Tabs have been successfully removed. Now aborting the commit.') + print( + 'You can check the changes made. Then simply ' + '"git add --update ." and re-commit', + ) + return return_code + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tests/remove_tabs_test.py b/tests/remove_tabs_test.py new file mode 100644 index 0000000..24befc4 --- /dev/null +++ b/tests/remove_tabs_test.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from __future__ import unicode_literals + +import pytest + +from pre_commit_hooks.remove_tabs import main as remove_tabs + + +@pytest.mark.parametrize( + ('input_s', 'expected'), + ( + ('foo \t\nbar', 'foo \nbar'), + ('bar\n\tbaz\n', 'bar\n baz\n'), + ), +) +def test_remove_tabs(input_s, expected, tmpdir): + path = tmpdir.join('file.txt') + path.write(input_s) + assert remove_tabs(('--whitespaces-count=4', path.strpath)) == 1 + assert path.read() == expected + + +@pytest.mark.parametrize(('arg'), ('', '--', 'a.b', 'a/b')) +def test_badopt(arg): + with pytest.raises(SystemExit) as excinfo: + remove_tabs(['--whitespaces-count', arg]) + assert excinfo.value.code == 2 + + +def test_nothing_to_fix(): + assert remove_tabs(['--whitespaces-count=4', __file__]) == 0