Repository: barbosa/clorox

Issue:
Add option to remove all leading whitespace
I really enjoy Clorox and we even enforce it's usage using the [danger plugin](https://github.com/barbosa/danger-clorox), however I have some files with trailing whitespaces/newlines before the actual code so when I run it these files start being flagged as with errors due to  the [SwiftLint Leading Whitespace rule](https://github.com/realm/SwiftLint/blob/master/Source/SwiftLintFramework/Rules/LeadingWhitespaceRule.swift).

How do you feel about adding an option to enable the complete removal of leading whitespace?

Thanks for the awesome job so far! 🎉 

Comments:

Patch:
diff --git a/clorox/clorox.py b/clorox/clorox.py
index b194817..0d0910e 100755
--- a/clorox/clorox.py
+++ b/clorox/clorox.py
@@ -44,7 +44,7 @@ class Clorox:
 
     def _process_file(self, file_path):
         self.all_files.append(file_path)
-        has_header, updated_content = self._has_xcode_header(file_path)
+        has_header, updated_content = self._find_xcode_header(file_path)
         if has_header:
             succeeded = True
             if not self.args.inspection:
@@ -53,19 +53,14 @@ class Clorox:
             self.modified_files.append(file_path)
             self.printer.print_file(file_path, succeeded)
 
-    def _has_xcode_header(self, file_path):
+    def _find_xcode_header(self, file_path):
         with open(file_path, 'r') as file:
-            content = file.readlines()
-            header_height = Matcher.HEADER_TEMPLATE.count('\n')
-            for line in range(header_height, len(content)):
-                if content[line] == '\n':
-                    header_height = header_height + 1
-                else:
-                    break
-
-            header = ''.join(content[:header_height])
-            updated_content = content[header_height:]
-        return Matcher(header).matches(), updated_content
+            content = ''.join(file.readlines())
+        header = Matcher(content, trim_new_lines=self.args.trim).match()
+        if header is None:
+            return False, None
+
+        return True, content.replace(header, '')
 
     def _remove_header(self, file_path, updated_content):
         try:
@@ -78,10 +73,16 @@ class Clorox:
 
 def main():
     parser = argparse.ArgumentParser()
-    parser.add_argument('-p', '--path', nargs='+', required=True)
-    parser.add_argument('-i', '--inspection', dest='inspection', action='store_true')
-    parser.add_argument('-q', '--quiet', dest='quiet', action='store_true')
-    parser.add_argument('-r', '--reporter', choices=['json'])
+    parser.add_argument('-p', '--path', nargs='+', required=True,
+        help='directory of file to run clorox')
+    parser.add_argument('-t', '--trim', dest='trim', action='store_true',
+        default=True, help='trim new lines around header')
+    parser.add_argument('-i', '--inspection', dest='inspection',
+        action='store_true', help='do not change files (only inspect them)')
+    parser.add_argument('-q', '--quiet', dest='quiet', action='store_true',
+        help='do not print any output')
+    parser.add_argument('-r', '--reporter', choices=['json'],
+        help='render output using a custom report')
     args = parser.parse_args()
 
     if not args.path:
diff --git a/clorox/matcher.py b/clorox/matcher.py
index 83ac73a..1f10fa3 100644
--- a/clorox/matcher.py
+++ b/clorox/matcher.py
@@ -5,7 +5,7 @@ import re
 
 class Matcher:
 
