A template processor and language.
StaticTea produces a file from a template and optional json and code files.
Example hello world html template:
<!--$ nextline --> hello {s.name}
The associated json data:
{"name": "world"}
The result:
hello world
茶 (table of contents at the bottom)
You can view, edit and validate your templates with its normal tools as if they were static pages and at the same time share common template fragments between templates.
The template designer has full control over the presentation providing a clear separation of concerns. They can change the look without changing the server.
You create a template and add command lines around your variable content. This defines the replacement blocks.
In the replacement blocks you add variables in brackets for substitutions.
In the commands you format the variables for the replacement blocks.
You specify the commands so they look like comments for your template type.
A StaticTea command marks a replacement block in the template and it provides a place for your code statements.
- nextline – make substitutions in the next line
- block —- make substitutions in the next block of lines
- replace -— replace the block with a variable
- endblock – end the block and replace commands
- : (continue) – continue a command
- # (comment) – code comment
The nextline command targets the line following it for replacement. The targeted line is called the replacement block.
The following example’s replacement block contains two variables, drink and drinkType.
template:
<!--$ nextline --> Drink {s.drink} -- {s.drinkType} is my favorite.
server json:
{ "drink": "tea", "drinkType": "Earl Grey" }
result:
Drink tea -- Earl Grey is my favorite.
The block command targets multiple lines for replacement. The replacement block starts after the command and continues until the endblock line is found. It behaves like the nextline command except with multiple lines.
In the following example the block has three lines. The block contains three replacement variables, weekday, name and time.
template:
<!--$ block --> Join our tea party on {s.weekday} at {s.name}'s house at {s.time}. <!--$ endblock -->
server json:
{ "weekday": "Friday", "name": "John", "time": "5:00 pm" }
result:
Join our tea party on Friday at John's house at 5:00 pm.
The replace command’s replacement block gets replaced with the t.content variable. Here is a simple example:
template:
<!--$ replace t.content = o.header --> <!--$ endblock -->
shared code file:
o.header = """
<!doctype html>
<html lang="en">
"""
result:
<!doctype html> <html lang="en">
The above example generates the correct result but it doesn’t work as a static template because the template is missing the header lines.
You can add lines to the replace command’s replacement block to mirror the t.content variable so you can develop and test the template as if it was a static file.
The replace command allows you to share common template lines between templates and at the same time work with them as static pages.
Since you are duplicating content in the replacement block, when you want to edit the shared text it will get out of sync. You can update your templates using the Update Option.
If you don’t assign the t.content variable, a warning is generated, and the command behaves like a block command. This is good for testing changes you want to make to the shared value.
The following example uses a common header from the shared code file and it mirrors it in the replacement block.
template:
<!--$ replace t.content = o.header --> <!doctype html> <html lang="en"> <!--$ endblock -—>
The shared variable may contain replacement content too. Here is an example of that:
template:
<!--$ replace t.content = o.header --> <!DOCTYPE html> <html lang="{s.languageCode}" dir="{s.languageDirection}"> <head> <meta charset="UTF-8"/> <title>{s.title}</title> <--$ endblock -->
server json:
{ "languageCode": "en", "languageDirection": "ltr", "title": "Teas in England" }
shared code file:
o.header = """
<!DOCTYPE html>
<html lang="{s.languageCode}" dir="{s.languageDirection}">
<head>
<meta charset="UTF-8"/>
<title>{s.title}</title>
"""
result:
<!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="UTF-8"/> <title>Teas in England</title>
The endblock command ends the replacement block for the block and replace commands. For example:
$$ block replacement block text replacement block text $$ endblock $$ replace replacement block text replacement block text $$ endblock
Only the endblock command ends them. All text until the endblock is part of the replacement block. This includes lines that look like commands. For example:
template:
<!--$ block --> <!--$ # this is not a comment, just text --> fake nextline <!--$ nextline --> <!--$ endblock -->
result:
<!--$ # this is not a comment, just text --> fake nextline <!--$ nextline -->
The continue command (a colon) allows you to continue adding statements to the nextline, block and replace commands.
We often refer to the command and its continue commands as the command (one logical unit).
In the following example the nextline command continues on a second line and third line because of the two continue commands.
template:
$$ nextline $$ : tea = "Earl Grey" $$ : tea2 = "Masala chai" {tea}, {tea2}
result:
Earl Grey, Masala chai
You can comment templates with the comment command. Comments are line based and use the # character. They do not appear in the result.
template:
<!--$ # The main tea groups. --> There are five main groups of teas: white, green, oolong, black, and pu'erh. You make Oolong Tea in five time intensive steps.
result:
There are five main groups of teas: white, green, oolong, black, and pu'erh. You make Oolong Tea in five time intensive steps.
You can also comment in statements with # to the end of the line, see the Syntax section.
A replacement block is a group of contiguous lines in a template between a command and its endblock or for the nextline command, the one line after it.
The block contains any number of bracketed variables for substitution. Each variable gets replaced with its value.
You have control over where the replacement block goes, either the result file, standard out, standard error, or the log file using the t.output variable.
You can repeat the block to make lists and other repeating content. You control how many times the block repeats with the t.repeat variable. You can skip and stop early with the return function. The t.row counts the number of times the block has repeated and you use its value to customize each repeated block.
Below is an example that repeats the block three times and outputs 0, 1, 2:
template:
$$ nextline t.repeat = 3 {t.row}
result:
0 1 2
If a replacement block variable doesn’t exist, the bracketed variable remains as is in the result, and a message is output to standard error. For example:
template:
<!--$ block --> You're a {s.webmaster}, I'm a {s.teaMaster}! <!--$ endblock -->
server json:
{ "webmaster": "html wizard" }
stderr:
template.html(3): w58: The replacement variable doesn't exist: s.teaMaster.
result:
You're a html wizard, I'm a {s.teaMaster}!
You create a new variable with a statement. A statement is an expression consisting of a variable name, an operator and a right hand side.
For the example below, tea is the variable name, = is the operator and “Earl Grey” is the right hand side:
tea = "Earl Grey"
The operator is either an equal sign or an “&=”. The equal sign appends to a dictionary and the &= appends to a list.
The right hand side is either a variable, string, number, list, boolean expression or a function.
Here are a few example statements:
tea = "Earl Grey"
num = 5
t.repeat = len(tealist)
numList = [1, 2, 3]
flag = (s.name > 20)
name = s.firstName
b &= 3
Statements are allowed on the nextline, block, continue and replace commands.
In the next example, the block command and the continue command contain a statement.
$$ block tea = "Earl Grey" $$ : a = 5 $$ endblock
Most operations are done with functions. For example, to add 1 to t.row you use the add function.
num = add(t.row, 1)
Statements are executed from top to bottom. You can use the warn function to exit a statement early and use the return function to exit a command early.
If there is a syntax error or a function generates a warning, the statement is skipped.
You can continue a long statement on the next line by using a “+” character at the end.
You can have a bare IF function on a statement and a few other exceptions, see special forms Special Forms.
A template consists of command lines and non-command lines. The command lines are line oriented and they have the same form and they are limited to 1024 bytes. There are no restrictions on the non-command lines in a template.
Each command line is a comment to match the template type. The beginning comment characters are called the prefix and the optional ending comment characters are called the postfix. For example, in an html template the prefix is “<!–$” and the postfix is “–>”. See Prefix Postfix for more information.
From left to right a command line consists of:
- a prefix at column 1.
- a command name
- an optional statement
- an optional comment
- an optional plus continuation character
- an optional postfix
- the end of line, either \r\n or \n.
Here is a chart showing line components and where spaces are allowed:
prefix | command [statement] | | | [comment] | | | | [continuation] | | | | |[postfix] | | | | || [newline] | | | | || | <!--$ nextline a=5 # set a +--> | | | | | no spaces at the end | one required space optional spaces
The chart below shows a nextline command with three continuation commands and three statements: a = 5, b = “tea” and c = “The Earl of Grey”.
prefix | command statement | | | continuation | | | | | | | |postfix | | +------+ || newline | | | | || | <!--$ nextline a = 5 --> <!--$ : b = "tea" --> <!--$ : c = "The Earl of +--> <!--$ : Grey" -->
A statement starts one space after the command. You can use more spaces but they are part of the statement. This is important when you wrap quoted strings with a continuation.
Space isn’t allowed before the prefix, after the continuation or after the postfix or between the function name and its opening parentheses. Here are a few single line examples:
$$ nextline $$ nextline a=5 $$ nextline a = 5 $$ nextline num = len(s.tea_list) $$ nextline num = len( s.tea_list ) $$nextline $$ nextline
The statements may flow between lines by using the continuation plus character. The following two nextline commands are equivalent:
<!--$ nextline com = "Bigelow Tea Company" --> <!--$ nextline com = "Big+--> <!--$ : elow Tea Company" -->
You can have blank statements that do nothing.
$$ nextline $$ : $$ : # comment
You use variables to add variable content to replacement blocks.
A string variable gets replaced with its value, a function variable with its name, and the other variable types get replaced with their json equivalent.
In the example below, “name” and “teas” are variables that are defined in the command. Both variables are used in the replacement block.
Template:
$$ block $$ : name = "Eary Grey" $$ : teas = list("Black", "Green", "Oolong") Popular tea: {name} Available kinds: {teas} $$ endblock
Result:
Popular tea: Earl Grey Available kinds: ["Black","Green","Oolong"]
You use variables in the t dictionary (tea variables), to control where the output goes, how many times it repeats and other aspects controlling a command.
A variable name starts with a letter followed by letters, digits, hyphens and underscores and ends with a letter or a digit. It is limited to a total of 64 ASCII characters.
Some single letters are reserved for important dictionaries, Single Letter Dictionaries.
Example variable names:
a tea-type first_name nameLen b5
Each iteration of a replacement block starts fresh. Local variables and some tea variables are undefined before processing the next replacement block. The t.repeat and t.row variables are exceptions since they control the loop.
In the next example the name variable gets defined differently for each iteration based on t.row.
$$ nextline $$ : t.repeat = 3 $$ : name = format("a{t.row}") {name}
Output:
a0 a1 a2
You can append a new variable to a list or dictionary but you cannot change an existing variable.
A dot name is a fully qualified variable name where variables are connected with dots. It is limited to 64 characters.
Example dot names:
t.repeat s.name d.path.filename f.cmp entry.name
You can leave off the “l” prefix from your non-function local variables, they are assumed to be in the local dictionary.
Equivalent statements:
a = 5 l.a = 5
You can leave off the “f” prefix from built-in functions you call, they are assumed to be in the function dictionary. A function call is a variable followed by a left parentheses.
Equivalent statements:
rt = cmp(a, b)
rt = f.cmp(a, b)
All variables are stored in one of the reserved one letter top level dictionaries f - u. Seven are currently used: f, g, l, o, s, t, u.
The server json variables are stored in the s dictionary. You reference them with “s.”, for example:
s.name s.address
A local variable is stored in the l (local) dictionary. L is implied for an unqualified non-function name.
You can use the unreserved beginning and ending letters of the alphabet, a, b, c, d, e and v, w, x, y, z for your variable names.
Reserved single letter variables:
- f – Function Variables
- g – Global Variables
- h, i, j, k – Reserved
- l – Local Variables
- m, n – Reserved
- o – Shared Code Variables
- p, q, r – Reserved
- s – Server Json Variables
- t – Tea Variables
- u – User Function Vars
All the built-in functions exist in the f dictionary. F is implied for an unqualified function name you call. See Func Type for more information.
Variables you add to the g dictionary are global to the template file. They’re visible to all commands in the template and they are not reset like local variables.
Shared code cannot see or set g variables.
g.title = "Teas of the World"
g.names &= entry.name
Variables you create without a prefix go in the local (l) dictionary.
The local variables are cleared and recalculated for each iteration of a repeated block.
Inside a user defined function there is another local (l) dictionary used for its local variables and parameters.
Examples:
a = 5
pot = "black"
l.tea = "earl grey"
You create code variables with statements in code files. Code variables are good for sharing between templates.
The variables go in the o dictionary, see Code Files.
You pass variables from the server to the template in json files.
The variables are defined by the top level JSON dictionary items. Each item’s key is the name of a variable and the item’s value is the variable’s value.
You can use multiple server json files by specifying multiple files on the command line. The files are processed left to right.
The server json files populate the s dictionary.
The json null values get converted to the 0.
To give full control of the presentation to the template designers, the server json shouldn’t contain any presentation data.
You use the u dictionary to store your function variables for easy access inside other functions.
Similar to the f dictionary the u dictionary variables are visible inside user defined functions so you don’t need to pass them in. You specify the u prefix when defining and calling the function.
Only user function variables are allowed in the u dictionary. Below is an example of defining u.myCmp and calling it:
u.myCmp = func(…) int
…
a = u.myCmp("3", "4")
The tea variables control how the replacement block works and they provide information about the system. They are stored in the t dictionary.
Tea variables:
- t.args – arguments passed on the command line
- t.content – content of the replace block
- t.maxRepeat – maximum number of times to repeat the block
- t.maxLines – maximum number of replacement block lines allowed before the endblock
- t.output – where the block output goes
- t.repeat – how many times the block repeats
- t.row – the current index number of a repeating block
- t.version – the StaticTea version number
The read-only t.args variable contains the arguments passed to statictea on the command line.
For example using the command line below results in a t.args value shown:
statictea -l -s server.json -o codefile.tea \ -s server2.json -o codefile2.tea \ -p 'abc$,def' -p '$$' \ -t template.html -r result.html t.args => { "help":0, "version":0, "update":0, "log":1, "serverList":["server.json","server2.json"], "codeList":["codefile.tea","codefile2.tea"], "resultFilename":"result.html", "templateFilename":"template.html", "logFilename":"", "prepostList":[["abc$","def"],["$$",""]] }
The t.content variable determines the content used for the replace command’s whole replacement block.
t.content = h.header
You use Update Option to keep the template’s blocks in sync with their variables.
When t.content is not set, the command behaves like a block command except a warning message is output. This is good for testing changes you want to make to the shared value and the warning reminds you to set the variable when you’re done testing.
The variable only applies to the replace command. See the replace command section for an example.
The t.maxRepeat variable determines the maximum times a block can repeat. The default is 100. You can increase it to repeat more times. You cannot assign a number to t.repeat bigger than maxRepeat.
It prevents the case where you mistakenly assign a giant number, and it allows you to design your template to work well for the expected range of blocks.
The t.maxLines variable determines the maximum lines in a replacement block.
StaticTea reads lines looking for the endblock. By default, if it is not found in 50 lines, the 50 lines are used for the block and a warning is output. This catches the case where you forget the endblock command. You can increase or decrease the value.
<!--$ block t.maxLines = 200 -->
The t.output variable determines where the block output goes. By default it goes to the result file.
- “result” – to the result file (default)
- “stdout” – to standard out
- “stderr” – to standard error
- “log” – to the log file
- “skip” – to the bit bucket
The t.repeat variable is an integer that tells how many times to show the command’s replacement block. A value of 0 means don’t show the block at all. If you don’t set it, the block repeats one time.
Each time the block repeats the local variables get cleared then recalculated.
The t.row variable counts the number of times the block repeats and is used to customize each block iteration.
The t.maxRepeat variable limits how many times the block can repeat. You cannot assign a number bigger than t.maxRepeat to t.repeat. You can set t.maxRepeat to anything you want, the default is 100.
When you set t.repeat to 0, the command exits. The commands’ statements following are not run. This makes a difference when the command has side effects, like setting global variables. You can move the “t.repeat = 0” line around to compensate.
For the following example, the number of items in teaList is assigned to the t.repeat variable which outputs the block five times.
template:
<!--$ nextline t.repeat = len(s.teaList) --> <!--$ : tea = get(s.teaList, t.row) --> * {tea}
server json:
{ "teaList": [ "Black", "Green", "Oolong", "Sencha", "Herbal" ] }
result:
* Black * Green * Oolong * Sencha * Herbal
The following example builds an html select list of tea companies with the Twinings company selected and it shows how to access values from dictionaries.
template:
<h3>Tea Companies</h3> <select> <!--$ block t.repeat=len(s.companyList) --> <!--$ : d = s.companyList[t.row] --> <!--$ : selected = get(d, "selected", false) --> <!--$ : current = if(selected, " selected=\"selected\"", "") --> <option{current}>{d.company}</option> <!--$ endblock --> </select>
server json:
{ "companyList": [ {"company": "Lipton"}, {"company": "Tetley"}, {"company": "Twinings", "selected": true}, {"company": "American Tea Room"}, {"company": "Argo Tea"}, {"company": "Bigelow Tea Company"} ] }
result:
<h3>Tea Companies</h3> <select> <option>Lipton</option> <option>Tetley</option> <option selected="selected">Twinings</option> <option>American Tea Room</option> <option>Argo Tea</option> <option>Bigelow Tea Company</option> </select>
Setting t.repeat to 0 is good for building test lists.
When you view the following template fragment in a browser it shows one item in the list, “{tea}”.
template:
<h3>Tea</h3> <ul> <!--$ nextline t.repeat = len(s.teaList)--> <!--$ : tea = get(s.teaList, t.row) --> <li>{tea}</li> </ul>
server json:
{ "teaList": [ "Black", "Green", "Oolong", "Sencha", "Herbal" ] }
To create a static page that has more products for better testing you could create a test list of teas using t.repeat of 0. It will appear when testing but not when generating the final result. In the following example the test list will show: {tea}, Chamomile, Chrysanthemum, White, and Puer.
template:
<h3>Tea</h3> <ul> <!--$ nextline t.repeat = len(s.teaList) --> <!--$ : tea = get(s.teaList, t.row) --> <li>{tea}</li> <!--$ block t.repeat = 0 --> <li>Chamomile</li> <li>Chrysanthemum</li> <li>White</li> <li>Puer</li> <!--$ endblock --> </ul>
result:
<h3>Tea</h3> <ul> <li>Black</li> <li>Green</li> <li>Oolong</li> <li>Sencha</li> <li>Herbal</li> </ul>
The t.row read-only variable counts the number of times the replacement block repeats.
You use it to format lists and other repeating content in the template.
Here is an example using the row variable. In the example, row is used in three places.
template:
<ul> <!--$ nextline t.repeat=len(s.companies)--> <!--$ : company = s.companies[t.row] --> <!--$ : num = add(t.row, 1) --> <li id="r{t.row}">{num}. {company}</li> </ul>
server json:
{ "companies": [ "Mighty Leaf Tea", "Numi Organic Tea", "Peet's Coffee & Tea", "Red Diamond" ] }
result:
<ul> <li id="r0">1. Mighty Leaf Tea</li> <li id="r1">2. Numi Organic Tea</li> <li id="r2">3. Peet's Coffee & Tea</li> <li id="r3">4. Red Diamond</li> </ul>
The read-only t.version variable contains the current version number of StaticTea. See the cmpVersion function for more information.
StaticTea variable types:
A string is an immutable sequence of unicode characters.
You define a single line literal string with double quotes and a multiline string in code files with triple quotes.
The example below defines a literal string and assigns it to the variable str:
str = "black teas vs. green teas"
Strings are encoded as UTF-8 and invalid byte sequences generate a warning.
Strings follow the same escaping rules as json strings. You can escape eight special control characters using a slash followed by a letter, otherwise slash is a normal character. Special escape letters:
- ” – quotation mark (U+0022)
- \ – reverse solidus (U+005C)
- / – solidus (U+002F)
- b – backspace (U+0008)
- f – form feed (U+000C)
- n – line feed (U+000A)
- r – carriage return (U+000D)
- t – tab (U+0009)
Examples with escaping:
- “ending newline\n”
- “tab \t in the middle”
- “Mad Hatter: \"… you must have a cup of tea!\" - ‘Alice In Wonderland’.”
- “Unicode tea character ‘茶’ is ‘\u8336’”
- “smiley face 😀 by escaping: \uD83D\uDE00.”
You can enter any unicode value with \u and four hex digits or, for values greater the U-FFFF, two pairs. The two pairs are called surrogate pairs.
Unicode code point U-8336 is 茶, escaped: \u8336. Unicode code point U-1F600 is 😀, escaped: \uD83D\uDE00.
You can generate the surrogate pair for a unicode code point using Russell Cottrell’s surrogate pair calculator: Surrogate Pair Calculator.
An int is a 64 bit signed integer. Plus signs are not used with numbers. You can use underscores in long number literals to make them more readable.
Example numbers:
12345 0 -8823 42 1_234_567
A float is a 64 bit real number, it has a decimal point and starts with a digit or minus sign. You can use underscores in long number literals to make them more readable.
Example floats:
3.14159 24.95 0.123 -34.0 1_234.56
The dict type is an ordered key value store with fast lookup. It maps a string key to a value which can be any type. The dict is ordered by insertion order.
You create a dict using the dict function. The example below creates an empty dict d and a dict d2 with 2 elements.
d = dict() d2 = dict(["one", 1, "two", 2])
You can append to a dict using dot name notation:
d = dict()
d.a = 5
d.tea = "Eary Grey"
You can append to a dict using bracket notation on the left hand side of the equal sign and this way allows you to use a variable for the key:
d = dict()
d["a"] = 5
var = "tea"
d[var] = "Eary Grey"
You append to the l dict when you create a new variable without a prefix.
a = 5
tea = "Eary Grey"
You access a dict element with a dot name, bracket notation or with the get function. If the key is an invalid variable name, you cannot access it using dot notation.
The get function has an optional default parameter that’s used when the key doesn’t exist (the z case below).
d = dict(["x", 100, "y", 200])
x = get(d, "x") # 100
y = get(d, "y") # 200
z = get(d, "z", 300) # 300
The server json data becomes the s dictionary. For the example below s contains three top level elements, a, b and d. A and b are integers and d is a dictionary with elements x and y.
server json:
{ "a": 1, "b": 2, "d": { "x": 100, "y": 200 } }
A list contains a sequence of values of any type.
You can create a list with the list function or with brackets:
a = list()
a = list(1)
a = list(1, 2, 3)
a = list("a", 5, "b")
a = []
a = [1]
a = [1, 2, 3]
a = ["a", 5, "b"]
You can append to a list by assigning a value to a variable with the &= operator. It will create the list if it doesn’t exist. In the example below, the first line creates the list variable then assign “black” to it. The second line appends “green”:
teas &= "black" teas &= "green" teas => ["black", "green"]
The next example creates a g.names list from names contained in a list of dictionaries:
$$ block $$ : t.repeat = len(s.entries) $$ : entry = s.entries[t.row] $$ : g.names &= entry.name $$ endblock
You can access list elements with the get function or bracket notation:
list = list(1, 3.3, "a")
get(list, 0) # 1
get(list, 1) # 3.3
get(list, 2) # "a"
get(list, 3, 99) # 99
get(list, -1) # "a"
get(list, -2) # 3.3
get(list, -3) # 1
get(list, -4, 99) # 99
list[0] # 1
list[1] # 3.3
list[2] # "a"
list[-1] # "a"
list[-2] # 3.3
list[-3] # 1
A bool is a true or false value. You typically use the boolean type with IF statements and Boolean Expressions.
You can create a bool value using true or false or with the bool function.
a = true
b = false
c = bool(0) # false
d = bool(1) # true (not 0)
A func variable refers to a function. You use it to call the function, to get information about it, and to pass it around.
You create a func variable from an existing func variable or by defining a new function with the special “func” function, see User Functions.
All the built-in functions exist in the f dictionary by name. Each dictionary value is a list of func values with the same name. There are three cmp functions so the f dictionary value for it is a list of three function variables:
f.cmp => ["cmp","cmp","cmp"]
You can call a list of func variables. The function called is determined by the parameters passed, the one with the matching signature is called. All the built-in functions are lists containing one or more items. Below is an example calling a function given a list:
value = f.cmp(1,2)
You can call a func variable directly. In the following example the “a” func variable is defined to be equal to the second built-in cmp function.
l.a = f.cmp[1]
You call it with parentheses:
b = l.a(4, 7) #-1
You can have multiple functions with the same name as long as their signatures differ.
When you call a function variable without a prefix, statictea looks for it in the function dictionary.
When none of the signatures match the first argument, you see a message about that, for example:
shared.tea(1): w207: None of the 3 functions matched the first argument. statement: o.a = cmp(l, f) ^
When the first argument matches one of the signatures, that signature determines the message you see when one of the other arguments do not match. For example:
o.b = cmp(1, 4.5) shared.tea(2): w120: Wrong argument type, expected int. statement: o.b = cmp(1, 4.5) ^ o.c = cmp(1.9, 5) shared.tea(2): w120: Wrong argument type, expected float. statement: o.b = cmp(1.9, 5) ^
You can call functionDetails to get the signature, doc comment, statements etc. of a user or built-in function.
You use a boolean expression with an IF statement to make a decision that controls code flow.
A boolean expression is an infix expression wrapped with parentheses containing logical and compare operators that returns a true or false value.
In the following example the (3 == 4) is an expression and the e variable is assigned false because 3 does not equal 4.
e = (3 == 4) # false
You can write boolean expressions with the following operators:
- and
- or
- ==
- !=
- <
- >
- <=
- >=
You typically use a boolean expression with an IF statement. In the next example v is set to “s” because 3 is less than 5.
v = if((3 < 5), "s", "l") # "s"
Note: a conditional is wrapped in parentheses and there is no name on the left, so the following statement is invalid:
v = if(3 < 5, "s", "l") ^ invalid syntax
The comparisons have the highest precedence, then the logical “and” and “or”. Highest precedence to lowest:
- <, >, ==, !=, <=, >=
- and, or
You can control precedence with parentheses. The following two expressions are equivalent:
(a < b and c > d) => ( (a < b) and ( c > d) )
You can use multiple and’s or or’s in an expression. For example:
(a < b and c > d and e == f) (a < b or c > d or e == f)
If you mix ANDs and ORs, you need to specify the precedence with parentheses. For example:
( (a < b or c > d) and e == f)
The arguments are processed left to right and it uses short circuit evaluation. OR returns true on the first true argument and AND returns false on the first false argument and the rest are skipped.
AND and OR work with bool arguments. The comparisons work with numbers and strings.
NOT is not a logical operator but it is a function. You can pass a logical expression to it to invert it. Here are a couple of examples:
x = not( (a < b and c > d) )
y = (a < b and not((c > d)))
Note: If you need case insensitive string compare, use the cmp function.
A code file is a list of statements. You use a code file for defining variables for templates.
The template designer controls the code files.
Here is an example of a code file that defines two variables, pi and footer.
o.pi = 3.14159
o.footer = "</html>"
There are no prefix, postfix and other line decorations that you use in templates so the code is easier to read and write. It is suggested that you add most of your code in code files to minimize the amount in the template.
Below is an example of defining a multiline string header in a code file for sharing with templates. The code populates the o dictionary that is available to the template commands.
o.header = """
<!doctype html>
<html lang="en">
"""
You use the header in a template’s replace command
$$ replace t.content = o.header
See the Multiline Strings, Replace Command and User Functions sections for more information.
You import a code file from the command line with the -o or –code option. You can use multiple -o files and they run in the order specified.
The code files run after importing the JSON files so they have access to the server variables.
You can use the local variables for intermediate values but they disappear when the code file finishes running. For example the “a” variable is local:
a = 5
o.x = add(a, 6)
You can use the loop function to loop over a list and build lists and dictionaries. It’s the looping method you use in code files. You can loop over a dictionary using the keys function. You can pass variables to the callback with the state variable.
Processing continues after a warning except for a problem reading a multi-line string or a problem with line continuation which stops processing the code file.
Like template commands, the maximum line length is 1024 bytes and an ending plus sign continues a long statement on the next line.
Code files support multiline strings which are triple quoted UTF-8 encode strings.
They are useful for sharing template fragments without escaping characters. In the following example the header variable is assigned a two line string containing quotes and newlines.
o.header = """
<!doctype html>
<html lang="en">
"""
Both the leading and ending triple quotes end the line. Nothing follows the quotes except the lf or crlf. The following example is invalid because the leading triple quote does not end the line:
msg = """not valid""" ^ syntax error
The next couple of examples compare multiline strings with normal strings.
str = """
All the tea in China.
"""
is equivalent to:
str = "All the tea in China.\n"
The multiline string:
str = """
All the tea in China."""
is equivalent to:
str = "All the tea in China."
The advantage of a multiline string over a regular string is no quoting of the newline and other special characters. For example you can copy and paste HTML directly into the code file then mark variables in it:
o.header = """
<!DOCTYPE html>
<html lang="{s.languageCode}"
dir="{s.languageDirection}">
<head>
<meta charset="UTF-8"/>
<title>{s.title}</title>
"""
A multiline string can contain triple quotes as long as they don’t end the line.
str = """ This is a """triple quoted""" string. """
A multiline string literal cannot be an argument to a function. The workaround is to assign it to a local variable and pass that to the function.
str = """
Teas of China
"""
count = len(str)
You can define your own functions in code files.
User defined functions are important for sharing common code, as conditional code blocks, and as loop function callbacks for building lists and dictionaries.
In the following example we define a function called u.mycmp and assign the result to the u.mycmp function variable. The function takes two string parameters called numStr1 and numStr2 and it returns an integer.
u.mycmp = func(numStr1: string, numStr2: string) int
## Compare two number strings
## and return 1, 0, or -1.
num1 = int(numStr1)
num2 = int(numStr2)
return(cmp(num1, num2))
The first line names the function and defines its signature and assigns it to a variable. The function variable is stored in the u dictionary so it is visible inside other functions.
u.mycmp = func(numStr1: string, numStr2: string) int
The doc comment comes next and tells what the function does:
## Compare two number strings
## and return 1, 0, or -1.
The last lines of the definition are the statement lines. A bare return statement ends the function definition.
num1 = int(numStr1)
num2 = int(numStr2)
return(cmp(num1, num2))
The code is line based so the indentation doesn’t matter, there aren’t any brackets, and no line terminators.
Here is how you call the example function:
a = u.mycmp("1", "2")
Function Signature:
A function signature tells the function name, the parameter names and types and the return type. For the example above the signature string is:
u.mycmp = func(numStr1: string, numStr2: string) int
You can specify an optional last parameter and a parameter can use “any” when it accepts any type. The built-in get function is an example of optional and any.
get = func(dictionary: dict, key: string, default: optional any) any
Return Statement:
You use the return statement to return the function’s value and to finish the definition of the function. A bare return ends the function; you can use a return in a IF statement and they don’t end the function.
Function Local Variables:
The function‘s local variables and its arguments are stored in the function’s own l dictionary which exists while the function runs and is separate from the tea code local variables.
It contains the parameters and their argument values and local variables used in the function. The example below prints the l dictionary
u.get6 = func(msg: string) int ## Return 6. a = 3 echo(string(l, "dn", "l")) return(6) # output: l.msg = "hi" l.a = 3
You have access to f and u dictionaries inside the function. To access other external variables, you need to pass them in.
You run StaticTea from the command line. You specify the template file to process along with the json data files and code files and a result file is generated.
- Warning messages go to standard error.
- If you don’t specify the result file, the result goes to standard out.
- If you specify “stdin” for the template, the template comes from stdin.
- StaticTea returns success, return code 0, when there are no warning messages, else it returns 1.
The example below shows a typical invocation which specifies four file arguments, the server json, the shared code file, the template and the result.
statictea \ --server server.json \ --code shared.tea \ --template template.html \ --result result.html
The StaticTea command line options:
- help – show options and usage help then quit
- version – show the version number then quit
- server – a server json file You can specify multiple server files, see Server Json Variables.
- code – a shared code file. You can specify multiple shared options, see Code Files.
- template – the template file, or “stdin” for input from standard input.
- result – the result file. When not specified, standard out is used.
- update – update the template replace blocks. See the Update Option.
- prepost – a command prefix and postfix. You can specify multiple prepost options. When you specify a value, the defaults are no longer used. See the Prefix Postfix section.
- log - log to a file, see Logging section
Follow these guidelines when creating a new template. Open and test the template after each step in the process. For example an HTML file you would open it in a browser and validate it.
- Create a sample result file. For HTML create an HTML file. This will become your template.
- Identify the variable and repeating parts of the file. Put a command around each as a comment for your template type. This defines the replacement blocks.
- Inside the replacement blocks replace the variables with variable names in brackets, e.g. {name}.
- Create a test JSON file containing the variables defined in step 3. Create a list of dictionaries for repeating blocks.
- For repeating replacement blocks set t.repeat to the repeat count (the length of its JSON list).
- Run statictea combining the JSON file and the template to make a result file. Then edit the JSON or the template and repeat, perfecting the template.
In a project with many templates, you can share common template fragments using the replace command.
If you don’t have control over the JSON, create a tea code file and write code converting the JSON to variables you want in the template.
- Warning Messages
- Prefix Postfix
- Encoding and Line Endings
- Update Option
- Logging
- Special Forms
- IF Function
- Docs and Templates
- HTML Formatted Json
- Nimble Tasks
- StfRunner
- REPL Environment
- Docker Development
- Mac Development
- Ideas
When StaticTea detects a problem, a warning message is written to standard error, the problem statement is skipped, and processing continues.
It’s good style to change your template to be free of messages.
Each warning message shows the file and line number where the problem happened.
example messages:
- tea.html(0): w15: “Unable to parse the json file. Skipping file: test.json.
- tea.html(45): w61: No space after the command.
Statement errors generate multi-line messages showing the statement and problem location, for example:
template.html(16): w33: Expected a string, number, variable, list or condition. statement: tea = len("abc",) ^
Warnings are suppressed after the first 32. When you reach the limit you will see the message:
You reached the maximum number of warnings, suppressing the rest.
Statictea returns success, return code 0, when there are no warning messages, else it returns 1. If you want to treat warnings as errors, check for the 1 return code.
You can generate your own warnings messsage using the warn function. Like the system warning messages it skips the current statement, increments the warning count and produces a non-zero return code.
For example if the server item list should contain one or more items, you could output a warning when it’s empty:
if((len(s.items) == 0), warn("no items"))
The prefix and postfix are the leading and ending comment characters your template uses. For html they are “<!–” and “–>”.
You tell statictea what they are then you use them in the template so all the statictea commands are comments.
This allows you to edit the template using its native editors and run other native tools. For html, you can view it in a browser, edit with an html editor and validate it online with w3.org’s validator.
Comment syntax varies depending on the type of template file and sometimes depending on the location within the file. StaticTea supports several varieties and you can specify others.
You want to distinguish StaticTea commands from the file’s normal comments so it is clear which comments are which. Normal comments appear in the result and StaticTea comments don’t. The convention is to add a $ as the last character of the prefix.
<!- normal html comment -> <!-$ # statictea comment ->
Some file types, like markdown, don’t support comments, for them use $$.
Built-in Prefixes:
- markdown: $$
- html: <!–$ and –>
- html inside a textarea: <!–$ and –>
- bash: #$
- config files: ;$
- C++: //$
- org mode: # $
- C language: /*$ and */
You can define other comment types on the command line using the prepost option one or more times. When you specify your own prepost values, the defaults no longer exist so you have control of which prefixes get used.
You specify the prepost option with the prefix separated from the postfix with a comma and the postfix is optional, ‘prefix[,postfix]’. A prefix and postfix contain 1 to 20 ASCII characters including spaces but without control characters or commas.
Note: It’s recommended that you use single quotes so the command line doesn’t interpret $ as an environment variable.
examples:
--prepost 'pre$,post' --prepost 'a$,b' --prepost '@$,|' --prepost '#[$,]#' --prepost '# $'
Templates are treated as a stream of bytes. The embedded statictea commands only use ASCII except for quoted strings which are UTF-8 encoded.
Two line endings are supported on all platforms: LF, and CR/LF and they are preserved.
The maximum command line length is 1024 bytes. There is no limit on non-command lines.
Since line endings are preserved and there are no encoding or line length restrictions on non-command lines, you can make templates out of binary or mixed binary and text files like EPS or PDF files.
The update option updates the template’s replace blocks to match their t.content text. The text normally comes from the shared code files but it doesn’t have to.
You use this to keep the template blocks in sync with the shared content so you can work with them as static pages.
If the t.content does not end with a newline, one is added so the endcommand starts on a new line.
The following example shows a typical invocation:
statictea \ --server server.json \ --code shared.tea \ --template template.html \ --update
If the template content comes from the standard input stream the result goes to the standard output stream.
See the replace command for update examples.
Statictea appends statistics to the log file. Template commands can also append to the log file.
Logging is off by default. You turn it on with the log option. If you don’t specify a filename, the log lines are written to the platform default location:
- Mac: ~/Library/Logs/statictea.log
- Other: ~/statictea.log
You can specify a full path. If you don’t include path information, the log is written to the current directory.
statictea --log mylog.txt
The template file and line number appear in the log. For example:
2021-12-07 22:03:59.908; statictea.nim(42); Starting: argv: @["-l log.txt", "-t tmpl.txt", "-r result.txt"] 2021-12-07 22:03:59.908; statictea.nim(43); Version: 0.1.0 2021-12-07 22:03:59.909; tmpl.txt(2); ┌─────────┐ 2021-12-07 22:03:59.909; tmpl.txt(3); │log block│ 2021-12-07 22:03:59.909; tmpl.txt(4); └─────────┘ 2021-12-07 22:03:59.910; statictea.nim(66); Warnings: 0 2021-12-07 22:03:59.910; statictea.nim(69); Return code: 0 2021-12-07 22:03:59.910; statictea.nim(70); Done
The normal way functions behave when you call them is known as the normal form. A few functions deviate in one or more ways from the normal form and these are known as special forms.
The normal form you call with parentheses and pass a fixed number of arguments which get evaluated before hand. The function returns a value and there are no side effects.
A normal statement has a left side, an operator, and a right hand side, for example:
a = len("tea")
Below are the list of special functions and how they deviate from the normal form:
- case — conditionally evaluates its arguments
- echo — writes a message to standard out and has a bare form
- func - defines a function, see User Functions
- if - conditionally evaluates its arguments and it has a bare form, see IF Function
- list - takes any number of arguments
- loop — has a bare form
- log - writes a message to the log file and has a bare form
- return - exits the command block or function with a return value and has a bare form
- warn — exits the statement with a warning and has a bare form
If the form doesn’t have an assignment it’s called a bare function. The echo, warn, return, if, loop and log have bare forms.
Special Form Examples:
v = list(1,2,3,4,5,6,7)
v = if(b, 5, 6)
if(c, warn("abc"))
if(c, return("abc"))
newList &= if( (x > 5), value)
warn("the tea is hot")
echo("the tea is delicious")
return(1)
log("log message")
a = case(22, [11, 1, 22, 2])
The IF function is special in a few ways. It conditionally evaluates its arguments and it can be used in a statement without an assignment.
There are three basic forms, a three parameter form, a two parameter form and a bare two parameter form. Here are examples:
tea = if(cond, "black", "green")
newList &= if(cond, value)
if(cond, return("stop"))
Three Parameter Form
Normally functions evaluate all their arguments before passing them to the function but the three parameter IF evaluates its condition argument first to determine which argument to evaluate next and the non-matching argument is skipped.
In the following example do-this is executed and do-other is skipped.
a = if(true, do-this(), do-other())
Two Parameter Form
The two parameter form assigns the variable when the condition is true but not when it’s false. You use it to conditionally append to a list or dictionary. You cannot use this form as an argument to another function.
newList &= if(cond, value)
d.tea = if(cond, value)
Bare Two Parameter Form
The bare two parameter form doesn’t have an assignment. You use it with the echo, warn and return functions for their side effects.
if(c, return("stop"))
if(c, warn("buy more tea!"))
All the documents for this project, except this readme, were created with Statictea templates. The resulting documents are useful as documentation as well as examples for building your own templates. They also the serve as test cases.
Below we show the resulting documents and their associated templates and we tell which nimble task builds it.
Nim Source Docs
Statictea is written in the Nim language. The documentation for the functions and types exists in the source code as nim doc comments. The nim compiler provides a command to export these doc comments to a JSON file. The template uses the JSON to produce the resulting documentation in markdown format.
The markdown format was choosen so the source and documentation can reside in the same github repository. Github renders markdown (unlike HTML) without having to create a separate github.io repo.
The index was created with one template and the module docs with another. The nimble docmix task builds the module index and the docm task builds one or more modules docs. For more details see the HTML Formatted Json section.
For easier reading of the markdown templates, click the link then switch to “View Raw”.
- Source Code
- nimModuleIndex.md (view raw)
- nimModule.md (view raw)
- nimModule.tea
HTML Docs
A second set of nim source documents exist for HTML. They are designed for viewing locally on your machine and for testing. The HTML templates share the same tea code files with the markdown templates. The nimble dochix task builds the module index and the doch task builds one or more HTML modules docs.
- Open docs/html/index.html in your browser.
- nimModuleIndex2.html HTML template
- nimModule2.html HTML template
- nimModule.tea
Functions
The Statictea language functions doc was created from statictea’s embedded docs by looping over the f dictionary and fetching each function’s docComment. The nimble funcdocs task builds the template.
- Functions
- teaFunctions.md (view raw)
- teaFunctions.tea
Built-in Docs
Statictea function documentation is built in to the program. The documentation is extracted from the nim functions.nim module which is the “source of truth”.
The nimble dyfuncs task extracts the docs and creates the nim source file dynamicFunctions.nim which gets complied into statictea.
Since the function documentation is built in you can display it using the pf REPL command, see REPL Environment.
Stf Tests
The stf files used to test statictea exist in the testfiles folder. Each stf file is a test file that’s also a markdown file. The nimble stfix task extracts a sentence from the top of each stf file and makes an index from that with a template.
- StaticTea Stf Files
- stf-index.md (view raw)
Highlighted Tea Code
You can view the tea code files found in the templates folder syntax highlighted. The nimble tea2html task runs a statictea template that creates colorful HTML files. They are designed for viewing locally on your machine and for testing the syntax highlighter.
- tea2html.html – HTML template
- tea2html.tea – tea code file for the template
Dev Index
The dev.html file in the root folder is a master index to the svg files and to all the markdown and html documents. It’s handy for developers with the source code for debugging the docs. Open the dev.html file in your browser using the file command.
Local Markdown
You can view the markdown documents locally on your machine using the grip server. You start the server then navigate to the index.md file.
# from statictea directory grip dev.html * Running on http://localhost:6419/ (Press CTRL+C to quit)
In your browser enter the index.md file:
http://localhost:6419/docs/md/index.md
The nim jsondoc command produces html formatted descriptions. Our desired final format is Github markdown so having html presentation data in the JSON is a problem.
The jsondocraw command produces a JSON file like jsondoc, except the descriptions match the text in the source file. Jsondocraw calls nim’s jsondoc command then patches the descriptions.
The nimble jsondoc command creates the jsondocraw exe and the nimble json command runs it on one or more files.
Nim’s jsondoc command assumes the doc comment is formatted reStructuredText (RST). It converts the RST to HTML for the json description. If you don’t specify RST, you are likely to specify something that causes the RST parser to fail which fails the command.
The jsondocraw command has a workaround for this. You use a blank line as the doc comment then follow that with the real doc comment specified with “#$ ” instead of “## “.
For example:
func sample*() = ## #$ This is a sample #$ workaround doc comment echo “tea”
The leading ## is required for nim’s jsondoc to record it in the json. The jsondocraw reads the comments’ line number then reads the source file to extract the raw doc comment and strip the leading whitespace.
You can run commands to build, test, make docs etc. using nimble task commands. Run them from the statictea root folder. The n task lists the available tasks.
cd ~/code/statictea nimble n
Nimble Tasks:
- n: Show available tasks.
- test: Run one or more tests; specify part of the name.
- other: Run stf tests, build release exe and other tests.
- docsall: Create all the docs.
- release: Run tests and update docs; test, other, docsall.
- b: Build the statictea release exe (bin/x/statictea).
- docm: Create one or more markdown docs; specify part of the name.
- doch: Create one or more html docs; specify part of the name.
- docmix: Create markdown docs index (docs/md/index.md).
- dochix: Create html docs index (docs/html/index.html).
- jsonix: Display docs index json.
- json: Display a source file’s json doc comments; specify part of the name.
- teafuncs: Create the function docs (docs/md/teaFunctions.md).
- dyfuncs: Create the built-in function details (src/dynamicFuncList.nim) from (src/functions.nim).
- dotsrc: Create source module dependency graph (docs/staticteadep.svg).
- dotsys: Create system modules dependency graph (docs/staticteadep2.svg).
- tt: Compile and run t.nim.
- tree: Show the project directory tree.
- args: Show command line arguments.
- br: Build the stf test stfrunner (bin/x/stfrunner).
- rt: Run one or more stf tests in testfiles; specify part of the name.
- stfix: Create stf test files index (testfiles/stf-index.md).
- stfjson: Display stf test files index JSON.
- newstf: Create new stf test skeleton, specify a name no ext.
- runhelp: Show the stfrunner help text with glow.
- helpme: Show the statictea help text.
- cmdline: Build cmdline test app (bin/x/cmdline).
- jsondoc: Build jsondocraw app (bin/x/jsondocraw).
- drun: Run a statictea debian docker build environment
- ddelete: Delete the statictea docker image and container.
- dlist: List the docker image and container.
- clean: Remove all the binaries so everything gets built next time.
- replace: Show pattern for text search and replace in all the nim source files.
- tea2html: Create one or more html docs from the templates dir; specify part of the name.
The Single Test File (stf) runner is a standalone program in this project used for testing command line applications.
A stf file defines the test which the stfrunner executes to determine whether the test passed. The stf file contains instructions for creating files, running files and comparing files.
The stf files are designed to look good in markdown readers. The testfiles folder contains StaticTea Stf Files.
See the stfrunner help message for more information about stf files. The nimble runhelp task shows the stf help with glow.
You can run statictea code interactively at a prompt. You run statements at the prompt and print variables with commands.
The -x option starts the Read Eval Print Loop (REPL) and tea is the prompt.
statictea -x tea>
The prompt appears after processing code files and importing json files, so you can inspect values they create.
statictea -x -o codefile.tea -s in.json tea>
In the following example the statement “z = 5” is entered then z is printed. P is short for print.
statictea -x tea> z = 5 tea> p z 5
You can use several commands for printing out values. The help command (h) shows them.
tea> h Enter statements or commands at the prompt. Available commands: * h — this help text * p — print a variable like in a replacement block * pd — print a dictionary as dot names * pf - print function names, signatures or docs, e.g. f, f.cmp, f.cmp[0] * plc - print a list in columns * plv - print a list vertical, one element per line * v — print the number of variables in the one letter dictionaries * q — quit (ctrl-d too) tea>
You can explore the built-in functions with the REPL commands. We show some examples using the v and pf commands below.
You can view the number of variables in the one letter dictionaries with the v command. In the following example the f dictionary has 52 variables.
tea> v f={52} g={} l={} o={} s={} t={3} u={}
You can list all the built-in function names with the pf command specifying f as shown below.
tea> pf f add find join parseCode startsWith anchors float joinPath parseMarkdown string bool format keys path sub case func len readJson type cmp functionDetails list replace values cmpVersion get loop replaceRe warn dict html log return dup if lower slice exists int not sort
Each element in the f dictionary is a list of the function variables with the same name. You can print a list to see each signature. For example there are three cmp functions:
tea> pf f.cmp 0: cmp = func(a: float, b: float) int 1: cmp = func(a: int, b: int) int 2: cmp = func(a: string, b: string, c: optional bool) int
You can print the documentation of a built-in function with the pf command by specifying the function variable:
tea> pf f.cmp[0] Compare two floats. Returns -1 for less, 0 for equal and 1 for greater than. cmp = func(a: float, b: float) int Examples: cmp(7.8, 9.1) # -1 cmp(8.4, 8.4) # 0 cmp(9.3, 2.2) # 1
You can use the statictea docker image as a development environment. It is a Debian system configured with the nim compiler and other applications needed to build and test.
The statictea host source folder is shared with the docker container, so you can use your own editor and applications on the host as well.
Steps:
- download code
- start docker environment
- build nim (one time)
- build statictea
Download Code
Download the statictea source code to a folder on your system in a terminal window and pull the release version tag.
mkdir ~/code/statictea cd ~/code/statictea git clone [email protected]:flenniken/statictea.git . git tag git checkout v0.1.1 # variable git switch -c v0.1.1 # variable
Start Docker Environment
You start the docker statictea environment with the nimble drun command. The first time you run it, it builds the docker image and stops so you can check for errors. You run it again to start the env.
nimble drun nimble drun
Build Nim
You build nim from source in the environment using the build_all.sh script. This step is needed the first time you run the container, or after you delete it.
cd ~/Nim ./build_all.sh nim -v Nim Compiler Version 2.2.0 [Linux: arm64] Compiled at 2024-10-20 Copyright (c) 2006-2024 by Andreas Rumpf git hash: 78983f1876726a49c69d65629ab433ea1310ece1 active boot switches: -d:release
Build StaticTea
You build the statictea executable in the environment using the nimble release command. It runs all the tests and builds the exe and docs.
cd ~/statictea n release bin/debian/statictea -v 0.1.3
Note: Mac development is on pause until it is easier to install nim there. Currently choosenim doesn’t work on an ARM Mac. The following instructions are for an Intel Mac.
Besides the docker environment, you can develop on a mac.
You setup for it is similar to the docker environment except you need to install the needed apps to your mac manually. Once you’re setup, you run the release command.
Download Code
Download the statictea source code to a folder on your system:
mkdir ~/code/statictea cd ~/code/statictea git clone [email protected]:flenniken/statictea.git . git tag git checkout v0.1.1 # variable git switch -c v0.1.1 # variable
Download Nim
Download nim following instructions on their website: https://nim-lang.org/install.html.
Install Helper Apps (optional)
On the mac you install the helper applications using brew. They help debugging issues.
- glow —- for viewing markdown in your terminal
- jq -— for viewing JSON
- grip -— for viewing markdown in your browser
- tree —- for viewing the statictea directory structure
- graphviz dot -— for creating dependency charts
Install using brew at the command line:
brew install glow brew install jq brew install grip brew install tree brew install graphviz
Build
Then you build the statictea executable using the nimble release command. It runs all the tests and builds the exe and docs.
cd -/code/statictea nimble release
- Advantages
- How it Works
- Commands
- Replacement Block
- Statements
- Syntax
- Variables
- Single Letter Dictionaries
- Types
- Boolean Expression
- Code Files
- Multiline Strings
- User Functions
- Run StaticTea
- How To
- Miscellaneous (+15)
- Functions
Tea plant: Camellia sinensis
Tea is the most popular manufactured drink consumed in the world, equaling all others – including coffee, soft drinks, and alcohol – combined. – Wikipedia – Macfarlane, Alan; Macfarlane, Iris (2004). The Empire of Tea. The Overlook Press. p. 32. ISBN 978-1-58567-493-0.