Introduction to Archetect
Archetect is a powerful content and code generation tool, with the ability to generate files, projects, or entire architectures.
Archetect is similar to Java's Maven Archetypes, JavaScript's yeoman, and Python's cookiecutter. These are all powerful content generators in their own right, each with their own strengths and weaknesses. Archetect endeavors to take the best aspects of each, and make something greater than the sum of their parts.
Below are some of the specific and key goals and motivations behind the making of Archetect.
Language Agnostic
Unlike some code generators, Archetect does not favor any target programming language. It has a simple but powerful yaml based configuration language, and uses a Jinja 2 style templating language. Archetect gives you all the tools you need to generate code in any language, without needing to resort to embedding Python or JavaScript into your templates.
Easy to Use
Generating a project from an archetype hosted in a remote git repo is as easy as:
archetect render git@github.com:archetect/archetype-rust-cli.git
No need to install templates locally. No need to guess parameters required to feed the template. No need to guess whether the template author is hoping you'll type in answers that are camelCased, PascalCased, or train-cased. It can be run interactively or headless, online or off.
Easy Authoring and Publishing
Archetect archetypes are nothing more than a directory with an archetype.yml file at the root. No special conventions are enforced, and you're free to organize your archetypes as simply or as sophisticated as you'd like. Making your fancy archetype available for the entire world to use is as simple as publishing it in a git repository.
Low Barrier to Entry
Archetect is a native binary written in the Rust programming language, and depends on nothing more than your local git binary and setup. Binaries are available for OSX, Linux, and Windows. No need to have the right version of Python and libraries installed. No need to install NodeJS/NPM. No need to install a JVM. It can run on your laptop, or just as easily within a CI/CD pipeline.
Archetect Features
Archetect was designed specifically with these goals in mind:
- Language Agnostic
- Easy to Author archetypes
- Easy to Publish archetypes
- Easy to Use archetypes
- Usable within an Enterprise Environment
To meet these goals, Archetect has some core features unique amongst content generators.
Templating
Archetect leverages a Jinja2-like templating engine for generating content, with some unique extensions that make it easy to achieve common templating task without the need for custom scripting.
Smart Casing Functions
If an archetype author requests a consumer of their archetype to supply the name for a project, what happens if input is supplied that contains spaces in the name? How could the author safely use this input as a Java class name, as a field name, or a database column name?
Archetect's casing functions are capable of breaking inputs apart based on casing boundaries and reshape in various ways as needed throughout the archetype:
{{ 'Hello World' | snake_case }}
=> hello_world{{ 'Hello World' | camel_case }}
=> helloWorld{{ 'Hello World' | pascal_case }}
=> HelloWorld{{ 'Hello World' | train_case }}
=> hello-world{{ 'Hello World' | constant_case }}
=> HELLO_WORLD{{ 'helloWorld' | title_case }}
=> Hello World{{ 'HELLO_WORLD' | train_case }}
=> hello-world
Smart Pluralization
Most content generators that provide any sort of pluralization capability generally check to see if inputs end with an 's' or not, and then simply tack one on as necessary.
Archetect applies correct pluralizations based on the rules of, and exceptions to, the English Language.
As an example, if an archetype consumer inputs 'soliloquy' as the name of a new microservice, the archetype author can
take this input and ensure that a generated REST API has a GET /soliloquies/
endpoint, with a Soliloquy
object model.
{{ 'soliloquy' | pluralize }}
=> soliloquies{{ 'calf' | pluralize }}
=> calves{{ 'gas' | pluralize }}
=> gases{{ 'tax' | pluralize }}
=> taxes{{ 'wife' | pluralize }}
=> wives{{ 'cactus' | pluralize }}
=> cacti
Configuration
In the simplest case, an archetype is nothing more than a directory with an archetype.yml file at the root. This file is a templating-aware DSL for describing how to acquire inputs, either interactively or through answer parameters or files, and how to render templated content based on those inputs.
Conditional Rendering
Archetect does not prescribe a specific directory structure for holding templated content. Instructions in the archetype.yml file are used to tell Archetect what to render. Multiple target template directories can be rendered, conditionally if desired, allowing for projects to be generated with optional features as determined based on the inputs acquired from the consumer of an archetype.
Composition
Archetypes can not only specify one or more directories to render, but also other archetypes, allowing for interesting compositions. As examples:
- Having common archetypes containing content that should be shared across many other archetypes, while avoiding duplication and maintenance across all of them.
- Having different archetypes for various remoting and database layers, and using conditional rendering to mix and match these technologies in different combinations. For instance, a composing archetype could ask "What kind of remoting layer would you like? [gRPC, OpenAPI, Thrift, Hessian]", and "What kind of database layer would you like? [JPA, DynamoDB, Cassandra]", and render each of the selected technologies together in a unified output project.
Catalogs
Rendering a one-off archetype from a git repository generally requires copying and pasting the location to the command line. If you are frequently generating projects while prototyping architectures, this can be a hassle. Archetect has a catalog system that allows for selecting archetypes interactively from a cli menu.
A catalog can be composed of archetypes, groups, or other catalogs including those hosted in remote git repositories. In a sense, this forms a kind of RESTful, distributed menu system.
As an example, a company could have a master catalog which Archetect is configured to use. Different language teams could each have their own catalogs they maintain containing archetypes for microservices, batch applications, messaging listeners, etc. Each of these catalogs could be referenced by the master catalog. When using Archetect, it will pull the latest version of catalogs as they are used, providing developers with the latest and greatest available archetypes from these various teams. Someone has developed a fancy high-performance microservice in the Rust programming language? Simply add it to a catalog and make it immediately available to the entire staff!
Getting Started
Getting started with Archetect is considerably easier than many content generators, but there are a few steps you'll want follow to ensure you're ready to go.
First, you'll need to check off a prerequisite or two first, which are likely already satisfied if you are a developer.
Naturally, you'll want to install the binary.
And last, you'll want to perform a post setup to ensure everything is working correctly.
Prerequisites
Git
Archetect is a single native binary designed for use on the command line. Technically, placing this on your shell's path is all you would need to get going. You could limit yourself to generating projects from archetypes authored or downloaded on your local machine. But that's no fun.
Archetect was designed to work with git, and you'll want to have git set up on your machine to unleash it's full power. Furthermore, to get the best experience, you'll also want to set up SSH password-less authentication on any git hosts you might expect to pull archetypes from.
Setting up git is beyond the scope of this book. There are many resources available online for your specific platform. GitHub's own documentation on the subject is a good place to start.
If you are able to execute the following command from your shell's command line in a directory you like storing coding projects, without being prompted for a password, you should be ready to go:
git clone git@github.com:archetect/archetect.git
A Note on Windows
Archetect works best with a Unix-like shell with Emoji support. Archetect works flawlessly with wt.exe
.
In addition, Archetypes may contain long file names.
Windows, by default, notoriously has restrictions on path name lengths. Newer versions of Windows 10 and 11 now have support for Long Path Names, but it must be enabled in the Windows Registry (regedit.exe
) by changing:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled
from 0
to 1
, and rebooting.
You will also need to enable Long Path Names
with git, as well:
git config --global core.longpaths true
Note that running the Windows Installer for Archetect will make these changes for you.
Installation
Homebrew
On MacOS (arm64) and on Linux (x64), Archetect can be installed with Homebrew:
brew tap archetect/archetect
brew install archetect
Windows Installer
There is a Windows Installer on Archetect's Releases GitHub Page (archetect-vX.X.X-windows_x64-installer.exe
) that:
- Enables Windows Long Path Names
- Enables Git Long Path Names
- Installs Archetect
- Add Archetect to the PATH
Pre-compiled binaries
Pre-compiled binaries are available on Archetect's Releases GitHub Page for 64 bit flavors of OSX, Linux, and Windows.
Installation is as simple as downloading the zip file that matches your platform, extracting the contents, and placing the archetect
or archetect.exe
binary within a location of your choosing. Ideally, that location would be on your path.
Note: On MacOS, unsigned cli binaries like Archetect are not allowed to run by default. To allow Archetect to run on MacOS, you'll need to browse to the binary with Finder, right or Control-click on it, and click "Open". Alternatively, you can remove the com.apple.quarantine
flag using xattr
:
xattr -d com.apple.quarantine <path to archetect>
For Rust Developers
If you're an existing or aspiring Rust developer with a recent version of the Rust toolchain installed, you can install Archetect from source:
cd ~/projects/rust
git clone git@github.com:archetect/archetect.git
cd archetect
cargo install --path ./archetect-bin --force
Verifying Install
Be sure to verify your installation with Post Setup.
Post Setup
Once Archetect is installed, you can perform a couple of steps to ensure it is working correct.
First, check that you have a verion of a least 2.0.0
:
archetect --version
Next, perform a check to ensure basic requirements are met for your Operating System:
archetect check
Finally, if you run archetect
with no arguments, you should be presented with a default catalog menu. Browse the catalog and generate your first project. If everything is set up correctly, you should have a project generated without being prompted for a Git password.
Concepts
Archetypes
An archetype in Archetect is a self-contained directory containing everything needed to
When rendering an archetype, Archetect
Guide
This guide starts with the basics, and then presents new capabilities and features as we progress through each section. The completed work of each section is captured as branches in the archetect-tutorial Git repo, so you can progress through this guide and the tutorial examples at the same time.
If you'd like to use the companion tutorial examples, first switch into a directory where you keep projects, and then clone the tutorial's Git repo:
git clone https://github.com/archetect/archetect-tutorial.git
You can visit each section by using git to list them out and then check out the corresponding branch that interests you.
git branch
git checkout 01_basics
Basics
Getting started by introducing a few basics.
Structuring an Archetype
Beging laying out the structure of our archetype.
Some Basics
If you are following along with the archetect-tutorial, you can switch to this section's examples from within the directory where it was check out:
git checkout 01_basics
Let's begin making our first archetype, step by step, building up concepts as we go.
Creating our Script
Archetect gets it's instructions from a configuration file containing a simple, domain-specific scripting language.
Create a directory to start your archetype in. Then, create a YAML file at the root of this directory. For now, let's
call it script.yml
:
---
script:
- info: "Hello, World!"
Within this directory, we can 'render' this script as follows:
archetect render script.yml
Prompting for Input
Printing "Hello, World!" is fine and dandy, but not particularly interesting. Modify our script to look as follows:
---
script:
- set:
name:
prompt: "What is your name?"
- info: "Hello, {{ name }}!"
If we have Archetect render the script as we did above, you will now be prompted to enter a name before displaying our custom greeting to the screen.
Using Functions
With our initial script, the name
variable implies a proper noun. But currently, nothing stops someone using the
script from entering in any of the following:
- jane
- JANE
- jane doe
- janeDoe
- jane-doe
- JANE_DOE
- JANE DOE
Archetect makes it easy for archetype authors to accept inputs any way a user might enter them, providing all the tools necessary to reshape the inputs as needed based on the context where the inputs will be used.
Let's update our script so that our input is piped through a function, title_case
, which will reshape the input to
ensure our welcome message is formatted correctly. Use Archetect to render the script multiple times using the example
inputs listed above. Also, try entering in an empty string to see what happens.
---
script:
- set:
name:
prompt: "What is your name?"
- info: "Hello, {{ name | title_case }}!"
Output
In addition to the rendering capabilities we'll begin exploring in Structuring an Archetype,
Archetect provides multiple ways for outputting to both STDERR and STDOUT. Generally prompts, such as asking for
input with the set
action, and informational messages using the display
, trace
, debug
, info
, warn
, and
error
actions print to STDERR. The print
action sends output to STDOUT. This provides you tools to design an
interactive CLI experience, yet allow select output to be piped to a file. Let's start trying these out:
---
script:
- set:
name:
prompt: "What is your name?"
- display: "Hello, {{ name | title_case }}!"
- display: |
Let's generate a simple Java class...
We'll begin by prompting for the name of our class, and then create variables in different casings as needed
in our template.
- set:
class:
prompt: "What is the name of your class?"
ClassName:
value: "{{ class | pascal_case }}"
className:
value: "{{ class | camel_case }}"
- info: "Our PascalCased class name is '{{ ClassName }}'"
- debug: "Our camelCased field name is '{{ className }}'"
Render the script using Archetect as we've done before, trying various inputs to see how they behave:
archetect render script.yml
Notice that the line corresponding to the debug
action did not show up. We need to increase the verbosity of output to
see it by using the -v, or --verbose option. If we were to have a trace
, we would need to pass an additional -v
,
as well.
archetect render script.yml -v
archetect render script.yml -vv
Putting It Together
The rendering capabilities we'll cover in the next chapter are Archetect's bread and butter, but we can use the tools provided so far to allow us to interactively generate basic code that we can pipe anywhere. We could use this to generate simple bits of JSON, YAML, SQL, etc.
---
script:
- set:
name:
prompt: "What is your name?"
- display: "Hello, {{ name | title_case }}!"
- display: |
Let's generate a simple Java class...
We'll begin by prompting for the name of our class, and then create variables in different casings as needed
in our template.
- set:
class:
prompt: "What is the name of your class?"
ClassName:
value: "{{ class | pascal_case }}"
className:
value: "{{ class | camel_case }}"
- info: "Our PascalCased class name is '{{ ClassName }}'"
- debug: "Our camelCased field name is '{{ className }}'"
- print: |
public class {{ ClassName }} {
private static {{ ClassName }} {{ className }} = new {{ ClassName }}();
private {{ ClassName }}(){}
public static {{ ClassName }} getInstance(){
return {{ className }};
}
public void printMessage() {
System.out.println("This is a contrived example. You can try this at home... but don't try it at work!");
}
public static void main(String[] args) {
{{ ClassName }}.getInstance().printMessage();
}
}
While actions like trace
, debug
, info
, warn
, error
, and display
all output to STDERR
, the print
command
outputs to STDOUT
.
Render the script to the screen, and then render it to a file. Use inputs like 'SingletonGreeter', 'singleton greeter', etc as your input, and notice that your Java code will be rendered correctly in most reasonable cases.
archetect render script.yml > SingletonGreeter.java
javac SingletonGreeter.java
java SingletonGreeter
Whoa! Now we're cookin'!
Take notice that for two of the variables defined with the set
command, we explicitly set their values derived
from the previously set variable instead of prompting for a value. These derived variables are in the same shape as
the casing functions applied. This is a common convention used in Archetect archetypes: take an input, and then create
multiple variables in the cases required throughout a complex template. This is much preferable over repeatedly
using the same functions everywhere, and makes it much easier to author and read the template later! This practice
becomes especially important as we start using variables in directory structures.
Structuring an Archetype
If you are following along with the archetect-tutorial, you can switch to this section's examples from within the directory where it was check out:
git checkout 02_structure
Using inline templates within our script is not very practical. For one, we can only pipe out a single file. This might prove convenient simple bits of JSON, YAML, or SQL, but not for entire projects. In addition, for some programming languages, the file names often correlate to the name of structures within the files themself. Take Java for instance; we have to enter in the name of our class twice (first when prompted by Archetect, and second when specifying the file name to pipe to), making this error-prone to ensure these match.
We can do a lot better.
Archetect provides a render
action that allows us to specify a directory of templates to read files from, or
another archetype being composed by this archetype. Not only can we use variables within the templates, but we can
also use them in the directory and file names themselves.
Refactoring
So far, we've been rendering the script.yml
file. However, Archetypes are usually contained within a directory or
Git repository, and we can't always specify the name of a script to render.
Instead, we generally specify the path to a directory or the git URL. By convention, Archetect looks for either
archetype.yml
or archetype.yaml
at the root of the directory. Let's fix this:
mv script.yml archetect.yml
Now, we will instead specify the directory to render instead of the YAML file:
archetect render ./
Now, let's create a directory to store our templates in, and move our Java template into a file within:
mkdir contents
touch "contents/{{ ClassName }}.java"
Copy and paste the Java class from our initial script into the new file, and format it appropriately:
public class {{ ClassName }} {
private static {{ ClassName }} {{ className }} = new {{ ClassName }}();
private {{ ClassName }}(){}
public static {{ ClassName }} getInstance(){
return {{ className }};
}
public void printMessage() {
System.out.println("This is a contrived example. You can try this at home... but don't try it at work!");
}
public static void main(String[] args) {
{{ ClassName }}.getInstance().printMessage();
}
}
With these changes, we can reformat our archetype.yml
file to make use of our new templates directory:
---
script:
- set:
class:
prompt: "What is the name of your class?"
ClassName:
value: "{{ class | pascal_case }}"
className:
value: "{{ class | camel_case }}"
- render:
directory:
source: contents
Our directory is now becoming an archetype, and something we'll eventually check into revision control. We
don't want to pollute our archetype with generated files. Therefore, we should start rendering into a different
directory. As an example, if we had ~/projects/
and~/archetypes/
directories, where the former is where we render
our projects and the latter is where we author our archetypes, we would now render as follows:
cd ~/projects/
archetect render ~/archetypes/archetect-tutorial
We no longer need to pipe our output to a file, and whatever we supply as our class name will now be reflected within both the file name and file contents using the same variable. Easy!
Concepts
Scripting Engine
Templating Engine
Templating Configuration
Some aspects of the templating engine can be configured through settings in an archetype's archetype.yaml
manifest.
Undefined Behavior
Since: 2.0.0-ALPHA.17
Undefined Behavior
can be tuned based on the needs of an archetype. This configuration determines what happens if a
context value is "undefined" (null).
Archetect's templating engine allows for the following three options:
Strict (default)
The templating engine will throw an error if an undefined value is encountered. For most archetypes, this setting is suitable, and helps an archetype author identify problems with their archetype quicker.
For example, if a variable first_name
has not been defined when rending Jinja content, such as:
Hello, {{ first_name }}!
Archetect will throw an error and abort any further execution of the archetype.
This is the default behavior if this setting is not explicitly set, but may be set in archetype.yaml
:
---
templating:
undefined_behavior: Strict
Lenient
The templating engine will silently insert an empty string if a value is undefined. Useful when generating from more complex context models where you want to conditionally render content based on whether a variable is defined or not.
For example, if first_name
has been defined, but greeting
is optional and possibly undefined:
{% if greeting %}{{ greeting }}{% else %}Hello{% endif %}, {{ first_name}}!
Archetype will allow the conditional check on greeting
, and silently insert an empty string for first_name
if it has
not been defined.
To enable this behavior in archetype.yaml
:
templating:
undefined_behavior: Lenient
Chainable
The templating engine will silently insert an empty string, even if there are a chain of undefined values. Useful for complex, dynamic generation from models.
For example, if an object with properties is optionally expected in the context, Archetect will silently insert an empty
string if either person
or person.first_name
is undefined:
{{ person.first_name }}
To enable this behavior in archetype.yaml
:
templating:
undefined_behavior: Chainable
Configuration
Credits
These are some of the key libraries that are core to Archetect's functionality':