Repository: cookiecutter/cookiecutter

Issue:
Need help with generating GUID/UUID values for context variables
* Cookiecutter version: 1.6
* Template project url: none
* Python version: 3.7 (virtual env created using win python 3.7 x64)
* Operating System: Windows 10, 64 Bit

### Description:

First off many thanks for creating this project !
Here is some context of what I am trying to do and where I need some guidance
* I am trying to use CC to generate new a Visual Studio 2017 solution and project files with a particular folder/file organization that I like
* I was able to most of it working but for the below:
* Parts of the above project, solution files involves generating several unique GUIDs
* my first approach was creating a `pre_gen_project.py` inside the `hooks` folder and update/create new variables that could be added to the ones loaded from `cookiecutter.json` or entered by the user
* I was however blocked as I could not figure out how to access the context being used by CC and the jinja2 engine 
* I proceeded to go over the many issues on github and found some related ones like the following: #60, #102, #180, #288 but no clear answer on how to achieve what I'd like
* I also followed some others issues that suggested creating custom jinja2 extension/filter (#944) but I couldnt figure out how or where to put them in the template folder so the cookiecutter.exe can identify them and pick them up
* Lastly, I also tried going over the CC source code and tried to create a new executable from my script (similar to `cli.py`) that passes the guids via the `extra_context` to `cookiecutter.main(...)` but ran into some other problems that I am still trying to figure out

Appreciate any pointers on how I can inject GUID values for the context variables


Discussion:
Computing new Jinja context variables in the pre hook would be a great feature.
Having a pre-hook for computing values that are injected into the context for use in templates would be very useful.
Was also looking to do this exact thing. Fixing the vcxproj in a post hook script is not too hard, but I have other cases where I need to generate unique ids in unstructured files (cpp).

I was surprised when I started using cookiecutter that the scripts are rendered and ran from temp folder, rather than simply importing the script and giving access to the context dict. Seems like that would make it much simpler and more powerful!
As a solution, what I do is: in `pre_gen_project.py` I generate my ids, and write them to a tmp file inside the template. Then I `{%- include tmp/IDFILE.txt -%}` where needed. In `post_gen_project.py` I cleanup the tmp folder.
@agrant3d could you show a bit more of your code? I used `pre_gen_project.py` to write a file at `tmp/id1.txt` and I am doing `{%- include 'tmp/id1.txt' =%}` from file `a/b/c.xml` and its giving me a `TemplateNotFound` exception

Sure, looks something like this:
```python
import os
import uuid

CURDIR = os.path.realpath(os.path.curdir)
TEMPLATEDIR = os.path.normpath(os.path.join(CURDIR, '..', '{{cookiecutter._template}}'))
TMPDIR = os.path.join(TEMPLATEDIR, '{{'{{'}}cookiecutter.project_slug{{'}}'}}', 'tmp')

GUIDS = ['PROJECT_GUID', 'SOLUTION_GUID']
for guid in GUIDS:
    file_path = os.path.join(TMPDIR, guid + '.txt')
    with open(file_path, 'w') as f:
        f.write("{" + str(uuid.uuid4()).upper() + "}")
```
The thing you're probably missing is that you need to put the files inside the {{cookiecutter.project_slug}} directory. 
Note - this method only works when you are working with a local copy of the cookiecutter template. If it's a cached version - you cannot simply write to the directory.
worked a dream, thanks @agrant3d 

I am trying to write some Lambda CookieCutter projects because AWS SAM uses this project. Though I do like a good challenge, I've not had a fun time at it. Hoping someone can help. 

I need to take the `project_name` and derive folder/file names, as well as Ruby class names to match in my project. I really wish #944 was merged and I'd be able to use some Python inflector code and do what I need in the templates. Of course #60 would have been helpful too. But I've been forced to try the method above using pre/post hooks to write files to include. I cant get it working. I keep ending up with a `jinja2.exceptions.TemplateNotFound: tmp/class_name.txt` error.

> Note - this method only works when you are working with a local copy of the cookiecutter template. If it's a cached version - you cannot simply write to the directory.

So my `cookiecutter._tempalte` value is `gh:customink/lambda-starter-ruby`. My guess is that I'd have to write to `~/.cookiecutters/lambda-starter-ruby` where the cloned repo is for this to work? 
Confirmed on putting these tmp files in the `~/.cookiecutters` directory. My `pre_gen_project.py` file is basically the inflector code I needed followed by this.

```python
project_name  = '{{cookiecutter.project_name}}'
cc_template   = '{{cookiecutter._template}}'
template_name = cc_template.split("/")[-1]

file_name  = singularize(tableize(project_name))
class_name = singularize(camelize(tableize(project_name)))
dash_name  = singularize(dasherize(project_name))
all_names  = {
  'file_name': file_name,
  'class_name': class_name,
  'dash_name': dash_name
}

import os
import pathlib
from os.path import expanduser
home_dir = expanduser("~")
template_dir = os.path.join(home_dir, '.cookiecutters', template_name)
temp_dir = os.path.join(template_dir, '{{'{{'}}cookiecutter.project_name{{'}}'}}', '_cctmp')
pathlib.Path(temp_dir).mkdir(parents=True, exist_ok=True)

for name in all_names.keys():
  file_path = os.path.join(temp_dir, name + '.txt')
  with open(file_path, 'w') as f:
    f.write(all_names[name])
```

Resolving patch:
diff --git a/cookiecutter/environment.py b/cookiecutter/environment.py
--- a/cookiecutter/environment.py
+++ b/cookiecutter/environment.py
@@ -26,6 +26,7 @@ def __init__(self, **kwargs):
             'cookiecutter.extensions.JsonifyExtension',
             'cookiecutter.extensions.RandomStringExtension',
             'cookiecutter.extensions.SlugifyExtension',
+            'cookiecutter.extensions.UUIDExtension',
             'jinja2_time.TimeExtension',
         ]
         extensions = default_extensions + self._read_extensions(context)
