Repository: macisamuele/language-formatters-pre-commit-hooks

Issue:
in windows，can not download google format jar
error：
**PermissionError: [WinError 32] 另一个程序正在使用此文件，进程无法访问。**
means file not close

in utils：
```
    with tempfile.NamedTemporaryFile(delete=False) as tmp_file:  # Not delete because we're renaming it
        shutil.copyfileobj(r.raw, tmp_file)
        tmp_file.flush()
        os.fsync(tmp_file.fileno())
        tmp_file.close()  # add this is ok
        os.rename(tmp_file.name, final_file)
```


Comments:

Patch:
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..0042f4e
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+# Ensure that go files are forced to have "\n" as new line, regardless of the platform.
+# More context on: https://github.com/macisamuele/language-formatters-pre-commit-hooks/pull/29
+*.go text eol=lf
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 973aabe..eab6fe5 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -15,7 +15,8 @@ jobs:
       fail-fast: false
       matrix:
         pyversion: [2.7, 3.6, 3.7]
-    runs-on: ubuntu-latest
+        os: [macos-latest, ubuntu-latest, windows-latest]
+    runs-on: ${{ matrix.os }}
 
     steps:
     - name: Checkout Repo
@@ -39,8 +40,6 @@ jobs:
       uses: actions/setup-go@v2
       with:
         go-version: 1.15.2
-    - name: Create cache directory as pre-commit would do
-      run: mkdir -p ${HOME}/.cache/pre-commit
     - name: Install Python dependencies
       run: pip install codecov tox tox-gh-actions
     - name: Run Tox
@@ -83,8 +82,6 @@ jobs:
       uses: actions/setup-go@v2
       with:
         go-version: 1.15.2
-    - name: Create cache directory as pre-commit would do
-      run: mkdir -p ${HOME}/.cache/pre-commit
     - name: Install Python dependencies
       run: pip install codecov tox
     - name: Run Tox
diff --git a/language_formatters_pre_commit_hooks/pretty_format_golang.py b/language_formatters_pre_commit_hooks/pretty_format_golang.py
index 0b52011..bf4f1d9 100644
--- a/language_formatters_pre_commit_hooks/pretty_format_golang.py
+++ b/language_formatters_pre_commit_hooks/pretty_format_golang.py
@@ -10,6 +10,29 @@ from language_formatters_pre_commit_hooks.pre_conditions import golang_required
 from language_formatters_pre_commit_hooks.utils import run_command
 
 
+def _get_eol_attribute():
+    """
+    Retrieve eol attribute defined for golang files
+    The method will return None in case of any error interacting with git
+    """
+    status_code, output = run_command('git check-attr -z eol -- filename.go')
+    if status_code != 0:
+        return None
+
+    try:
+        # Expected output: "filename.go\0eol\0lf\0"
+        _, _, eol, _ = output.split('\0')
+        return eol
+    except:  # noqa: E722 (allow usage of bare 'except')
+        print(
+            '`git check-attr` output is not consistent to `<filename>\0<key>\0<value>\0` format: {output}'.format(
+                output=output,
+            ),
+            file=sys.stderr,
+        )
+        return None
+
+
 @golang_required
 def pretty_format_golang(argv=None):
     parser = argparse.ArgumentParser()
@@ -43,6 +66,15 @@ def pretty_format_golang(argv=None):
                 ', '.join(output.splitlines()),
             ),
         )
+        if sys.platform == 'win32':  # pragma: no cover
+            eol_attribute = _get_eol_attribute()
+            if eol_attribute and eol_attribute != 'lf':
+                print(
+                    'Hint: gofmt uses LF (aka `\\n`) as new line, but on Windows the default new line is CRLF (aka `\\r\\n`). '
+                    'You might want to ensure that go files are forced to use LF via `.gitattributes`. '
+                    'Example: https://github.com/macisamuele/language-formatters-pre-commit-hooks/commit/53f27fda02ead5b1b9b6a9bbd9c36bb66d229887',  # noqa: E501
+                    file=sys.stderr,
+                )
 
     return status
 
