0
# Hooks and Extensions
1
2
Pre/post generation hook system and Jinja2 template extensions for enhanced templating capabilities. This module provides extensibility through custom scripts and enhanced Jinja2 template functionality.
3
4
## Capabilities
5
6
### Hook System
7
8
Execute custom scripts before and after project generation.
9
10
```python { .api }
11
def run_hook(hook_name, project_dir, context):
12
"""
13
Find and execute hook from project directory.
14
15
Parameters:
16
- hook_name: str - Name of hook to run ('pre_gen_project' or 'post_gen_project')
17
- project_dir: str - Generated project directory path
18
- context: dict - Template context available to hook script
19
"""
20
21
def run_hook_from_repo_dir(repo_dir, hook_name, project_dir, context, delete_project_on_failure):
22
"""
23
Run hook from repo directory with cleanup.
24
25
Parameters:
26
- repo_dir: str - Template repository directory
27
- hook_name: str - Hook name to execute
28
- project_dir: str - Generated project directory
29
- context: dict - Template context
30
- delete_project_on_failure: bool - Whether to cleanup on hook failure
31
"""
32
33
def run_pre_prompt_hook(repo_dir):
34
"""
35
Run pre_prompt hook from repo directory.
36
37
Parameters:
38
- repo_dir: str - Template repository directory
39
40
Returns:
41
str - Path to repository directory (may be modified by hook)
42
"""
43
```
44
45
### Hook Discovery and Validation
46
47
Find and validate hook scripts in template directories.
48
49
```python { .api }
50
def valid_hook(hook_file, hook_name):
51
"""
52
Determine if hook file is valid.
53
54
Parameters:
55
- hook_file: str - Path to hook file
56
- hook_name: str - Expected hook name
57
58
Returns:
59
bool - True if hook file is valid and executable
60
"""
61
62
def find_hook(hook_name, hooks_dir='hooks'):
63
"""
64
Find hook scripts in directory.
65
66
Parameters:
67
- hook_name: str - Name of hook to find
68
- hooks_dir: str - Directory to search for hooks
69
70
Returns:
71
str or None - Path to hook script if found
72
"""
73
```
74
75
### Script Execution
76
77
Execute hook scripts with proper context and error handling.
78
79
```python { .api }
80
def run_script(script_path, cwd='.'):
81
"""
82
Execute script from working directory.
83
84
Parameters:
85
- script_path: str - Path to script file
86
- cwd: str - Working directory for script execution
87
"""
88
89
def run_script_with_context(script_path, cwd, context):
90
"""
91
Execute script after Jinja rendering.
92
93
Parameters:
94
- script_path: str - Path to script template file
95
- cwd: str - Working directory for execution
96
- context: dict - Context for rendering script template
97
"""
98
```
99
100
## Jinja2 Extensions
101
102
Enhanced templating capabilities through custom Jinja2 extensions.
103
104
### JSON and Data Extensions
105
106
```python { .api }
107
class JsonifyExtension(Extension):
108
"""Converts Python objects to JSON."""
109
110
class RandomStringExtension(Extension):
111
"""Creates random ASCII strings."""
112
```
113
114
### String Processing Extensions
115
116
```python { .api }
117
class SlugifyExtension(Extension):
118
"""Slugifies strings for use in URLs and filenames."""
119
120
class UUIDExtension(Extension):
121
"""Generates UUID4 strings."""
122
```
123
124
### Date and Time Extensions
125
126
```python { .api }
127
class TimeExtension(Extension):
128
"""Handles dates and times with 'now' tag."""
129
```
130
131
### Environment Classes
132
133
Enhanced Jinja2 environments for template processing.
134
135
```python { .api }
136
class ExtensionLoaderMixin:
137
"""Mixin for loading Jinja2 extensions from context."""
138
139
class StrictEnvironment(ExtensionLoaderMixin, Environment):
140
"""Strict Jinja2 environment that raises errors on undefined variables."""
141
```
142
143
## Hook Constants
144
145
```python { .api }
146
EXIT_SUCCESS: int # Success exit status (0)
147
```
148
149
## Usage Examples
150
151
### Hook Implementation
152
153
Create hook scripts in your template's `hooks/` directory:
154
155
**hooks/pre_gen_project.py:**
156
```python
157
#!/usr/bin/env python
158
"""Pre-generation hook script."""
159
160
import sys
161
162
# Validate user input
163
project_name = '{{cookiecutter.project_name}}'
164
if not project_name.replace('-', '').replace('_', '').isalnum():
165
print('ERROR: Project name must be alphanumeric (with hyphens/underscores)')
166
sys.exit(1)
167
168
print(f'✓ Pre-generation validation passed for: {project_name}')
169
```
170
171
**hooks/post_gen_project.py:**
172
```python
173
#!/usr/bin/env python
174
"""Post-generation hook script."""
175
176
import os
177
import subprocess
178
179
# Initialize git repository
180
if '{{cookiecutter.initialize_git}}' == 'yes':
181
subprocess.run(['git', 'init'], check=True)
182
subprocess.run(['git', 'add', '.'], check=True)
183
subprocess.run(['git', 'commit', '-m', 'Initial commit'], check=True)
184
print('✓ Git repository initialized')
185
186
# Install dependencies
187
if '{{cookiecutter.install_dependencies}}' == 'yes':
188
subprocess.run(['pip', 'install', '-e', '.'], check=True)
189
print('✓ Dependencies installed')
190
```
191
192
### Using Hooks Programmatically
193
194
```python
195
from cookiecutter.hooks import run_hook, run_pre_prompt_hook, find_hook
196
197
# Run pre-prompt hook
198
repo_dir = './my-template'
199
modified_repo_dir = run_pre_prompt_hook(repo_dir)
200
201
# Find and validate hooks
202
pre_hook = find_hook('pre_gen_project', hooks_dir='./template/hooks')
203
if pre_hook:
204
print(f"Found pre-generation hook: {pre_hook}")
205
206
# Run post-generation hook
207
context = {
208
'cookiecutter': {
209
'project_name': 'my-project',
210
'initialize_git': 'yes',
211
'install_dependencies': 'no'
212
}
213
}
214
215
run_hook(
216
hook_name='post_gen_project',
217
project_dir='./generated-project',
218
context=context
219
)
220
```
221
222
### Jinja2 Extensions Usage
223
224
Templates can use enhanced Jinja2 functionality:
225
226
**Template file example:**
227
```jinja2
228
{# Generate unique identifier #}
229
PROJECT_ID = "{{ cookiecutter.project_name | uuid4 }}"
230
231
{# Create URL-friendly slug #}
232
URL_SLUG = "{{ cookiecutter.project_name | slugify }}"
233
234
{# Generate random secret key #}
235
SECRET_KEY = "{{ cookiecutter.project_name | random_ascii_string(50) }}"
236
237
{# Convert complex data to JSON #}
238
CONFIG = {{ cookiecutter.database_config | jsonify }}
239
240
{# Current timestamp #}
241
CREATED_AT = "{% now 'utc', '%Y-%m-%d %H:%M:%S' %}"
242
```
243
244
### Custom Environment Setup
245
246
```python
247
from cookiecutter.environment import StrictEnvironment
248
from cookiecutter.extensions import JsonifyExtension, SlugifyExtension
249
250
# Create environment with custom extensions
251
env = StrictEnvironment()
252
env.add_extension(JsonifyExtension)
253
env.add_extension(SlugifyExtension)
254
255
# Use environment for template rendering
256
template = env.from_string('{{ project_name | slugify }}')
257
result = template.render(project_name='My Awesome Project')
258
# Returns: 'my-awesome-project'
259
```
260
261
### Advanced Hook Usage
262
263
```python
264
from cookiecutter.hooks import run_hook_from_repo_dir, valid_hook
265
import os
266
267
# Validate hook before execution
268
hook_path = './template/hooks/pre_gen_project.py'
269
if os.path.exists(hook_path) and valid_hook(hook_path, 'pre_gen_project'):
270
print("Hook is valid and executable")
271
272
# Run hook with cleanup on failure
273
try:
274
run_hook_from_repo_dir(
275
repo_dir='./template',
276
hook_name='pre_gen_project',
277
project_dir='./output/my-project',
278
context=context,
279
delete_project_on_failure=True
280
)
281
except Exception as e:
282
print(f"Hook execution failed: {e}")
283
```
284
285
### Extension Usage in Python
286
287
```python
288
from cookiecutter.extensions import (
289
JsonifyExtension,
290
SlugifyExtension,
291
RandomStringExtension,
292
UUIDExtension,
293
TimeExtension
294
)
295
from jinja2 import Environment
296
297
# Set up environment with all extensions
298
env = Environment()
299
env.add_extension(JsonifyExtension)
300
env.add_extension(SlugifyExtension)
301
env.add_extension(RandomStringExtension)
302
env.add_extension(UUIDExtension)
303
env.add_extension(TimeExtension)
304
305
# Example template using extensions
306
template = env.from_string("""
307
Project: {{ name | slugify }}
308
ID: {{ name | uuid4 }}
309
Secret: {{ name | random_ascii_string(32) }}
310
Config: {{ config | jsonify }}
311
Created: {% now 'utc', '%Y-%m-%d' %}
312
""")
313
314
result = template.render(
315
name="My Cool Project",
316
config={"debug": True, "port": 8000}
317
)
318
```
319
320
### Hook Error Handling
321
322
```python
323
from cookiecutter.hooks import run_hook
324
from cookiecutter.exceptions import FailedHookException
325
326
try:
327
run_hook(
328
hook_name='post_gen_project',
329
project_dir='./my-project',
330
context=context
331
)
332
except FailedHookException as e:
333
print(f"Hook failed: {e}")
334
# Optionally clean up generated files
335
import shutil
336
shutil.rmtree('./my-project')
337
```
338
339
### Dynamic Hook Discovery
340
341
```python
342
from cookiecutter.hooks import find_hook
343
import os
344
345
hooks_dir = './template/hooks'
346
hook_types = ['pre_prompt', 'pre_gen_project', 'post_gen_project']
347
348
available_hooks = {}
349
for hook_type in hook_types:
350
hook_path = find_hook(hook_type, hooks_dir)
351
if hook_path:
352
available_hooks[hook_type] = hook_path
353
354
print("Available hooks:")
355
for hook_type, path in available_hooks.items():
356
print(f" {hook_type}: {path}")
357
```