diff --git a/cookiecutter/extensions.py b/cookiecutter/extensions.py
--- a/cookiecutter/extensions.py
+++ b/cookiecutter/extensions.py
@@ -1,6 +1,7 @@
 """Jinja2 extensions."""
 import json
 import string
+import uuid
 from secrets import choice
 
 from jinja2.ext import Extension
@@ -49,3 +50,17 @@ def slugify(value, **kwargs):
             return pyslugify(value, **kwargs)
 
         environment.filters['slugify'] = slugify
+
+
+class UUIDExtension(Extension):
+    """Jinja2 Extension to generate uuid4 string."""
+
+    def __init__(self, environment):
+        """Jinja2 Extension constructor."""
+        super(UUIDExtension, self).__init__(environment)
+
+        def uuid4():
+            """Generate UUID4."""
+            return str(uuid.uuid4())
+
+        environment.globals.update(uuid4=uuid4)


Test patch:
diff --git a/tests/test-extensions/default/{{cookiecutter.project_slug}}/id b/tests/test-extensions/default/{{cookiecutter.project_slug}}/id
new file mode 100644
--- /dev/null
+++ b/tests/test-extensions/default/{{cookiecutter.project_slug}}/id
@@ -0,0 +1 @@
+{{ uuid4() }}
\ No newline at end of file
diff --git a/tests/test_default_extensions.py b/tests/test_default_extensions.py
--- a/tests/test_default_extensions.py
+++ b/tests/test_default_extensions.py
@@ -3,6 +3,7 @@
 
 import freezegun
 import pytest
+import uuid
 
 from cookiecutter.main import cookiecutter
 
@@ -46,3 +47,18 @@ def test_jinja2_slugify_extension(tmpdir):
     )
 
     assert os.path.basename(project_dir) == "it-s-slugified-foobar"
+
+
+def test_jinja2_uuid_extension(tmpdir):
+    """Verify Jinja2 uuid extension work correctly."""
+    project_dir = cookiecutter(
+        'tests/test-extensions/default/', no_input=True, output_dir=str(tmpdir)
+    )
+    changelog_file = os.path.join(project_dir, 'id')
+    assert os.path.isfile(changelog_file)
+
+    with open(changelog_file, 'r', encoding='utf-8') as f:
+        changelog_lines = f.readlines()
+
+    uuid.UUID(changelog_lines[0], version=4)
+    assert True
diff --git a/tests/test_environment.py b/tests/test_environment.py
--- a/tests/test_environment.py
+++ b/tests/test_environment.py
@@ -22,3 +22,4 @@ def test_env_should_come_with_default_extensions():
     assert 'cookiecutter.extensions.JsonifyExtension' in env.extensions
     assert 'cookiecutter.extensions.RandomStringExtension' in env.extensions
     assert 'cookiecutter.extensions.SlugifyExtension' in env.extensions
+    assert 'cookiecutter.extensions.UUIDExtension' in env.extensions