Skip to content

Latest commit

 

History

History
334 lines (245 loc) · 7.02 KB

File metadata and controls

334 lines (245 loc) · 7.02 KB

Advanced Jinja2 in Cookiecutter

Cookiecutter uses Jinja2 as its template engine. This guide covers the advanced features you can use in file names, content, and cookiecutter.json.


Variables

All cookiecutter.json variables are available as {{ cookiecutter.xxx }}:

{{ cookiecutter.project_name }}
{{ cookiecutter.author_name }}
{{ cookiecutter.version }}

Filters

Jinja2 supports chained filters with |:

Useful Standard Filters

{{ cookiecutter.project_name|lower }}              # "my project"
{{ cookiecutter.project_name|upper }}              # "MY PROJECT"
{{ cookiecutter.project_name|title }}              # "My Project"
{{ cookiecutter.project_name|trim }}               # no leading/trailing spaces
{{ cookiecutter.project_name|replace(' ', '_') }}  # "my_project"
{{ cookiecutter.project_name|replace(' ', '-') }}  # "my-project"
{{ cookiecutter.project_name|length }}             # 11 (number of chars)
{{ cookiecutter.version|replace('.', '') }}        # "010" for v0.1.0

Combining Filters

{{ cookiecutter.project_name|lower|replace(' ', '_')|replace('-', '_') }}

With project_name = "My Awesome-Project":

my_awesome_project

tojson Filter

Converts a value to JSON. Useful in hooks and for generating valid JSON:

{{ cookiecutter.use_docker|tojson }}

With use_docker = "yes""yes" (with JSON quotes).


Conditionals

{% if %} / {% else %} / {% endif %}

# setup.py
setup(
    name="{{ cookiecutter.project_slug }}",
    version="{{ cookiecutter.version }}",
    {% if cookiecutter.license == "MIT" %}
    license="MIT",
    {% elif cookiecutter.license == "Apache-2.0" %}
    license="Apache 2.0",
    {% else %}
    # license: {{ cookiecutter.license }}
    {% endif %}
)

Inline Conditionals

{{ "with Docker" if cookiecutter.use_docker == "yes" else "without Docker" }}

String Comparison

{% if cookiecutter.database == "postgresql" %}
DATABASE_URL = "postgresql://localhost/{{ cookiecutter.project_slug }}"
{% elif cookiecutter.database == "sqlite" %}
DATABASE_URL = "sqlite:///{{ cookiecutter.project_slug }}.db"
{% endif %}

Loops

{% for %} / {% endfor %}

# requirements.txt
{% for package in cookiecutter.packages.split(',') %}
{{ package.strip() }}
{% endfor %}

With packages = "flask,requests,pytest":

flask
requests
pytest

Loops with Conditions

{% for ext in cookiecutter.extensions.split(',') if ext.strip() %}
import {{ ext.strip() }}
{% endfor %}

Whitespace Control

Jinja2 leaves blank lines when processing tags. Use hyphens to control whitespace:

{%- if cookiecutter.use_docker == "yes" -%}
FROM python:{{ cookiecutter.python_version }}
{%- endif -%}
  • {%- removes whitespace before the tag.
  • -%} removes whitespace after the tag.

Example: Clean Conditional README

# {{ cookiecutter.project_name }}

{% if cookiecutter.use_docker == "yes" -%}
## Docker

```bash
docker build -t {{ cookiecutter.project_slug }} .
docker run {{ cookiecutter.project_slug }}

{% endif %} {%- if cookiecutter.use_ci == "yes" %}

CI/CD

Configured with {{ cookiecutter.ci_provider }}. {% endif -%}


---

## Macros

```jinja2
{% macro license_header(license_type, author) -%}
{%- if license_type == "MIT" %}
# MIT License
# Copyright (c) {{ author }}
{%- elif license_type == "Apache-2.0" %}
# Apache License 2.0
# Copyright (c) {{ author }}
{%- endif %}
{%- endmacro %}

{{ license_header(cookiecutter.license, cookiecutter.author_name) }}

Custom Filters

Create custom filters in _extensions/:

my-template/
├── _extensions/
│   └── my_filters.py
├── cookiecutter.json
└── {{ cookiecutter.project_name }}/
# _extensions/my_filters.py
import re


def slugify(value):
    value = value.lower().strip()
    value = re.sub(r'[^\w\s-]', '', value)
    value = re.sub(r'[\s_-]+', '-', value)
    return re.sub(r'^-+|-+$', '', value)


def camel_case(value):
    value = value.replace('_', ' ').replace('-', ' ')
    return ''.join(word.capitalize() for word in value.split())


def snake_case(value):
    value = value.replace('-', ' ').replace('.', ' ')
    return '_'.join(word.lower() for word in value.split())

Register in cookiecutter.json:

{
  "_extensions": ["my_filters.slugify", "my_filters.camel_case", "my_filters.snake_case"],
  "project_name": "My Project",
  "project_slug": "{{ cookiecutter.project_name|slugify }}",
  "class_name": "{{ cookiecutter.project_name|camel_case }}"
}

With project_name = "My Awesome Project":

  • project_slugmy-awesome-project
  • class_nameMyAwesomeProject

Jinja2 in cookiecutter.json

Derived Variables

{
  "project_name": "My Project",
  "project_slug": "{{ cookiecutter.project_name|lower|replace(' ', '_') }}",
  "pkg_name": "{{ cookiecutter.project_slug|replace('_', '') }}",
  "repo_name": "{{ cookiecutter.project_name|lower|replace(' ', '-') }}",
  "class_name": "{{ cookiecutter.project_name|replace(' ', '') }}"
}

These variables are computed automatically and not shown in the prompt.

Filters in JSON

{
  "project_name": "My Project",
  "year": "{{ cookiecutter.year }}",
  "copyright": "Copyright (c) {{ cookiecutter.year }} {{ cookiecutter.author_name }}"
}

Common Patterns

Generate Module Structure

{# In an __init__.py file #}
"""
{{ cookiecutter.project_name }} package.
"""

__version__ = "{{ cookiecutter.version }}"
{% for module in cookiecutter.modules.split(',') %}
from . import {{ module.strip() }}
{% endfor %}

Generate Configuration Based on Choices

# config.yaml
project:
  name: {{ cookiecutter.project_name }}
  version: {{ cookiecutter.version }}
  {% if cookiecutter.environment == "production" %}
  debug: false
  {% else %}
  debug: true
  {% endif %}
database:
  type: {{ cookiecutter.database }}
  {% if cookiecutter.database == "postgresql" %}
  host: {{ cookiecutter.db_host|default("localhost") }}
  port: {{ cookiecutter.db_port|default(5432) }}
  {% elif cookiecutter.database == "sqlite" %}
  path: ./{{ cookiecutter.project_slug }}.db
  {% endif %}

Dynamic Licenses

{# LICENSE #}
{% if cookiecutter.license == "MIT" %}
MIT License

Copyright (c) {{ cookiecutter.year }} {{ cookiecutter.author_name }}

Permission is hereby granted, free of charge, to any person obtaining a copy
...
{% elif cookiecutter.license == "Apache-2.0" %}
Apache License, Version 2.0
...
{% elif cookiecutter.license == "proprietary" %}
Proprietary - {{ cookiecutter.author_name }}
All rights reserved.
{% endif %}

Escaping

If you need literal {{ }} in a file (that is not Jinja2), use:

{{ "{{ cookiecutter.project_name }}" }}    # produces literal {{ cookiecutter.project_name }}

Or use _copy_without_render to exclude the file from Jinja2 processing.


Next Steps