diff --git a/language_formatters_pre_commit_hooks/utils.py b/language_formatters_pre_commit_hooks/utils.py
index d54928a..e9d1a84 100644
--- a/language_formatters_pre_commit_hooks/utils.py
+++ b/language_formatters_pre_commit_hooks/utils.py
@@ -31,9 +31,11 @@ def run_command(command):
 def _base_directory():
     # Extracted from pre-commit code:
     # https://github.com/pre-commit/pre-commit/blob/master/pre_commit/store.py
-    return os.environ.get('PRE_COMMIT_HOME') or os.path.join(
-        os.environ.get('XDG_CACHE_HOME') or os.path.expanduser('~/.cache'),
-        'pre-commit',
+    return os.path.realpath(
+        os.environ.get('PRE_COMMIT_HOME') or os.path.join(
+            os.environ.get('XDG_CACHE_HOME') or os.path.expanduser('~/.cache'),
+            'pre-commit',
+        ),
     )
 
 
@@ -55,16 +57,18 @@ def download_url(url, file_name=None):
         # via `pre-commit` as it would ensure that the directories
         # are present
         print('Unexisting base directory ({base_directory}). Creating it'.format(base_directory=base_directory), file=sys.stderr)
-        os.mkdir(base_directory)
+        os.makedirs(base_directory)
 
     print("Downloading {url}".format(url=url), file=sys.stderr)
     r = requests.get(url, stream=True)
     r.raise_for_status()
     with tempfile.NamedTemporaryFile(delete=False) as tmp_file:  # Not delete because we're renaming it
+        tmp_file_name = tmp_file.name
         shutil.copyfileobj(r.raw, tmp_file)
         tmp_file.flush()
         os.fsync(tmp_file.fileno())
-        os.rename(tmp_file.name, final_file)
+
+    os.rename(tmp_file_name, final_file)
 
     return final_file
 


Test patch:
diff --git a/tests/pretty_format_golang_test.py b/tests/pretty_format_golang_test.py
index f910b23..5875b3c 100644
--- a/tests/pretty_format_golang_test.py
+++ b/tests/pretty_format_golang_test.py
@@ -6,7 +6,9 @@ from __future__ import unicode_literals
 import shutil
 
 import pytest
+from mock import patch
 
+from language_formatters_pre_commit_hooks.pretty_format_golang import _get_eol_attribute
 from language_formatters_pre_commit_hooks.pretty_format_golang import pretty_format_golang
 from tests.conftest import change_dir_context
 from tests.conftest import undecorate_function
@@ -46,3 +48,17 @@ def test_pretty_format_golang_autofix(tmpdir, undecorate_method):
     # file was formatted (shouldn't trigger linter again)
     ret = undecorate_method([srcfile.strpath])
     assert ret == 0
+
+
+@pytest.mark.parametrize(
+    'exit_status, output, expected_eol',
+    [
+        (1, '', None),
+        (0, '', None),
+        (0, 'a\0eol\0lf\0', 'lf'),
+    ],
+)
+@patch('language_formatters_pre_commit_hooks.pretty_format_golang.run_command', autospec=True)
+def test__get_eol_attribute(mock_run_command, exit_status, output, expected_eol):
+    mock_run_command.return_value = (exit_status, output)
+    assert _get_eol_attribute() == expected_eol
diff --git a/tests/utils_test.py b/tests/utils_test.py
index 9924209..f47593c 100644
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -4,6 +4,7 @@ from __future__ import print_function
 from __future__ import unicode_literals
 
 import os
+import sys
 from os.path import basename
 
 import mock
@@ -18,8 +19,15 @@ from language_formatters_pre_commit_hooks.utils import run_command
 @pytest.mark.parametrize(
     'command, expected_status, expected_output',
     [
-        ['echo "1"', 0, '1\n'],
-        ['echo "1" | grep 0', 1, ''],
+        ('echo 1', 0, '1{}'.format(os.linesep)),
+        pytest.param(
+            'echo 1 | grep 0', 1, '',
+            marks=pytest.mark.skipif(condition=sys.platform == 'win32', reason='Windows does not have `grep`'),
+        ),
+        pytest.param(
+            'echo 1 | findstr 0', 1, '',
+            marks=pytest.mark.skipif(condition=sys.platform != 'win32', reason='Linux and MacOS does not have `findstr`'),
+        ),
         ['true', 0, ''],
         ['false', 1, ''],
     ],