Repository: pre-commit/pre-commit
Pull request #716 (2018-02-26 09:20:46)

Add multiline mode to pygrep
As a follow-up on https://github.com/pre-commit/pre-commit/issues/634, I'm using today pcre hooks with `-z` option, which is the multiline mode, I'd like to add support for this to be able to move to pygrep hooks. I used the same argument name as `grep` does


Patch:
diff --git a/pre_commit/languages/pygrep.py b/pre_commit/languages/pygrep.py
index 878f57d..7eead9e 100644
--- a/pre_commit/languages/pygrep.py
+++ b/pre_commit/languages/pygrep.py
@@ -27,6 +27,23 @@ def _process_filename_by_line(pattern, filename):
     return retv
 
 
+def _process_filename_at_once(pattern, filename):
+    retv = 0
+    with open(filename, 'rb') as f:
+        contents = f.read()
+        match = pattern.search(contents)
+        if match:
+            retv = 1
+            line_no = contents[:match.start()].count(b'\n')
+            output.write('{}:{}:'.format(filename, line_no + 1))
+
+            matched_lines = match.group().split(b'\n')
+            matched_lines[0] = contents.split(b'\n')[line_no]
+
+            output.write_line(b'\n'.join(matched_lines))
+    return retv
+
+
 def run_hook(prefix, hook, file_args):
     exe = (sys.executable, '-m', __name__)
     exe += tuple(hook['args']) + (hook['entry'],)
@@ -42,16 +59,23 @@ def main(argv=None):
         ),
     )
     parser.add_argument('-i', '--ignore-case', action='store_true')
+    parser.add_argument('--multiline', action='store_true')
     parser.add_argument('pattern', help='python regex pattern.')
     parser.add_argument('filenames', nargs='*')
     args = parser.parse_args(argv)
 
     flags = re.IGNORECASE if args.ignore_case else 0
+    if args.multiline:
+        flags |= re.MULTILINE | re.DOTALL
+
     pattern = re.compile(args.pattern.encode(), flags)
 
     retv = 0
     for filename in args.filenames:
-        retv |= _process_filename_by_line(pattern, filename)
+        if args.multiline:
+            retv |= _process_filename_at_once(pattern, filename)
+        else:
+            retv |= _process_filename_by_line(pattern, filename)
     return retv
 
 


Test patch:
diff --git a/tests/languages/pygrep_test.py b/tests/languages/pygrep_test.py
index 048a590..d91363e 100644
--- a/tests/languages/pygrep_test.py
+++ b/tests/languages/pygrep_test.py
@@ -38,3 +38,31 @@ def test_ignore_case(some_files, cap_out):
     out = cap_out.get()
     assert ret == 1
     assert out == 'f2:1:[INFO] hi\n'
+
+
+def test_multiline(some_files, cap_out):
+    ret = pygrep.main(('--multiline', r'foo\nbar', 'f1', 'f2', 'f3'))
+    out = cap_out.get()
+    assert ret == 1
+    assert out == 'f1:1:foo\nbar\n'
+
+
+def test_multiline_line_number(some_files, cap_out):
+    ret = pygrep.main(('--multiline', r'ar', 'f1', 'f2', 'f3'))
+    out = cap_out.get()
+    assert ret == 1
+    assert out == 'f1:2:bar\n'
+
+
+def test_multiline_dotall_flag_is_enabled(some_files, cap_out):
+    ret = pygrep.main(('--multiline', r'o.*bar', 'f1', 'f2', 'f3'))
+    out = cap_out.get()
+    assert ret == 1
+    assert out == 'f1:1:foo\nbar\n'
+
+
+def test_multiline_multiline_flag_is_enabled(some_files, cap_out):
+    ret = pygrep.main(('--multiline', r'foo$.*bar', 'f1', 'f2', 'f3'))
+    out = cap_out.get()
+    assert ret == 1
+    assert out == 'f1:1:foo\nbar\n'