DEV Community

Cover image for Create a project template with ffizer and TDD
David Bernard
David Bernard

Posted on • Updated on • Originally published at ffizer.github.io

Create a project template with ffizer and TDD

ffizer is a files and folders initializer. It can be used as a project generator. It provides some unique features (vs Alternatives) like proposing to show diff or to merge when file already exists, composing template and more. ffizer is developed in Rust, but can be used for any kind of project or directory layout (at work we used it for python projects). ffizer is pretty fast and easy to install as a standalone binary file.

This tutorial will demonstrate how to create a template ala TDD with help of ffizer test-samples.

DISCLAIMER: I'm the author of ffizer, feel free to share your feedback, open issue. I hope you will enjoy ffizer and my first publication on dev.to 🀞.

Begin with a existing sample

Create the folder for the template by copying the existing folder (project) that you would like to generate with the future template.

cp -R my-existing-project my-template
cd my-template
Enter fullscreen mode Exit fullscreen mode

If you don't have an existing sample you can start with the following code, just to have something πŸ˜‰.

mkdir my-template
cd my-template

cat >README.md <<EOF
Hello World
EOF
Enter fullscreen mode Exit fullscreen mode

Clean the content of my-templates folder

  • remove files and folder that you don't want to be part of the template
  • maybe add missing files and folder

Test the template

At this point you have a valid template but it's also a sample of what the template can produce. Use it to create a sample that can show a possible output, and a test to validate what ffizer + my-template can generate.

# create the folder for samples (the name of folder should be `.ffizer.samples.d`)
mkdir .ffizer.samples.d

# copy the current of my-template as a sample named `my-sample`
# except .ffizer.samples.d (its why we use `rsync` instead of `cp -R`)
rsync -rv --exclude=.ffizer.samples.d . .ffizer.samples.d/my-sample.expected

# test that ffizer is able to generate every samples under `.ffizer.samples.d`
# from `my-templates`
ffizer test-samples --source .
Enter fullscreen mode Exit fullscreen mode

Enable git versionning

git init
Enter fullscreen mode Exit fullscreen mode

A new folder .git was created and it should not be part of the template. To ignore this folder, you need to add a configuration for ffizer with this information.

cat >.ffizer.yaml <<EOF
ignores:
  - .git # exclude .git of the template host

EOF
Enter fullscreen mode Exit fullscreen mode

An other alternatives would be to move the template, but not the sample under a template folder and to configure use_template_dir: true

Now you can test again, and commit.

ffizer test-samples --source .
git add .
git commit -m ":tada: initialize the template"
Enter fullscreen mode Exit fullscreen mode

Publish on remote git repository

ffizer support templates hosted on local file system or on remote git repositories (accessible via ssh or https). To share your template you can create and push your local repository to your favorite host (github, gitlab, bitbucket,...) following the same workflow than for any git repository (instructions are provided by the hosting service).

# adapt the following parameters for your case
git remote add origin git@github.com:xxxx/my_template.git
git push -u origin main

# you can also test a remote template
ffizer test-samples --source git@github.com:xxxx/my_template.git
Enter fullscreen mode Exit fullscreen mode

Parametrize the template with variables

The interest of a template is to be more than a cp -R, or a git clone. The first expected feature is to be able to render some parametrized content. ffizer renders only files with .ffizer.hbs as part of the filename (it could be at end or before final extension), the part .ffizer.hbs will be removed from the final name after rendering. Handlebars syntax is used for template string.

# adapt to your case
mv README.md README.ffizer.hbs.md
cat >README.ffizer.hbs.md <<EOF
Hello {{env_var "USERNAME"}}
EOF
Enter fullscreen mode Exit fullscreen mode

At render time, the content between {{ and }} is evaluated, the sample bellow it call the function env_var with the parameter "USERNAME", this function is built-in into ffizer.

If you run the test, an error should be raised, because the rendered content doesn't match the sample.

> ffizer test-samples --source .

Configure variables



Plan to execute

   - make dir        my-sample
   - add file         └─README.md
Nov 08 17:54:38.837 WARN check failed Differences: [
    EntryDiff {
        expect_base_path: "/home/david/tmp/my-template/.ffizer.samples.d/my-sample.expected",
        actual_base_path: "/tmp/.tmpROuSyU/my-sample",
        relative_path: "README.md",
        difference: StringContent {
            expect: "Hello World\n",
            actual: "Hello david\n",
        },
    },
], sample: my-sample, sub-cmd: test-samples
Nov 08 17:54:38.837 ERRO cmd: CliOpts {
    verbose: 0,
    cmd: TestSamples(
        TestSamplesOpts {
            src: SourceLoc {
                uri: SourceUri {
                    raw: ".",
                    path: ".",
                    host: None,
                },
                rev: "master",
                subfolder: None,
            },
            offline: false,
        },
    ),
}
Nov 08 17:54:38.837 ERRO failed: TestSamplesFailed
Enter fullscreen mode Exit fullscreen mode

