Allow imports in inline templates
Is your feature request related to a problem? Please describe.
Copier users keep requesting variables support. There are good reasons to avoid supporting variables:
- You will want to use them to generate answer defaults. But you will want to use answers within them. 🐔 + 🥚 problem.
- Jinja already has many ways to DRY your code. Namely: set, macro, import and include.
- YAML also supports anchors, which help you DRY your code within
copier.yaml. - There's already the
ContextHookextension for that, and it's on our docs.
However, it's easy to understand that people still need to keep their templates DRY.
The real problem is not within the template files. As said, Jinja is DRY enough out of the box. The real problem lies within 2 other spaces:
- Inside
copier.yaml. Here, keys are not rendered but values are. - Templated file and directory names.
These are not files, so they get rendered in-line: https://github.com/copier-org/copier/blob/0f5257a0b01fde57ad69a2d51cb20eec1f9e6025/copier/user_data.py#L389
Describe the solution you'd like
I really don't want to create a new system for declaring variables or importing code when Jinja already has them. I just want to be able to use them in in-line templates.
This way, I could declare a copier.yaml like this:
_exclude:
- name-slug
name:
type: str
default: A nice human-readable name
slug:
type: str
default: '{% include "name-slug.jinja" %}'
Also, I could have a template structure like this:
📄 copier.yaml
📄 name-slug.jinja
🐍 {% include "name-slug.jinja" %}.py
Apart from freeing Copier devs from reinventing some wheels, this would empower Copier templaters to not only add variables, but rather do a lot more stuff. Also it would make them more used to get reusable knowledge on the Jinja engine, rather than on a nic~~h~~e use case such as Copier.
Describe how to implement it
Quoting from Jinja docs:
Using a template loader rather than passing strings to Template or Environment.from_string() has multiple advantages. Besides being a lot easier to use it also enables template inheritance.
In other words, you can't import or include from an in-line template (or without a loader).
However, in our in-line templates, we do have a filesystem location context. So, the solution is relatively simple:
- Write a custom loader that tells Jinja that context.
- Let Copier know which loader to use, depending on where the rendered value comes from (is it a file? a path? or a
copier.ymlvalue?). This would mean possibly using one ofPrefixLoaderorChoiceLoader.
Describe alternatives you've considered
See other user requests. All of them contain workarounds:
- #229
- #545
- #629
- #678
- #700
- #716
Additional context