Repository: pre-commit/pre-commit-hooks
Pull request #133 (2016-08-12 16:59:10)

Allow encoding pragma to be customizable
See also #131.  CC @ushuz

coveralls: 
[![Coverage Status](https://coveralls.io/builds/7425735/badge)](https://coveralls.io/builds/7425735)

Coverage remained the same at 100.0% when pulling **f10d02723d0ae5fc15c384c06b76800cb403f7c9 on customizable_encoding_pragma** into **3fa02652357ff0dbb42b5bc78c673b7bc105fcf3 on master**.


Patch:
diff --git a/hooks.yaml b/hooks.yaml
index 0e1259e..08f01c5 100644
--- a/hooks.yaml
+++ b/hooks.yaml
@@ -140,4 +140,4 @@
     description: This hook trims trailing whitespace.
     entry: trailing-whitespace-fixer
     language: python
-    files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|h|haml|hh|hpp|hxx|html|in|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scss|sh|slim|tmpl|txt|yaml|yml)$
+    files: \.(c|cpp|html|erb|slim|haml|ejs|jade|js|coffee|json|rb|md|py|css|scss|less|sh|tmpl|txt|yaml|yml|pp)$
diff --git a/pre_commit_hooks/debug_statement_hook.py b/pre_commit_hooks/debug_statement_hook.py
index 52fe72e..8e02480 100644
--- a/pre_commit_hooks/debug_statement_hook.py
+++ b/pre_commit_hooks/debug_statement_hook.py
@@ -7,7 +7,7 @@ import collections
 import traceback
 
 
-DEBUG_STATEMENTS = set(['pdb', 'ipdb', 'pudb', 'q', 'rdb'])
+DEBUG_STATEMENTS = set(['pdb', 'ipdb', 'pudb', 'q'])
 
 
 DebugStatement = collections.namedtuple(
diff --git a/pre_commit_hooks/fix_encoding_pragma.py b/pre_commit_hooks/fix_encoding_pragma.py
index 8586937..5dcff93 100644
--- a/pre_commit_hooks/fix_encoding_pragma.py
+++ b/pre_commit_hooks/fix_encoding_pragma.py
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
 import argparse
 import collections
 
-expected_pragma = b'# -*- coding: utf-8 -*-\n'
+DEFAULT_PRAGMA = b'# -*- coding: utf-8 -*-\n'
 
 
 def has_coding(line):
@@ -41,7 +41,7 @@ class ExpectedContents(collections.namedtuple(
         return self.pragma_status is expected_pragma_status
 
 
-def _get_expected_contents(first_line, second_line, rest):
+def _get_expected_contents(first_line, second_line, rest, expected_pragma):
     if first_line.startswith(b'#!'):
         shebang = first_line
         potential_coding = second_line
@@ -63,8 +63,10 @@ def _get_expected_contents(first_line, second_line, rest):
     )
 
 
-def fix_encoding_pragma(f, remove=False):
-    expected = _get_expected_contents(f.readline(), f.readline(), f.read())
+def fix_encoding_pragma(f, remove=False, expected_pragma=DEFAULT_PRAGMA):
+    expected = _get_expected_contents(
+        f.readline(), f.readline(), f.read(), expected_pragma,
+    )
 
     # Special cases for empty files
     if not expected.rest.strip():
@@ -91,9 +93,25 @@ def fix_encoding_pragma(f, remove=False):
     return 1
 
 
+def _normalize_pragma(pragma):
+    if not isinstance(pragma, bytes):
+        pragma = pragma.encode('UTF-8')
+    return pragma.rstrip() + b'\n'
+
+
+def _to_disp(pragma):
+    return pragma.decode().rstrip()
+
+
 def main(argv=None):
     parser = argparse.ArgumentParser('Fixes the encoding pragma of python files')
     parser.add_argument('filenames', nargs='*', help='Filenames to fix')
+    parser.add_argument(
+        '--pragma', default=DEFAULT_PRAGMA, type=_normalize_pragma,
+        help='The encoding pragma to use.  Default: {}'.format(
+            _to_disp(DEFAULT_PRAGMA),
+        ),
+    )
     parser.add_argument(
         '--remove', action='store_true',
         help='Remove the encoding pragma (Useful in a python3-only codebase)',
@@ -109,10 +127,14 @@ def main(argv=None):
 
     for filename in args.filenames:
         with open(filename, 'r+b') as f:
-            file_ret = fix_encoding_pragma(f, remove=args.remove)
+            file_ret = fix_encoding_pragma(
+                f, remove=args.remove, expected_pragma=args.pragma,
+            )
             retv |= file_ret
             if file_ret:
-                print(fmt.format(pragma=expected_pragma, filename=filename))
+                print(fmt.format(
+                    pragma=_to_disp(args.pragma), filename=filename,
+                ))
 
     return retv
 
diff --git a/pre_commit_hooks/requirements_txt_fixer.py b/pre_commit_hooks/requirements_txt_fixer.py
index 64b5e47..bf7f75d 100644
--- a/pre_commit_hooks/requirements_txt_fixer.py
+++ b/pre_commit_hooks/requirements_txt_fixer.py
@@ -10,13 +10,6 @@ class Requirement(object):
         self.value = None
         self.comments = []
 
-    @property
-    def name(self):
-        if self.value.startswith(b'-e '):
-            return self.value.lower().partition(b'=')[-1]
-
-        return self.value.lower().partition(b'==')[0]
-
     def __lt__(self, requirement):
         # \n means top of file comment, so always return True,
         # otherwise just do a string comparison with value.
@@ -25,7 +18,10 @@ class Requirement(object):
         elif requirement.value == b'\n':
             return False
         else:
-            return self.name < requirement.name
+            return (
+                self.value.lower().partition(b'==') <
+                requirement.value.lower().partition(b'==')
+            )
 
 
 def fix_requirements(f):


Test patch:
diff --git a/tests/fix_encoding_pragma_test.py b/tests/fix_encoding_pragma_test.py
index a9502a2..d49f1ba 100644
--- a/tests/fix_encoding_pragma_test.py
+++ b/tests/fix_encoding_pragma_test.py
@@ -5,6 +5,7 @@ import io
 
 import pytest
 
+from pre_commit_hooks.fix_encoding_pragma import _normalize_pragma
 from pre_commit_hooks.fix_encoding_pragma import fix_encoding_pragma
 from pre_commit_hooks.fix_encoding_pragma import main
 
@@ -106,3 +107,46 @@ def test_not_ok_inputs(input_str, output):
     assert fix_encoding_pragma(bytesio) == 1
     bytesio.seek(0)
     assert bytesio.read() == output
+
+
+def test_ok_input_alternate_pragma():
+    input_s = b'# coding: utf-8\nx = 1\n'
+    bytesio = io.BytesIO(input_s)
+    ret = fix_encoding_pragma(bytesio, expected_pragma=b'# coding: utf-8\n')
+    assert ret == 0
+    bytesio.seek(0)
+    assert bytesio.read() == input_s
+
+
+def test_not_ok_input_alternate_pragma():
+    bytesio = io.BytesIO(b'x = 1\n')
+    ret = fix_encoding_pragma(bytesio, expected_pragma=b'# coding: utf-8\n')
+    assert ret == 1
+    bytesio.seek(0)
+    assert bytesio.read() == b'# coding: utf-8\nx = 1\n'
+
+
+@pytest.mark.parametrize(
+    ('input_s', 'expected'),
+    (
+        # Python 2 cli parameters are bytes
+        (b'# coding: utf-8', b'# coding: utf-8\n'),
+        # Python 3 cli parameters are text
+        ('# coding: utf-8', b'# coding: utf-8\n'),
+        # trailing whitespace
+        ('# coding: utf-8\n', b'# coding: utf-8\n'),
+    ),
+)
+def test_normalize_pragma(input_s, expected):
+    assert _normalize_pragma(input_s) == expected
+
+
+def test_integration_alternate_pragma(tmpdir, capsys):
+    f = tmpdir.join('f.py')
+    f.write('x = 1\n')
+
+    pragma = '# coding: utf-8'
+    assert main((f.strpath, '--pragma', pragma)) == 1
+    assert f.read() == '# coding: utf-8\nx = 1\n'
+    out, _ = capsys.readouterr()
+    assert out == 'Added `# coding: utf-8` to {}\n'.format(f.strpath)
diff --git a/tests/requirements_txt_fixer_test.py b/tests/requirements_txt_fixer_test.py
index 1c590a5..fe51f4a 100644
--- a/tests/requirements_txt_fixer_test.py
+++ b/tests/requirements_txt_fixer_test.py
@@ -15,7 +15,6 @@ TESTS = (
     (b'\nbar\nfoo\n', 0, b'\nbar\nfoo\n'),
     (b'pyramid==1\npyramid-foo==2\n', 0, b'pyramid==1\npyramid-foo==2\n'),
     (b'ocflib\nDjango\nPyMySQL\n', 1, b'Django\nocflib\nPyMySQL\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'),
 )