To fix this issue the better is to define a variable with a value that could be provided by the template user and with the default value define like previously.

cat >.ffizer.yaml <<EOF
variables:
  - name: author_name
    default_value: '{{ env_var "USERNAME" }}'

ignores:
  - .git # exclude .git of the template host

EOF

cat >README.ffizer.hbs.md <<EOF
Hello {{ author_name }}
EOF
Enter fullscreen mode Exit fullscreen mode

At this point if we rerun the test, we will have the same result, because test run ffizer apply with the option --no-interaction that imply using the default value of a variable. So we need to modify the test to force the value of the variable like a user will do by providing a response to the interactive ask of the value for author_name or by using the option --variables.

# setup a configuration for the test sample `my-sample`
cat >.ffizer.samples.d/my-sample.cfg.yaml <<EOF
apply_args:
  - --variables
  - author_name=World

EOF

ffizer test-samples --source .
Enter fullscreen mode Exit fullscreen mode

Test is back to OK, with your parametrized template.

File with .ffizer.hbs are not the only place where you can use handlebars rendering. It can also be used in file or foldername (see How to...) and into some part of the configuration file .ffizer.yaml (see Configuration).

Compose with an external template

To Avoid to duplicate content and configuration between template, you can import the content of one or several other templates into your template. In this tutorial, you will add a template that generate .gitignore files from .gitignore.io (now branded as part of topcal).

To that you should add a section imports:

cat >.ffizer.yaml <<EOF
variables:
  - name: author_name
    default_value: '{{ env_var "USERNAME" }}'

ignores:
  - .git # exclude .git of the template host

imports:
  - uri: "https://github.com/ffizer/templates_default.git"
    rev: "master"
    subfolder: "gitignore_io"

EOF
Enter fullscreen mode Exit fullscreen mode
  • The gitignore_io template is not hosted at a root level the git repository so you should specify the subfolder (like from the options of ffizer apply).
  • The uri can in the format git@github.... or https://github.com/...

If you run ffizer test-samples --source ., you will have an error because the file .gitignore does not exist in the expected of my-sample. If you would like to see the generated file, the simplest way is to apply the template on a local folder (because test-samples remove files and folders created during the test). It is also the opportunity to try your template like a final user.

ffizer apply -s . -d ../my-template-t1
Enter fullscreen mode Exit fullscreen mode

You can notice the following points:

  • the imported template ask for variable's value
  • the ask is after your template's variables
  • the ask is not the variable name but a sentence

because the .ffizer.yaml of the gitignore_io template includes

variables:
  - name: gitignore_what
    default_value: git,visualstudiocode
    ask: Create useful .gitignore (via gitignore.io) for
Enter fullscreen mode Exit fullscreen mode

In this tutorial, you will override the default value of gitignore_what to be only git

cat >.ffizer.yaml <<EOF
variables:
  - name: author_name
    default_value: '{{ env_var "USERNAME" }}'
  - name: gitignore_what
    default_value: git
    ask: Create useful .gitignore (via gitignore.io) for

ignores:
  - .git # exclude .git of the template host

imports:
  - uri: "https://github.com/ffizer/templates_default.git"
    rev: "master"
    subfolder: "gitignore_io"

EOF
Enter fullscreen mode Exit fullscreen mode

And you will also update the my-sample.expected.

ffizer apply -s . -d ../my-template-t2
cp ../my-template-t2/.gitignore .ffizer.samples.d/my-sample.expected
ffizer test-samples --source .
Enter fullscreen mode Exit fullscreen mode

You can now remove the result of your previous manual run.

rm -Rf ../my-template-t*
Enter fullscreen mode Exit fullscreen mode

Next

It's the end of this tutorial but I hope not the end of your journey with ffizer. Now you know the basics to create, to test, to parametrize and to publish a template. You can take a look to existing templates and you can continue to read the ffizer's book, you could learn how to customize ask of value for variable, how to display selection, how to display a message, how to run command, how to read value of variable from existing file in the project,...

I forgot you can also continue to work with your current template:

  • publish / share the new revision of your template
  • add parameters
  • extract template from your template or other stuff
  • compose with other existing templates
  • improve the user experience on update
  • ...

Top comments (0)