If you’re familiar with JavaScript, Origami is essentially JavaScript expressions plus paths. It also includes minor adaptations that make it easier to define sites with expressions.
Origami language features are generally optional, but it does enforce one stylistic rule: you have to put spaces around operators like math operators.
Quick reference #
Origami | Equivalent JavaScript and notes |
---|---|
src/data.json |
await fs.readFile("src/data.json")
A file name or path in source code reads that file. |
3 / 2 |
3 / 2 or 3/2
Spaces are required around math operators. |
<My File.txt> |
await fs.readFile("My File.txt")
Use angle brackets if a path contains spaces, parentheses, quotes, etc. |
(data.json).name |
JSON.parse(await fs.readFile("data.json")).name
Reading data out of a file implicitly parses it. |
https://example.com |
await fetch("https://example.com)
A URL can fetch network resources. |
"hello" |
export default "hello";
An Origami file implicitly exports the value of its top-level expression. |
fn(a, b) |
await fn(await a, await b)
All functions and values are potentially async. |
|
An object’s properties can reference its other properties. |
|
Define a getter with an equals sign. |
|
A property name in parentheses makes it non-enumerable. |
|
Newlines can be used as separators in arrays, objects, and parameters. |
() => { a: 1 } |
() => ({ a: 1 })
A function can directly return an object without surrounding parentheses. |
|
Object keys can be file names without having to be quoted. |
a → b → c |
c(b(a))
The pipe operator lets left-to-right order indicate the order of evaluation. |
When evaluating Origami expressions on the command line, some additional shell shorthand features are also available.
Identifiers #
As in JavaScript, an Origami identifier is a sequence of characters that can reference a local variable, global variable, function parameter. Additionally, in Origami an identifier can also refer to a local file or folder.
All valid JavaScript identifiers like name
or postal_code
are valid Origami identifiers — but to allow identifiers to more easily refer to files, Origami identifiers are more permissive than JavaScript’s.
- Any character allowed in a JavaScript identifier, including characters like digits that are only allowed after the start, are allowed at any position in an Origami identifier. (Specifically, any character can be drawn from the the Unicode ID_Continue class.)
- Origami identifiers can also include these anywhere:
.
@
~
- Origami identifiers can include these after the first position:
!
%
&
*
+
-
^
|
Examples of identifiers which are not valid in JavaScript but which are valid in Origami:
index.md
404.html
my-pictures
An identifier can appear on its own or as part of a path.
A standalone sequence of digits and no other characters like 123
is treated as a number; if you need to reference a file or folder whose name is only digits, use angle brackets: <123>
. However, a sequence of digits in a path is always treated as an identifier: the path posts/2026
ends with the string identifier “2026”, not the number 2026.
Globals #
Origami expressions can reference the full set of standard JavaScript built-in objects. Origami also adds its own globals:
File name heuristic #
The above definition of identifiers lets you directly include file names in Origami expressions:
Origami.mdHtml(ReadMe.md)
This expression passes the ReadMe.md
file to Origami’s builtin mdHtml
function.
The period in Origami.mdHtml
above is standard JavaScript syntax for property access, but the period in ReadMe.md
is just a character in a file name. Origami distinguishes these cases using a heuristic.
To determine how to interpret a period in a sequence like a.b.c
:
- If there’s a local variable whose entire name is
a.b.c
, Origami treats the complete name as a reference to that local variable. - Next Origami looks at the part before the first period:
a
. If that is the name of a local variable, Origami treats thea
as a reference to that local variable, and the.b
and.c
as property access. - Origami then considers whether
a
is the name of a global. If so, thea
references that global variable and the.b
and.c
are property access. - Otherwise Origami treats the complete name
a.b.c
as a reference to a local file or folder that will be located using Origami scope.
In the above example of Origami.mdHtml(ReadMe.md)
:
Origami
is a global, soOrigami.mdHtml
gets themdHtml
property of the globalOrigami
object. The value of that is a builtin function.- There is no global variable called
ReadMe
, and assuming there is no local variable with that name, Origami treatsReadMe.md
as the name of a local file or folder.
Origami allows the JavaScript math symbols +
, -
, *
, and ~
to appear in a name: e.g., package-lock.json
. To invoke a binary operator, add spaces around it; see Operators.
Name conflicts #
One reason the above heuristic generally works is that most JavaScript globals start with or contain uppercase letters, while development projects often use lowercase names for folder and file names. This reduces the chances for name collisions.
That said, the following lowercase JavaScript globals or keywords are available in Origami:
async
await
atob
bota
console
crypto
escape
eval
false
fetch
import
navigator
null
performance
process
true
typeof
undefined
unescape
void
If your project happens to have a folder or file that is one of these names, or has an initial section before the first .
that is one of these names, you can surround the path with angle brackets. Example: performance.yaml
starts with performance
; to avoid conflicting with the performance
global, use angle brackets: <performance.yaml>
.
Another potential source of name conflicts are file names that begin with the same name as a local:
{
index.html = index.ori(posts.ori)
posts/ = Tree.map(markdown/, postPage.ori)
}
Here the posts.ori
reference won’t find the expected local file. Instead, it will match the local posts/
variable; see local property references below. Origami will then search that result for a non-existent ori
property. To fix this, rename posts.ori
or surround it with angle brackets: <posts.ori>
.
Note that the following does not produce a conflict:
{
posts/ = Tree.map(markdown/, posts.ori)
}
When defining an object key like posts/
here, the expression that defines the value will never match the key being defined. (Otherwise this would create an infinite loop.) So posts.ori
in this case can’t match the posts/
local variable, and will find the file as expected.
Paths #
A sequence of identifiers separated with /
slash characters is treated as a path.
package.json/name
In a path, the first part of the path (here, package.json
) will always be treated as a complete identifier. If that name matches a local variable, then the path will be evaluated using that variable as the start. Otherwise Origami assumes the path starts with the name of a local file or folder.
- A path beginning with
~/
will be taken as a reference to the user’s home directory. - If you want to reference an absolute path that starts with a slash, use angle brackets. This avoids conflict with JavaScript’s regular expression syntax:
/etc/
is a regular expression, not a path.
Angle brackets #
You can explicitly define a path or URL by enclosing it in < >
angle brackets:
<src/data.json>
The first part of the path (here, src
) will be resolved using Origami scope; the rest of the path keys will be used to traverse the value to retrieve a final result.
Angle brackets are also useful if your path includes spaces or other characters that aren’t valid in Origami identifiers:
<markdown/My File with Spaces (and Parens).md>
Inside the angle brackets you can use any character except a tab, newline, or >
. If you need to include a >
in a path, escape it with a \
backslash.
Although Origami can always recognize URLs that start with a protocol, you can also put a URL in angle brackets:
<https://example.com>
Trailing slashes #
Paths in Origami follow a trailing slash convention:
- If a trailing slash is present, then the value is definitely a traversable subtree.
- If a trailing slash is not present, the value may or may not be a subtree. That is, a tree isn’t obligated to append slashes to any or all of its keys for traversable subtrees.
In practice, when writing a path for a folder, you have the option to end the path with a trailing slash. If your project includes a folder called “markdown”, both of these paths will find the folder:
markdown
markdown/
You may find the latter form a helpful as a reminder that “markdown/” references a folder.
A trailing slash on a path to a file containing data unpacks the file content into data; see below.
Result of a path #
A path to a file returns the raw contents of the indicated file.
- For a file name or path, this will be a Uint8Array.
- For a URL, this will be an ArrayBuffer.
Origami has built-in handlers that can parse the contents of common file types such as JSON and markdown with front matter; see that page for details. This allows you to, for example, obtain your project’s version number from its package.json
file via:
(package.json).version
Or you can download a file and extract data from it:
(https://example.com/data.json).users[0].name
You can also use angle brackets for this:
<https://example.com/data.json>.users[0].name
If a file path ends in a trailing slash, Origami will unpack the file content into data. For example, if posts/post1.md
is a markdown file, the expression:
posts/post1.md
returns the file content as a Uint8Array
. If you want to work with that as text, you can pass that to Origami.string
.
If, however, you want to work with that as a document object, you can append a trailing slash:
posts/post1.md/
This returns the content of post1.md
as an object, including any front matter data as properties.
Note that most Origami builtin functions that expect a treelike argument will automatically unpack a file or resource (such as Uint8Array
or ArrayBuffer
) value as long as the path includes a known file extension.
URLs #
If a sequence starts with a scheme (protocol) and a colon, Origami treats it as a URL:
https://example.com
The only characters that cannot be used in a URL are whitespace or characters that end an expression: ,
)
, ]
, }
. If you need to incorporate such a character into a URL, escape it with a \
backslash. Alternatively, place the URL in angle brackets.
Numbers #
Origami supports integers and floating point JavaScript Number values.
Origami does not yet support binary, octal, hexadecimal numeric literals, nor does it support exponential notation. If necessary you can create such numbers with the JavaScript Number()
function:
Number('0xf') // 15
Number('1e2') // 100
Strings #
Strings with double quotes and single quotes are essentially the same as in JavaScript:
'string'
"string"
You can escape characters with a \
backslash:
'It\'s great'
Templates #
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.
Expressions inside an Origami template literal can directly return complex values like arrays, objects, or trees. Origami will perform a depth-first traversal of the result, await any Promise
values, and concatenate the final values into the string result:
`Hello, ${ { name: 'Alice' } }.` // "Hello, Alice."
This is different than JavaScript, where the above produces: "Hello, [object Object]."
Among other things, this means that a template can inline the text content a file by name:
$ cat pet.txt
Jiji
$ cat bio.ori
`I have a cat named ${ pet.txt }.`
$ ori bio.ori/
I have a cat named Jiji.
See Templates for more about using templates to generate HTML and other text formats.
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 — the same function signature as a JavaScript tagged template function. See Tree.indent
for an example of a builtin function that you can use in a tagged template.
Object literals #
Origami’s basic syntax for constructing object literals is essentially the same as JavaScript’s: define an object literal with {
and }
curly braces surrounding key/value pairs:
{
name: "Alice"
location: "Honolulu"
}
You can separate key/value pairs with commas, newlines, or both.
Any valid Origami identifier can be used as an object key without being quoted. This means you can define a virtual file with a name like index.html
:
{
index.html: "Welcome to my site!"
}
To define a key that’s not a valid identifier, such as one that includes spaces, put the key in single or double quotes:
{
"Test File.txt": "Sample text"
}
Shorthand properties #
As a shorthand, you can define a property with just an identifier:
{
// Include the project's README.md file in this object
README.md
}
This is shorthand for defining that key with a value that looks up that same key in the current scope:
{
// Include the project's README.md file in this object
README.md: README.md
}
This extends to defining a property with a path. The last part of the path becomes the key, so
{
path/to/file.txt
}
is the same as
{
file.txt: path/to/file.txt
}
You can also achieve the same result with angle brackets:
{
<path/to/file.txt>
}
Property getters #
The value in a key/value pair will be evaluated once, when the object is loaded. The following object, for example, evaluates createPage.js()
only once:
{
index.html: createPage.js()
}
Origami lets you define property getters that will be evaluated each time the property is requested, using =
equals signs instead of :
colons:
{
index.html = createPage.js()
}
Each time this object is asked for the value of index.html
, Origami will invoke createPage.js()
and return that result. The above is roughly equivalent to the following JavaScript syntax:
/* JavaScript approximation of the above */
{
get ["index.html"]() { return createPage(); }
}
Property access #
There are several ways to access the property of an object.
Suppose alice.ori
contains:
// alice.ori
{
name: "Alice"
location: "Honolulu"
}
You can reference the “name” property of this file in several ways:
(alice.ori).name
— Put the.name
reference after a closing parenthesisalice.ori .name
— Put the.name
reference after whitespace (this might look a little strange, but is standard JavaScript syntax)alice.ori["name"]
— Use the string"name"
in square bracketsalice.ori/name
— Origami treats any treelike object as a tree that can be traversed with path syntaxalice.ori("name")
— Origami allows any tree to be called like a function.
Local property access #
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 property access is not possible in JavaScript without introducing a separate variable declaration:
// JavaScript equivalent
const a = 1;
const obj = {
a,
b: a,
};
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 }
resolves to the inherited name
defined in the parent object.
$ ori inherited.ori
name: Alice
user:
name: My name is Alice
Non-enumerable properties #
Object keys in parentheses like (x)
are not enumerable: they will not be included in a list of the object’s keys. Such properties will generally not be included when the object is copied or displayed.
One use for non-enumerable properties is in calculating a value that will be used in more than one place.
// hidden.ori
{
(company): "Yoyodyne"
index.html: `<h1>${ company }</h1>`
about: {
index.html: `<h1>About ${ company }</h1>`
}
}
Here, the declaration of the company
property is in parentheses, so it is non-enumerable. It can be referenced by other object properties, but will not be included in the object’s keys. This means it won’t be shown when the object is displayed:
$ ori hidden.ori/
index.html: <h1>Yoyodyne</h1>
about:
index.html: <h1>About Yoyodyne</h1>
Marking a property as non-enumerable only affects whether it is included in the object’s list of keys; a non-enumerable property is still accessible if one knows the property name:
$ ori hidden.ori/company
Yoyodyne
Keys with trailing slashes #
As noted above, paths can end in trailing slashes to follow Origami’s trailing slash convention.
When defining an object, you have the option of adding a trailing slash to a key whose value is a traversable subtree. This can be a useful way of reminding readers of the code (including yourself later) that the value is a tree.
For example, you might use the Tree.map
builtin to convert a collection of markdown files to HTML:
// site.ori
{
pages/ = Tree.map(markdown/, Origami.mdHtml)
}
Here the trailing slash in pages/
serves as a reminder that the property’s value is a traversable tree of data. If someone asks for this object’s keys, the key for pages/
will include this trailing slash signal:
$ ori keys site.ori
- pages/
Object nesting #
As in JavaScript, object literals can be nested. A common use for this in Origami is to define the overall structure of a website in a .ori
file:
// subtree.ori
{
index.html: `<h1>Home</h1>`
about: {
index.html: `<h1>About Us</h1>`
}
}
When this file is served as a site, the user will be able to browse to about/index.html
.
When an object created with an object literal is treated as a tree, any key whose value is another object literal implicitly ends in a trailing slash:
$ ori Tree.keys subtree.ori
- index.html
- about/
Here the builtin Tree.keys
function shows a trailing slash on about/
even though a trailing slash isn’t explicitly included in the subtree.ori
source code. However, if the object is unpacked and passed to the standard JavaScript Object.keys
method, the keys will be returned directly as defined:
$ ori Object.keys subtree.ori/
- index.html
- about
Array literals #
Origami arrays are essentially the same as JavaScript arrays, and are defined with [
]
square brackets:
[1, 2, 3]
As with object literals, you can separate array items with commas, newlines, or both:
[
1
2
3
]
Function calls #
Function calls in Origami look like those in JavaScript:
Math.max(1, 2)
You can call a function defined in a JavaScript file by referencing its file name and then invoking it with parentheses:
greet.js("Alice")
or with an explicit path in angle brackets:
<greet.js>("Alice")
Origami assumes that any function might be asynchronous, so implicitly uses await
when calling all functions. For compatibility with JavaScript, Origami allows use of the async
and await
keywords, but these have no effect on the behavior of the code — they are always implied.
The Origami language runtime itself is written in JavaScript, so types such as numbers, strings, and objects are the same as in JavaScript. E.g., the above example passes an Origami string to a JavaScript function; inside that function, the supplied argument will be a regular JavaScript string.
Origami does not yet support ...
spreads in function calls.
Trees as functions #
Origami’s AsyncTree interface lets you treat a wide variety of treelike structures as hierarchical trees. You can use function syntax to obtain a value from the tree; this invokes the tree’s get
method.
This means you can use function call syntax to retrieve a value from a data structure, a folder, etc. If you have a data file:
# capitals.yaml
Japan: Tokyo
Turkey: Ankara
Australia: Canberra
Spain: Madrid
you can extract a value from it with function syntax:
$ ori "capitals.yaml('Spain')"
Madrid
One use for this is to programmatically look up values: if a variable holds the name of a key, you can call the tree as a function and pass the variable as the argument.
Functions as trees #
Conversely, Origami lets you treat a function as a treelike object. This is true for arrow functions, Origami builtin functions, and functions exported by JavaScript modules.
This means you can traverse a function using path syntax, which can be convenient when using the Origami CLI to evaluate expressions in the shell. A shell will typically try to handle parentheses, and so disallow a function call with unquoted parentheses. But a shell will generally leave slashes untouched, allowing you to invoke a function and pass an argument.
// uppercase.js
export default (x) => x.toString().toUpperCase();
$ ori uppercase.js/hello
HELLO
Arrow functions #
An Origami expression can define a type of unnamed function called an arrow function.
You can define arrow functions using =>
and any number of parameters:
(parameter1, parameter2, parameter3, …) => expression
That expression
will not be evaluated immediately, only later when the function is invoked.
For example, the Tree.map
builtin function can apply another function to a tree’s values and/or keys. To define a function that will be evaluated in the context of each tree value, you can use an arrow function:
$ ori "Tree.map(['a', 'b', 'c'], (letter) => letter.toUpperCase())"
- A
- B
- C
Origami arrow function syntax does not yet support JavaScript’s syntax for default parameters, rest parameters, or parameter destructuring.
Operators #
Origami closely follows JavaScript operator precedence. Origami introduces some operators of its own, listed here alongside the standard JavaScript operators in order from highest to lowest precedence.
Operator | Example | Associativity |
---|---|---|
Group | (x) |
n/a |
Path | x/y/z |
left-to-right |
Member access | x.y |
left-to-right |
Computed member access | x[y] |
n/a |
Function call | x(y) |
n/a |
import | import(x) |
n/a |
Tagged template | x`y` |
n/a |
Logical NOT | !x |
n/a |
Bitwise NOT | ~x |
n/a |
Unary plus | +x |
n/a |
Unary minus | -x |
n/a |
typeof | typeof x |
n/a |
void | void x |
n/a |
await | await x |
n/a |
Exponentiation | x ** y |
right-to-left |
Multiplication | x * y |
left-to-right |
Division | x / y |
left-to-right |
Remainder | x % y |
left-to-right |
Addition | x + y |
left-to-right |
Subtraction | x - y |
left-to-right |
Left shift | x << y |
left-to-right |
Right shift | x >> y |
left-to-right |
Unsigned right shift | x >>> y |
left-to-right |
Less than | x < y |
left-to-right |
Less than or equal | x <= y |
left-to-right |
Greater than | x > y |
left-to-right |
Greater than or equal | x >= y |
left-to-right |
Equality | x == y |
left-to-right |
Inequality | x != y |
left-to-right |
Strict equality | x === y |
left-to-right |
Strict inequality | x !== y |
left-to-right |
Logical AND | x && y |
left-to-right |
Logical OR | x || y |
left-to-right |
Nullish coalescing | x ?? y |
left-to-right |
Conditional (ternary) | x ? y : z |
right-to-left |
Arrow | (x) => y |
right-to-left |
Implicit parentheses | x y |
right-to-left |
Shorthand function | =x |
right-to-left |
Pipe | x -> y |
left-to-right |
Spread | ...x |
n/a |
Comma | x, y |
left-to-right |
An operator’s precedence determines how the Origami parser handles expressions that have more than one possible interpretation.
Example: a -> b => c
could be interpreted as a -> (b => c)
or (a -> b) => c
. Origami uses the former interpretation, because the arrow operator has a higher precedence than Origami’s pipe operator.
Origami requires spaces around binary operators:
x + y
x - y
x * y
x / y
Without the spaces, Origami interprets x/y
as a path. Similarly, operators without surrounding spaces are treated as part of an identifier: package-lock.json
is an identifier, not a subtraction.
Spaces are not required around unary operators: -foo
negates the value of foo
. If you need to reference a local file that starts with a hyphen, put the name in angle brackets: <-foo>
.
Some addition usage notes:
- Instead of calling
import
, you can generally use a path instead. - The
await
operator is implied in Origami so can always be omitted.
Origami does not yet support JavaScript’s optional chaining operator: x?.y
.
Spread operator #
You can use ...
three periods 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
As in JavaScript, the last definition of a given key will be used. While both files above define a value for c
, it’s the second file whose value of c
is used.
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 spread operator performs a shallow merge. You can also perform a shallow merge with the built-in Tree.merge
function. For a deep merge, see Tree.deepMerge
.
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(Origami.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 → Origami.mdHtml → template.ori
}
This may make the flow of data easier to see.
The pipe operator passes the value from the left side of the operator to the function on the right side. If you need to provide additional arguments to that function, wrap the call in a arrow function that accepts a single argument and then calls your desired function with the additional arguments.
Example: these nested calls to Origami.mdHtml
and Tree.sort
both take multiple arguments:
Tree.sort(Tree.map(markdown/, Origami.mdHtml), { compare: Origami.naturalOrder })
This can be rewritten with the pipe operator and arrow functions:
markdown/
→ (mdFiles) => Tree.map(mdFiles, Origami.mdHtml)
→ (htmlFiles) => Tree.sort(htmlFiles, { compare: Origami.naturalOrder })
Grouping #
You can group expressions with (
)
parentheses.
Comments #
Just like JavaScript, line comments start with //
double slashes and extend to the end of the line:
// This is a line comment
One difference is that, in a URL or path, Origami interprets consecutive double slashes as part of the path, not a comment.
https://example.com/
path/with/consecutive//slashes
Those lines contain double slashes, but neither contains a comment.
Block comments are enclosed by /* */
/*
Block comment
*/
Implied exports #
Evaluating any Origami file with the .ori
extension returns an evaluation of the file’s top-level expression — unlike JavaScript, there is no need to explicitly export
a value.
This message.ori
file evaluates to a string:
// message.ori
"This file exports this message."
It is equivalent to this JavaScript:
// message.js
export default "This file exports this message.";
Shell shorthand syntax #
To accommodate the use of Origami on the command line, the Origami CLI supports some additional shorthand syntax. The shorthands are not supported inside Origami .ori
files.
Unsupported JavaScript features #
As an expression language, Origami does not include any of JavaScript’s control structures like for
or while
loops. For the same reason, Origami doesn’t support JavaScript operators that have side effects:
- Postfix and prefix operators
++
,--
- Assignment operators
=
,+=
,-=
, etc. delete
operator
Additionally, Origami does not currently support these features from JavaScript:
- binary, octal, hexadecimal numeric literals, or exponential notation (see Numbers)
- default parameters, rest parameters, parameter destructuring (see Arrow functions)
...
spread operator in function calls?.
optional chaining operator (see Operators)- defining an object key with a computed property