-    HEADER_TEMPLATE = (r""
+    _DEFAULT_HEADER_TEMPLATE = (r""
         "\/\/\n"
         "\/\/.*\..*\n"
         "\/\/.*\n"
@@ -15,8 +15,18 @@ class Matcher:
         "\/\/\n"
     )
 
-    def __init__(self, header):
-        self.header = header
+    def __init__(self, content, trim_new_lines=False):
+        self.content = content
+        self.trim_new_lines = trim_new_lines
 
-    def matches(self):
-        return re.match(self.HEADER_TEMPLATE, self.header) is not None
+    @property
+    def header(self):
+        trim_regex = r"\s*" if self.trim_new_lines else r""
+        return r"{trim_regex}{core}{trim_regex}".format(
+            trim_regex=trim_regex,
+            core=self._DEFAULT_HEADER_TEMPLATE
+        )
+
+    def match(self):
+        result = re.match(self.header, self.content)
+        return result.group(0) if result else None
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..469e4c7
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,3 @@
+-r requirements.txt
+nose
+ipdb


Test patch:
diff --git a/tests/test_matcher.py b/tests/test_matcher.py
index 6665fff..4694a94 100644
--- a/tests/test_matcher.py
+++ b/tests/test_matcher.py
@@ -17,7 +17,7 @@ class MatcherTestCase(unittest.TestCase):
         "//  Copyright (c) 2015 MyCompany. All rights reserved.\n"
         "//\n")
 
-        assert Matcher(header).matches()
+        assert Matcher(header).match()
 
     def test_matcher_with_1_digit_month(self):
         header = (""
@@ -29,7 +29,7 @@ class MatcherTestCase(unittest.TestCase):
         "//  Copyright (c) 2015 MyCompany. All rights reserved.\n"
         "//\n")
 
-        assert Matcher(header).matches()
+        assert Matcher(header).match()
 
     def test_matcher_with_1_digit_day(self):
         header = (""
@@ -41,7 +41,7 @@ class MatcherTestCase(unittest.TestCase):
         "//  Copyright (c) 2015 MyCompany. All rights reserved.\n"
         "//\n")
 
-        assert Matcher(header).matches()
+        assert Matcher(header).match()
 
     def test_matcher_with_objc_header_file(self):
         header = (""
@@ -53,7 +53,7 @@ class MatcherTestCase(unittest.TestCase):
         "//  Copyright (c) 2015 MyCompany. All rights reserved.\n"
         "//\n")
 
-        assert Matcher(header).matches()
+        assert Matcher(header).match()
 
     def test_matcher_with_objc_implementation_file(self):
         header = (""
@@ -65,7 +65,7 @@ class MatcherTestCase(unittest.TestCase):
         "//  Copyright (c) 2015 MyCompany. All rights reserved.\n"
         "//\n")
 
-        assert Matcher(header).matches()
+        assert Matcher(header).match()
 
     def test_matcher_with_objc_implementation_file(self):
         header = (""
@@ -77,7 +77,7 @@ class MatcherTestCase(unittest.TestCase):
         "//  Copyright (c) 2015 MyCompany. All rights reserved.\n"
         "//\n")
 
-        assert Matcher(header).matches()
+        assert Matcher(header).match()
 
     def test_matcher_with_special_copyright_character(self):
         header = (""
@@ -89,7 +89,39 @@ class MatcherTestCase(unittest.TestCase):
         "//  Copyright © 2015 MyCompany. All rights reserved.\n"
         "//\n")
 
-        assert Matcher(header).matches()
+        assert Matcher(header).match()
+
+    def test_matcher_with_trim_new_lines_on(self):
+        header = (""
+        "\n"
+        "\n"
+        "//\n"
+        "//  MyFile.m\n"
+        "//  MyCompany\n"
+        "//\n"
+        "//  Created by John Appleseed on 12/18/15.\n"
+        "//  Copyright © 2015 MyCompany. All rights reserved.\n"
+        "//\n"
+        "\n"
+        "\n")
+
+        assert Matcher(header, trim_new_lines=True).match()
+
+    def test_matcher_with_trim_new_lines_off(self):
+        header = (""
+        "\n"
+        "\n"
+        "//\n"
+        "//  MyFile.m\n"
+        "//  MyCompany\n"
+        "//\n"
+        "//  Created by John Appleseed on 12/18/15.\n"
+        "//  Copyright © 2015 MyCompany. All rights reserved.\n"
+        "//\n"
+        "\n"
+        "\n")
+
+        assert not Matcher(header, trim_new_lines=False).match()
 
 if __name__ == '__main__':
     unittest.main()