🐮 MOO (moolang)

⚡ 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;
}