copier/docs/faq.md
Jairo Llopis 50d478e19d docs: explain how to use computed variables, more clearly
Fixes https://github.com/copier-org/copier/issues/1414 by documenting a clearer and safe alternative that covers most cases.
2023-11-21 16:45:54 +00:00

195 lines
6.1 KiB
Markdown

# Frequently Asked Questions
## Can Copier be applied over a preexisting project?
Yes, of course. Copier understands this use case out of the box. That's actually what
powers features such as [updating](updating.md) or the ability of [applying multiple
templates to the same subproject][applying-multiple-templates-to-the-same-subproject].
!!! example
```shell
copier copy https://github.com/me/my-template.git ./my-preexisting-git-project
```
## How to use Copier from Docker or Podman?
Copier doesn't provide an image by default. However, it does provide a nix package, so
you can use Nix to run Copier reproducibly from within a container:
```shell
# Change for docker if needed
engine=podman
# You can pin the version; example: github:copier-org/copier/v8.0.0
copier=github:copier-org/copier
$engine container run --rm -it docker.io/nixos/nix \
nix --extra-experimental-features 'nix-command flakes' --accept-flake-config \
run $copier -- --help
```
You can even generate a reproducible minimal docker image with just Copier inside, with:
```shell
nix bundle --bundler github:NixOS/bundlers#toDockerImage \
github:copier-org/copier#packages.x86_64-linux.default
docker load < python*copier*.tar.gz
```
## How to create computed values?
Combine `default` and `when: false`.
!!! example
```yaml title="copier.yaml"
copyright_year:
type: int
default: 2024
next_year:
type: int
default: "{{ copyright_year + 1 }}" # This computes the value
when: false # This makes sure it isn't asked nor stored
```
See [advanced prompt formatting docs][advanced-prompt-formatting]. If you need more
power, see [below][how-can-i-alter-the-context-before-rendering-the-project].
## How can I alter the context before rendering the project?
**Use the [`ContextHook` extension][context-hook].** It lets you modify the context used
to render templates, so that you can add, change or remove variables. Since it is a
Python extension, you have the full power of Python at your fingertips, at the cost of
having to mark the template as [unsafe][].
[context-hook]:
https://github.com/copier-org/copier-templates-extensions#context-hook-extension
In order for Copier to be able to load and use the extension when generating a project,
it must be installed alongside Copier itself. More details in the [`jinja_extensions`
docs][jinja_extensions].
You can then configure your Jinja extensions in Copier's configuration file:
```yaml title="copier.yaml"
_jinja_extensions:
- copier_templates_extensions.TemplateExtensionLoader
- extensions/context.py:ContextUpdater
```
Following this example, you are supposed to provide a `context.py` file in the
`extensions` folder at the root of your template to modify the context. If for example
your `copier.yaml` contains a multiple-choice variable like this:
```yaml title="copier.yaml"
flavor:
type: str
choices:
- docker
- instances
- kubernetes
- none
```
The `context.py` file contains your context hook which could look like:
```python title="extensions/context.py"
from copier_templates_extensions import ContextHook
class ContextUpdater(ContextHook):
def hook(self, context):
flavor = context["flavor"] # user's answer to the "flavor" question
return {
"isDocker": flavor == "docker"
"isK8s": flavor == "kubernetes"
"isInstances": flavor == "instances"
"isLite": flavor == "none"
"isNotDocker": flavor != "docker"
"isNotK8s": flavor != "kubernetes"
"isNotInstances": flavor != "instances"
"isNotLite": flavor != "none"
"hasContainers": flavor in {"docker", "kubernetes"}
}
```
Before rendering each templated file/folder, the context will be updated with this new
context object that you return from the hook. If you wish to update the context in-place
rather than update it, set the `update` class attribute to false:
```python title="extensions/context.py"
from copier_templates_extensions import ContextHook
class ContextUpdater(ContextHook):
update = False
def hook(self, context):
flavor = context["flavor"] # user's answer to the "flavor" question
context["isDocker"] = flavor == "docker"
context["isK8s"] = flavor == "kubernetes"
context["isInstances"] = flavor == "instances"
context["isLite"] = flavor == "none"
context["isNotDocker"] = flavor != "docker"
context["isNotK8s"] = flavor != "kubernetes"
context["isNotInstances"] = flavor != "instances"
context["isNotLite"] = flavor != "none"
context["hasContainers"] = context["isDocker"] or context["isK8s"]
# you can now actually remove items from the context
del context["flavor"]
```
Now you can use these added variables in your Jinja templates, and in files and folders
names!
## Why Copier consumes a lot of resources?
If the repository containing the template is a shallow clone, the git process called by
Copier might consume unusually high resources. To avoid that, use a fully-cloned
repository.
## While developing, why the template doesn't include dirty changes?
Copier follows [a specific algorithm][templates-versions] to choose what reference to
use from the template. It also [includes dirty changes in the `HEAD` ref while
developing locally][copying-dirty-changes].
However, did you make sure you are selecting the `HEAD` ref for copying?
Imagine this is the status of your dirty template in `./src`:
```shell
$ git -C ./src status --porcelain=v1
?? new-file.txt
$ git -C ./src tag
v1.0.0
v2.0.0
```
Now, if you copy that template into a folder like this:
```shell
$ copier copy ./src ./dst
```
... you'll notice there's no `new-file.txt`. Why?
Well, Copier indeed included that into the `HEAD` ref. However, it still selected
`v2.0.0` as the ref to copy, because that's what Copier does.
However, if you do this:
```shell
$ copier copy -r HEAD ./src ./dst
```
... then you'll notice `new-file.txt` does exist. You passed a specific ref to copy, so
Copier skips its autodetection and just goes for the `HEAD` you already chose.