Repository: cookiecutter/cookiecutter

Issue:
Wrong hook executed when a tilde-suffixed file of the same name exists
- Cookiecutter version: 1.4.0
- Template project url: https://github.com/thorgate/django-project-template
- Python version: 3.4
- Operating System: Ubuntu 15.10 wily
### Description:

When using gedit or some other text editor that pollutes the directory with backup files ending with a tilde, cookiecutter mistakes that for the "real" hook it should run. This resulted in cookiecutter running a ridiculously outdated version of my pre-gen hook.

The obvious solution is to just remove `pre_gen_project.py~`, which works, but I believe ideally cookiecutter shouldn't be running it in the first place.
### What I've run:

```
gedit django-template/hooks/pre_gen_project.py
cookiecutter django-template
```



Discussion:
The problem is that cookiecutter treats any file with a basename of `pre_gen_project` as a hook. If it has extension `.py` it runs it via Python, but otherwise it just lets the OS run it. This allows for Windows to use hooks with extensions `.bat`, `.cmd`, `.ps1`, etc, as well as letting Unix users add extensions like `.sh`, or `.rb` (with a suitable shebang).

It's hard to know what extensions should be disallowed. Maybe skip any files with names ending in `~` as that's a common convention for old backup files?

That's what I also thought should be done. I also forked and wrote a quick patch for it, but I read the PR guide and apparently you need tests and whatnot, which I currently don't have the time to write.


Resolving patch:
diff --git a/cookiecutter/hooks.py b/cookiecutter/hooks.py
--- a/cookiecutter/hooks.py
+++ b/cookiecutter/hooks.py
@@ -37,16 +37,20 @@ def find_hooks():
     Missing scripts will not be included in the returned dict.
     """
     hooks_dir = 'hooks'
-    r = {}
+    hooks = {}
     logging.debug('hooks_dir is {0}'.format(hooks_dir))
+
     if not os.path.isdir(hooks_dir):
         logging.debug('No hooks/ dir in template_dir')
-        return r
+        return hooks
+
     for f in os.listdir(hooks_dir):
-        basename = os.path.splitext(os.path.basename(f))[0]
-        if basename in _HOOKS:
-            r[basename] = os.path.abspath(os.path.join(hooks_dir, f))
-    return r
+        filename = os.path.basename(f)
+        basename = os.path.splitext(filename)[0]
+
+        if basename in _HOOKS and not filename.endswith('~'):
+            hooks[basename] = os.path.abspath(os.path.join(hooks_dir, f))
+    return hooks
 
 
 def run_script(script_path, cwd='.'):


Test patch:
diff --git a/tests/hooks-backup-files/hooks/post_gen_project.py~ b/tests/hooks-backup-files/hooks/post_gen_project.py~
new file mode 100644
--- /dev/null
+++ b/tests/hooks-backup-files/hooks/post_gen_project.py~
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import logging
+logging.basicConfig(level=logging.DEBUG)
+logger = logging.getLogger('post_gen_project')
+logger.info('post_gen_project.py~')
diff --git a/tests/hooks-backup-files/hooks/pre_gen_project.py~ b/tests/hooks-backup-files/hooks/pre_gen_project.py~
new file mode 100644
--- /dev/null
+++ b/tests/hooks-backup-files/hooks/pre_gen_project.py~
@@ -0,0 +1,8 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import logging
+
+logging.basicConfig(level=logging.DEBUG)
+logger = logging.getLogger('pre_gen_project')
+logger.info('pre_gen_project.py~')
diff --git a/tests/test_hooks.py b/tests/test_hooks.py
--- a/tests/test_hooks.py
+++ b/tests/test_hooks.py
@@ -7,10 +7,10 @@
 Tests for `cookiecutter.hooks` module.
 """
 
-import sys
 import os
-import stat
 import pytest
+import stat
+import sys
 
 from cookiecutter import hooks, utils, exceptions
 
@@ -179,3 +179,23 @@ def test_run_failing_hook(self):
             with pytest.raises(exceptions.FailedHookException) as excinfo:
                 hooks.run_hook('pre_gen_project', tests_dir, {})
             assert 'Hook script failed' in str(excinfo.value)
+
+
+@pytest.fixture
+def hook_backup_files():
+    """Return basenames of all files in the hooks dir."""
+    return sorted([
+        os.path.basename(f) for f in
+        os.listdir('tests/hooks-backup-files/hooks')
+    ])
+
+
+def test_ignore_hook_backup_files(monkeypatch, hook_backup_files):
+    # This makes sure that the files are actually in the directory
+    assert hook_backup_files == [
+        'post_gen_project.py~',
+        'pre_gen_project.py~',
+    ]
+
+    monkeypatch.chdir('tests/hooks-backup-files/')
+    assert hooks.find_hooks() == {}