The Origami language is relatively small and focused on:
- Defining tree structures like that of a website’s pages and resources
- Traversing paths into trees of data or files
- Creating text content using templates
- Invoking functions defined in other languages like JavaScript
Origami expression syntax generally follows JavaScript, with some differences such as: URLs, file paths, and file names are valid references; and some shorthands make it easier to type expressions in the shell when using the CLI.
Microsoft Visual Studio Code users can install the Origami language extension for syntax highlighting.
Strings
Strings use single or double quotes:
'string'
"string"
You can escape characters with a \
backslash:
'It\'s great'
Since command lines often process both single and double quotes, Origami also supports the use of French-style «
and »
guillemet quote characters:
$ ori «Hello»
Hello
On macOS, you can type the «
character with Option+Backslash, and the »
character with Option+Shift+Backslash. On Windows, you can type Alt+0171 and Alt+0187, respectively.
Numbers
On their own, integers or floating-point numbers are treated as JavaScript Number
values.
42
3.14159
Numbers that appear in paths are treated as strings:
years/2023
searches in the years
tree for the key “2023”.
If you have a string that you want to convert to a number, use the Number
builtin function. Like any function, you can call it with parentheses or a slash:
Number("123") // the number 123
Number/123 // the number 123
References
Unquoted character sequences will be evaluated in the current scope to find, for example, a file or other tree value with the indicated name.
$ ls
Hello.md
$ cat Hello.md
Hello, world.
$ ori Hello.md
Hello, world.
In the last command above, Hello.md
is evaluated as a reference. In this case, it finds the local file, Hello.md
, so ori displays the contents of that file.
Unlike JavaScript identifiers, it is legal to include a .
period in a reference. Spaces and the following characters
(){}[]<>?!=,/:`"'«»\→⇒…
must be escaped with a \
backslash or in quotes.
Example: If you have a file with a space like foo bar
, you could reference it with
foo\ bar
If you know the file’s in a particular folder, such as the current directory, you could also use:
./("foo bar")
Or you can search the current scope with the scope: protocol:
scope:("foo bar")
A reference that’s entirely numeric digits also requires special handling or it will be treated as a number. For example, you might have folders organized by year with names like 2025
. To force Origami to treat that as a reference, you can write:
scope:2025
Object literals
Object literals are created with {
and }
curly braces, and contain key/value pairs:
{
name: "Alice"
location: "Honolulu"
}
You can separate key/value pairs with commas, newlines, or both. Keys should not be quoted.
As with references (above), you can use periods in keys. Escape any special characters like spaces:
{
Test\ File.txt: "Sample text"
}
You can also put keys in single or double quotes:
{
"Test File.txt": "Sample text"
}
As a shorthand, you can define a property with just a key. The key will be used as a reference (above) that will look up that key in the current scope.
{
// Include the project's ReadMe.md file in this object
ReadMe.md
}
The value in a key/value pair will be evaluated once when the object is loaded. The following object, for example, evaluates getName()
only once:
{
name: getName()
}
Object properties can reference other local properties
The expression that defines the value of a property can reference other properties in the same object, or any parent object, by name.
// localRef.ori
{
a: 1
b: a
}
This evaluates to:
$ ori localRef.ori/
a: 1
b: 1
This type of local reference is not possible in languages like JavaScript.
Origami will avoid recursive local references. If you try to define a name
property whose value expression refers to name
, Origami assumes the latter refers to an inherited name
property.
// inherited.ori
{
name: "Alice"
user: {
name: `My name is ${name}`
}
}
Here the expression ${name}
will resolve to the inherited name
defined in the parent object.
$ ori inherited.ori
name: Alice
user:
name: My name is Alice
Object property getters
If you’d like a value to be calculated every time it’s requested, you can create a property getter. You create a property getter in Origami by using an =
equals sign to define the property instead of a :
colon.
{
index.html = greet.js()
}
This .ori
expression defines a getter called index.html
. Each time the object is asked for index.html
, it will invoke the JavaScript function exported by greet.js
.
Object nesting
Objects can be deeply nested.
One use for this is to define the overall structure of a website in a .ori
file:
{
// Include the entire `assets` folder
assets
about: {
// Generate the index page for the "About" area
index.html = about.ori()
}
}
When this file is served as a site, the user will be able to browse to about/index.html
.
Array literals
Arrays use [
]
square brackets.
[1, 2, 3]
As with object literals (above), you can separate array items with commas, newlines, or both:
[
1
2
3
]
Spread operator
You can use ...
three periods or the single …
ellipsis character to merge arrays and objects.
$ ori tree1.yaml
a: The letter A
b: The letter B
c: This will be overwritten when merged
$ ori tree2.yaml
c: The letter C
d: The letter D
e: The letter E
$ ori { ...tree1.yaml, ...tree2.yaml }
a: The letter A
b: The letter B
c: The letter C
d: The letter D
e: The letter E
In an .ori
file, you can use this to merge a folder into an object that also defines individual files.
{
index.html: "Hello!"
// Merge in everything in the `styles` folder
…styles
}
The built-in merge
function performs this same operation as a function.
Function calls
You can invoke a function with parentheses:
fn(arg)
The arguments to a function will be evaluated, then the function will be invoked:
$ ori sample.txt
This is a text file.
$ ori uppercase.js
export default (x) => x.toString().toUpperCase();
$ ori "uppercase.js(sample.txt)"
THIS IS A TEXT FILE.
To make it easier for you to invoke functions in the command line, Origami expressions also let you use implicit parentheses for function calls. For example, the above can also be written as:
$ ori uppercase.js sample.txt
THIS IS A TEXT FILE.
In some situations, you can also avoid the need for parentheses by using a /
slash; see below.
Paths
Paths are a sequence of string keys separated with /
slashes.
tree/with/path/to/something
The head of the path, like tree
here, is resolved as a reference. If that returns a treelike object, it will be traversed using the remaining keys “with”, “path”, “to”, and “something”. Treelike objects include object literals and hierarchical data in JSON and YAML files:
$ ori greetings.yaml
Alice: Hello, Alice.
Bob: Hello, Bob.
Carol: Hello, Carol.
$ ori greetings.yaml/Alice
Hello, Alice.
In Origami, functions are a type of tree, so a function can also be invoked with slash syntax:
$ ori greet.js
export default (name = "world") => `Hello, ${name}.`;
$ ori greet.js/David
Hello, David.
A function call using a slash like this lets you avoid having to quote parentheses in the command line.
If the greet.js
function here is invoked with a trailing slash, that invokes the function with an undefined
value for the name
parameter. In this example, greet
then uses a default name:
$ ori greet.js/
Hello, world.
File paths
Paths that start with a leading /
slash refer to absolute paths in the filesystem:
/Users/alice/example.txt
Similarly, paths that start with a leading ./
refer to relative paths, and paths that start with ../
refer to the current parent folder.
Paths can traverse into files of known file types like .json
files:
$ ori /Users/alice/myProject/package.json
{
"name": "Test project",
"type": "module"
}
$ ori /Users/alice/myProject/package.json/name
Test project
You can reference folder or file by name without any special leading character; such names will be resolved in scope. Example: if a project contains a folder called src
, then the following are equivalent:
$ ori ./src
$ ori src
Inside an Origami program, if you have a local variable that has the same name as a folder in scope, and you want to explicitly reference the folder instead of the variable, you can use the files:
protocol.
Namespaces
Origami organizes its built-in functions and objects into namespaces: groups of things with a name. These names end in a colon.
Some namespaces like https:
act like protocols in URLs:
https://example.com
The result of this URL will be the contents of the data at the indicated internet location.
In addition to https:
and http:
, Origami has some custom protocols like files:
.
Other namespaces like dev:
and tree:
act as containers for functions and objects built into Origami. For example, the tree:
namespace contains a function called tree:plain
that converts a tree (of markdown files, say) to a plain JavaScript object.
tree:plain(markdown)
Shorthand for builtin functions
For faster typing in the command line and for more concise code generally, Origami allows built-in functions like tree:plain
to be called in a shorthand form like plain
that omits the namespace.
This means the following commands are equivalent:
$ ori tree:copy src/site.ori, tree:clear ./build
$ ori copy src/site.ori, clear ./build
To distinguish between builtin function names and your own function names, the Origami parser enforces the following rule for the names of functions: if a function name contains only letters and numbers, it is taken to refer to a builtin. This rule applies to the names of functions:
- invoked with parentheses
- invoked with spaces that represent implicit parentheses
- invoked as a tagged template (see template literals, below)
Examples:
map(markdown, page.ori)
—map
is taken as a shorthand for thetree:map
becausemap
is only letters and appears before parentheses.yaml package.json
—yaml
is a shorthand (fororigami:yaml
) because it’s only letters and appears in the function position. The parentheses are implicit; it’s the same asyaml(package.json)
.uppercase.js(sample.txt)
—uppercase.js
is not a builtin name because it contains a period.mdHtml("Hello")
—mdHtml
is a shorthand (fortext:mdHtml
).mdHtml/Hello
—mdHtml
isn’t immediately assumed to be a shorthand because it’s being called with slash syntax (see Paths, above). The referencemdHtml
will be resolved in scope. If a folder exists called “mdHtml”, the reference will resolve to that folder, not the builtin, but if there is nothing else in scope called “mdHtml”, thenmdHtml
will ultimately resolve to the builtin.
In the last example, using a slash path prevented mdHtml
from being immediately taken as the name of a builtin. In some cases you may have a local variable that contains a function, and the variable’s name is only letters and numbers, such that it will be assumed to be a shorthand for a builtin function. If you want the name to reference the local variable in a function call, append a /
slash to the name:
// Call `fn` as a function even though its name looks like a shorthand
(fn) => fn/()
Alternatively, place parentheses around the name:
// Another way to call `fn` as a function
(fn) => (fn)()
Both techniques cause the fn
reference to be treated as a regular scope reference. In both cases the reference will find the local fn
parameter, which can then be invoked as a function.
Template literals
Text templates are quoted in backticks and can contain Origami expressions inside ${
}
placeholders. The evaluated expression results will be substituted for those placeholders in the template’s text output.
$ cat pet.txt
Fluffy
$ cat sample.ori
`I have a pet named ${ pet.txt }.`
$ ori sample.ori/
I have a pet named Fluffy.
You can prefix a template literal with a function name immediately before the leading backtick. This invokes that function with the strings that make up the template’s boilerplate text and a set of values to be substituted in the output. This function signature is compatible with JavaScript tagged template functions. See indent
for an example of a builtin function that you can use in a tagged template.
Also see Templates for more about using templates to generate HTML and other text formats.
Grouping
You can group expressions with (
)
parentheses. Command shells generally interpret parentheses, so you will need to escape them with backslashes or quote the expression you want ori to evaluate.
Lambdas (unnamed functions)
An Origami expression can define a type of unnamed function called a lambda.
You can create the simplest form of a lambda function with an =
equals sign:
=expression
This expression will not be evaluated immediately, but only later when explicitly invoked.
For example, the map
built-in function can apply another function to a tree’s values and/or keys. To concisely define a function that will be evaluated in the context of each tree value, you can use a lambda:
$ cat letters.json
{
"a": "The letter A",
"b": "The letter B",
"c": "The letter C"
}
$ cat uppercase.js
export default (x) => x.toString().toUpperCase();
$ ori "map(letters.json, =uppercase.js(_))"
a: THE LETTER A
b: THE LETTER B
c: THE LETTER C
The _
underscore above refers to the value being mapped, so =uppercase.js(_)
will convert the value to uppercase.
You can also define lambda functions with an expanded syntax using a “=>” (or the Unicode ⇒ Rightwards Double Arrow) that allows for multiple named parameters:
(parameter1, parameter2, parameter3, …) => expression
The map
function shown above passes the mapping function the value and key being mapped — in that order — as arguments, so the above example can be rewritten:
$ ori "map(letters.json, (description, letter) => uppercase.js(description))"
a: THE LETTER A
b: THE LETTER B
c: THE LETTER C
In this case, since the letter
argument isn’t used, it can be omitted:
$ ori "map(letters.json, (description) => uppercase.js(description))"
a: THE LETTER A
b: THE LETTER B
c: THE LETTER C
Pipe operator
Origami has a pipe operator →
(which can also be written ->
) for representing a sequence of function calls. This can be used to avoid deep call nesting.
These deeply-nested function calls:
three(two(one(value)))
can be rewritten using the pipe operator:
value → one → two → three
This can be useful when applying multiple transformations of data. Suppose an index page is generated from markdown and then placed inside a template:
{
index.html = template.ori(mdHtml(index.md))
}
You can rewrite the above using the pipe operator so that the flow of data reads proceeds from left to right:
{
index.html = index.md → mdHtml → template.ori
}
This may make the flow of data easier to see.
Comments
Line comments start with //
double slashes and extend to the end of the line:
// This is a line comment
Note: In a URL or file path (see above), Origami interprets consecutive double slashes as part of the path and not a comment.
https://example.com/path/with/consecutive//slashes
Block comments are enclosed by /* */
/*
Block comment
*/
Instantiating classes
For interoperability with JavaScript classes, Origami supports a new:
syntax for creating new class instances.
If the JavaScript file User.js
exports a User
class, then
new:User.js("David")
is equivalent to the JavaScript new User("David")
.