⚡ About
This is a build system that inlines external configuration and files. It aims
to reconcile its creator's three contradictory truths of software development:
- Monoliths are lightweight (no adding communication layers).
- Separation of concern is good.
- Splitting configuration from code is tedious.
No configuration files! Only your code and the MOO‑NOLITH.
🚀 Quickstart
Download the moo executable for your platform
from the latest release.
If something goes wrong, or if you are not in Linux or Windows, clone the
project's repository
and run g++ moo.cpp -o moo -Wall -O3 to produce the executable. Add the executable
either to your working dir or to your system path. Then run the next command given the following files
(this particular build also requires Python only to demonstrate interaction with system utilities).
./moo examples/hello.txt.moo
examples/hello.txt.moo
Hi!
/**/ moo.safe += pattern {moo.python} scripts/*
/**/ os = system {moo.python} scripts/os.py
/**/ user = $world
/**/ import examples/hello.{os}.txt.moo
- Be safe out there.
examples/hello.linux.txt.moo
Hello /***str {user}***/ from a linux file!
scripts/os.py
import platform
system = platform.system()
print(system.lower(), end="")
Running the above on a Linux OS creates examples/hello.txt (the .moo suffix is stripped):
Hi!
Hello world from a linux file!
- Be safe out there.
📚 Docs
Import a file by parsing its contents according to moo and then placing the
result inline. The pattern for this is import filepath. If you do not want to process the file
but directly process its contents use read filepath instead.
In either case, the language fundamentally lacks the ability to harm your data other than creating
one explicit file and adding safety exceptions in main file that cannot be expanded upon by imported
files.
When parsing moo files, instructions can be used within other material so as to generate code and perform various tasks.
To make the instructions distinct, they reside in lines starting with /**/.
Use /*** code ***/ for multi-line blocks or inline replacement.
Variables are created per varname = command expression and are immutable once created.
Their name must start with a letter or underscore (_) and they can contain any such letter, number, underscore,
or dot. Commands correspond to various means of computing values given an expression.
The simplest one is interpretation as a string with str (or $ that looks similar)
like so:
/**/s = str Hi!
/**/s
This just writes Hi! inside the resulting file; parsing just one variable as moo code
in the second line replaces it with its string representation. All variables have such a representation.
Setting a variable creates no text, but you can use the variable's name or simply the pattern command expression
without any assignment to obtain text that is added to the file being parsed.
Before expressions are processed by the command, they may resolve nested moo code enclosed in brackets. For example, the following creates a set of line-separated values 1,2,3 (how the range works will be shown later) and replaces both the range and a new line symbol within the string being parsed.
/**/s = str This is a range of values: {moo.symbols.line} {range 1 4}
/**/s
When importing files, variables are inherited from their parents. However, variables other than moo.safe can be shadowed locally with different values. Some common variables that are supplied by default are presented below. What being a list means is covered next:
| Variable | Details |
|---|---|
moo.args |
Extra arguments passed to the language. |
moo.cwd |
The working directory from which the language was invoked. |
moo.python |
Absolute path to the running Python interpreter. |
moo.safe |
A list of whitelisted command prefixes (cannot be shadowed). |
moo.symbols.line |
New‑line character \n. |
moo.symbols.comma |
Comma character ,. |
moo.symbols.space |
Space character. |
moo.symbols.lbracket |
Left bracket character {. |
moo.symbols.rbracket |
Right bracket character }. |
moo.symbols.colon |
Colon character :. |
moo.symbols.pipe |
A text representation of the pipe operator {...}. |
Lists hold an ordered collection of values created per listname = list delimiter.
When a list is converted into text, for example via /***listname***/,
its elements are joined using the delimiter declared upon list creation. Add an element to a list
per listname += command expression, where the commands and expressions
are the same to what you would use for variables.
One special list type are ranges, which are line-separated and
created per range start end, where the start is a first integer and end
the range's end (non-inclusive). For example range 1 4 creates a range
with elements 1,2,3.
In addition to direct conversion of lists to text, there is a mechanism to set placeholders in the text that are declared early but filled only when all list elements have been determined - mainly after parsing the whole file. The mechanism for doing so is demonstrated below:
/**/ HEAD = list {moo.symbols.line}
/**/ BODY = list {moo.symbols.line}
<head>/*** {placeholder HEAD} ***/</head>
<body>/*** {placeholder BODY} ***/</body>
/**/ HEAD += str <title>My page</title>
/**/ BODY += Hello world!
For statements can loop through list values.
In particular, the syntax for varname=command expression: code
sets varname as a loop variable that progressively assumes all elements in the list
as values, and executes the internal code. That code can be assignments, system runs, etc.,
but its goal can also be to yield values. This is because loop results are gathered into
lists (empty text is skipped), as shown below:
/**/numbers = for number=range 1 4: str - number {number}
Here are some numbers:
/**/numbers
This yields:
Here are some numbers:
- number 1
- number 2
- number 3
When creating loops, the loop variable must not exist (or you can guard it within namespaces explained later) and is removed afterwards. This is a convenient mechanism for also declaring temporary variables by considering them lists of one element, like below. Do note that command-expression pairs can be substituted by just a variable name:
/**/for one=str 1: str One is {one}.
/**/x = str 42
/**/for value=x: str The Answer is {value}.
This yields:
One is 1.
The Answer is 42.
Patterns are text that contains some wildcard character regions, designated
by *. Declaring a pattern is performed per pattern expression where the expression.
Patterns, regular text, and lists all accept a matchin operation that compares them to some
text value. Its syntax is match query: expression where the query is a query expression.
If the query is normal text, this is an exact comparison. If it is a pattern, pattern compliance is
checked. If it is a list, matching with at least one element is checked. The result is either True
or False.
An example of pattern matching is presented below, which also demonstrates the if command.
This has a similar structure to loops, with the difference that the code after the double colons
is executed only if the checked expression is True.
/**/ testlist = list {moo.symbols.line}
/**/ testlist += pattern el*
Does the pattern match?
/**/ if str {match testlist: element}: str matched!
System commands can run with the command system expression. Similarly to other
constructs, brackets inside expression are expanded into text. The moo.safe list
determines allowed system commands through an internal match operation.
Use schedule expression to run a system command after
generatinc the output file. Scheduled commands may, for example, call build processes on the resulting file.
System commands are executed concurrently in separate operating system processes; this is typically parallelized.
They are both cached and synchronized with the rest of the code lazily, at the point when their string representations
are needed. For example, using for loops to create command batches means that they are synchronized only
when converting the loop's outcome to text. As an example, below is moo's test suite that use this principle.
/**/ moo.safe += str rm tests
/**/ moo.safe += pattern rm examples/*
/**/ moo.safe += pattern {moo.python} moo.py examples/*
/**/ schedule rm tests
/**/ print Running tests
/**/ system {moo.python} moo.py examples/hello.txt.moo --nocolor
/**/ for i=range 1 8: system {moo.python} moo.py examples/file{i}.txt.moo --nocolor
/**/ print Cleanup
/**/ system rm examples/hello.txt
/**/ for i=range 1 8: system rm examples/file{i}.txt
Lambda expressions are basically expression
segments that are injected in the code later. Use the do
command to run text code.
/**/ moo.safe += {moo.python} scripts/
/**/ b64 = str system {moo.python} scripts/b64.py
Command to be run (for debugging):
/**/ b64
Actually run the b64 conversion:
/**/ do {b64} examples/file1.txt.moo
scripts/b64.py
import sys
import base64
from pathlib import Path
def encode_file(in_path: Path):
# Standard Base‑64 with newline every 76 characters (RFC 2045)
data = in_path.read_bytes()
return base64.encodebytes(data).decode("ascii").strip()
if __name__ == "__main__":
argv = sys.argv[1:]
if len(argv) != 1:
sys.stderr.write(f"b64 need exactly one input: {argv}\n")
sys.exit(1)
in_path = Path(argv[0])
if not in_path.is_file():
sys.stderr.write(f"file not found: {in_path}\n")
sys.exit(1)
b64 = encode_file(in_path)
sys.stdout.write(b64)
sys.stdout.flush()
One helper utility is the pipeing operator {...}, which is used
to place the remainder of the. You can inject this into your text using the
moo.symbols.pipe symbols.
Below is an equivalent command to the one defined above that does not use the external Python script;
base64 encodings are provided by moo, and this expression will run tens of times faster.
Notice the use of $$, where the first of these repeted symbols denotes a string,
and the second will be used to denote a string (the outcome of the encoding) when the command expands.
/**/ imgtype = $data:image/png;base64,
/**/ b64 = $${imgtype}{moo.symbols.pipe} base64.encode.text {moo.symbols.pipe} file.raw
/**/ do {b64} examples/lettuce.png
Namespaces let you group related statements and optionally disable the whole group at runtime.
In particular, if the enabled condition evaluates to False, all statements in that namespace
are ignored for the rest of the file. A disabled namespace cannot be re‑enabled later.
In the example below, a namespace is disabled based on the existence of a specific argument in the list of program arguments.
For example, call the script below per ./moo main.c.moo --compile to evoke its compilation process.
// src/main.c.moo
///**/ if ${match moo.args: $--compile}: enabled COMPILE
///**/ COMPILE: moo.safe += gcc
///**/ COMPILE: schedule gcc -Wall -O3 -o lettuce src/main.c
#include <stdio.h>
int main() {
printf("Hello world!\n");
return 0;
}