Skip to content

Add template inheritance #1484

@simobasso

Description

@simobasso

Hey @audreyr @pydanny @hackebrot, happy new year and thank you all for maintaining this project. Is awesome.

This project helps me to deliver better and faster but lacks an important feature (IMHO).

Template inheritance

This issue (and the attached pr) aims to implement a templates directory so a maintainer can use extends, includes, blocks, import and super inside the cookiecutter project template.

Motivation

Exists a lot of cases where an option can drastically change a whole file, or you need to include some repeatable fields.

For example, we have a cookiecutter were an option changes completely the ci file. This is a simple problem but requires some effort (check Alternative Considered) when a template engine is built to solve this kind of problem.

Cookiecutter use jinja under the hood to render the project boilerplate but at render time doesn't have any knowledge about the original project template (as also stated here for example), this means that all files should be included inside the cookiecutter template before the render happen.

But with this behaviour, the user is forced to bypass these mechanics when the project becomes complex.

User Benefit

User can finally use jinja Template Inheritance, and this includes (tl;dr):

  • extends
  • blocks
  • super
  • scoped
  • import
  • include

This works really well with directories options.

This can step into #59 because if templates are pluggable from the outside, we can include external package templates into the loader as well (in a second iteration).

This may also fit in #1004 if we use the base template as a master template.

There are other requests for this like #818, #409 and other similar grouped by #364 or like #1479

I know that cookiecutter is a very conservative project, but this is a little change with no impact has a high ROI.

Design Proposal

to implement this we just need to add ../templates in the cookiecutter env.loader

in this way and generating a cookiecutter like this:

https://github.com/user/repo-name.git
├── {{cookiecutter.project_slug}}/
|   └── file.txt
├── templates/
|   └── base.txt
└── cookiecutter.json

we can load into the jinja env every file listed under that directory and use it in cookiecutter templates like this:

{% extends "base.txt" %}

or

{% include "base.txt" %}

And so on.

Alternatives Considered

Now, this can be implemented in a few ways:

  1. giant if statements
{# config.txt #}
{% if True%}
{# whole config A (200 lines of codes)#}
{% else %}
{# whole config B (250 lines of codes)#}
{% endif%}

and the readability is a mess

  1. pre/post hooks

we can add a cleaning/renaming/moving function before or after the baking process but is very complicated and require "magic":

if "{{ cookiecutter.is_A }}".lower() == "y":
    os.remove("configB")
    os.rename('configA','config')

if "{{ cookiecutter.is_B}}".lower() == "y":
    os.remove("configA")
    os.rename('configB','config') 

as a reference: https://github.com/pydanny/cookiecutter-django/blob/master/hooks/post_gen_project.py

  1. using logic in the filename
https://github.com/user/repo-name.git
├── `{% if cookiecutter.is_B %}config{% endif %}`
└── `{% if cookiecutter.is_A %}config{% endif %}`

this logic can be really helpful when you need not render specific files or directory
but not when you need to extend common configurations.

  1. template inside the project and hook to remove it after
https://github.com/user/repo-name.git
├── {{cookiecutter.project_slug}}/
|   ├── templates/
|   |   └── base.txt
|   └── file.txt
└── cookiecutter.json

and somewhere at post gen hooks

...
shutil.rmtree("templates")
...

Compatibility

This implementation is full retro compatible with already baked cookiecutter.

there are two main scenarios:

  1. the project doesn't have a templates dir: the templates directory is optional, so no harm here.
  2. the project already have a templates dir: all keyword implemented have no sense before this implementation so that no one can use it, this means that the templates will be loaded, but we can exclude the use.

Questions and Discussion Topics

  • There are other cases not included?
  • What do you think about it?
  • templates is a too generic name?

Let me know!

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureThis issue/PR relates to major feature request.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions