or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdhooks-extensions.mdindex.mdmain-api.mdrepository-handling.mdtemplate-processing.mduser-interaction.mdutilities-exceptions.md

hooks-extensions.mddocs/

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

```