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.