tree:

map(tree, options)

Creates a new tree from another by applying mapping functions to the original tree’s values and/or keys.

Mapping values

In the simplest form of map, you provide a tree and a function that will be applied to the tree’s values. The resulting tree will have the same keys as the original:

$ cat greetings.yaml
Alice: Hello, Alice.
Bob: Hello, Bob.
Carol: Hello, Carol.
$ cat uppercase.js
export default (x) => x.toString().toUpperCase();
$ ori map greetings.yaml, uppercase.js
Alice: HELLO, ALICE.
Bob: HELLO, BOB.
Carol: HELLO, CAROL.
g Alice Hello, Alice. ->Alice Alice Bob Hello, Bob. ->Bob Bob Carol Hello, Carol. ->Carol Carol
g Alice HELLO, ALICE. ->Alice Alice Bob HELLO, BOB. ->Bob Bob Carol HELLO, CAROL. ->Carol Carol
Original tree
Mapped values

Unlike a JavaScript Array map, the map function does not do any mapping work upon invocation — it only does the work when someone requests the mapped tree’s keys or values.

The mapping function is typically a JavaScript function, an Origami lambda (unnamed function), or a template such as an Origami template. You can also use a second tree as a map.

map options

In the basic form of map shown above, the second parameter is some kind of mapping function that will be applied to the tree’s values. You can also use an expanded form of map in which the second parameter is a collection of options:

$ ori map greetings.yaml, { value: uppercase.js }

The options include:

  • deep: If false (the default), this only maps the top-level values in the tree. If true, this maps values at all levels of the tree.
  • extensions: See “Transforming extensions”.
  • inverseKey: A function that will be applied to a result key to get back the original key. See “Inverse keys”.
  • key: A function that will be applied to a key from the original tree to get a result key. See “Mapping keys”.
  • value: A function that will be applied to the original tree’s values.

The value, key, and tree being mapped

When map calls a function to map a key or value, it passes the function three arguments:

  1. The value being mapped
  2. The key being mapped
  3. The original tree being mapped

The function being called does not need to use all three arguments. The following example only needs the value, so it only defines a parameter for the first argument:

$ ori "map(greetings.yaml, (greeting) => uppercase.js(greeting))"
Alice: HELLO, ALICE.
Bob: HELLO, BOB.
Carol: HELLO, CAROL.

Mapping keys

This function wants to reference the key being mapped (a person’s name). That will be passed as the second argument, so the function defines two parameters:

$ ori "map(greetings.yaml, (greeting, name) => uppercase.js(name))"
Alice: ALICE
Bob: BOB
Carol: CAROL

A common case for mapping keys is turning some data into a file name. For example, if a tree has keys which are names like “Alice”, you can map those keys to ones that end in .html, like Alice.html.

$ cat greetings.yaml
Alice: Hello, Alice.
Bob: Hello, Bob.
Carol: Hello, Carol.
$ ori "map(greetings.yaml, { key: (greeting, name) => `${ name }.html` })"
Alice.html: Hello, Alice.
Bob.html: Hello, Bob.
Carol.html: Hello, Carol.
g Alice Hello, Alice. ->Alice Alice Bob Hello, Bob. ->Bob Bob Carol Hello, Carol. ->Carol Carol
g Alice.html Hello, Alice. ->Alice.html Alice.html Bob.html Hello, Bob. ->Bob.html Bob.html Carol.html Hello, Carol. ->Carol.html Carol.html
Original tree
Mapped keys

Inverse keys

Generally speaking, a map operation needs to be able to change an original key like “Alice” into a result key like Alice.html and it must be able to go in the other direction. If someone asks the above mapped tree for Bob.html, the map operation will need to change Bob.html into the key “Bob” in order to be able to retrieve Bob’s data.

To do this, you can provide an inverseKey function that turns a result key into an original key. As a convenience, if you do not provide your own inverseKey function, map will provide a default inverseKey function for you.

This default inverseKey function is not especially efficient, as it exhaustively maps each original key to see if the result matches what is being requested. In this example, if someone asks for Bob.html, the default inverseKey function will try mapping the original key “Alice” to Alice.html to see if that matches. It doesn’t, so the function next tries mapping “Bob” to Bob.html. That does match, so it knows that the inverse of Bob.html is “Bob”.

For small maps, this default inverseKey function may be perfectly acceptable. For the best performance in mapping larger collections, define both the key and inverseKey functions.

Transforming extensions

Changing a key’s extension is very common. Mapping values often changes the type of the data, and it is useful to be able to reflect that change in type in file extensions.

To facilitate changing extensions in a map, you can supply an extension option that indicates whether and how the extensions of the original tree should be changed:

  • extension: ".md" restricts the map to only apply to keys ending in .md
  • extension: "->.html" adds the .html extension to the keys in the result
  • extension: ".md->.html" only applies the map to keys ending in .md, and adds the .html extension to keys in the result

The extension option generates a key and inverseKey functions for you. If you provide two extensions, like ".md->.html", the inverseKey function will be much more efficient.

So you can also write the above example as:

$ cat greetings.yaml
Alice: Hello, Alice.
Bob: Hello, Bob.
Carol: Hello, Carol.
$ ori "map(greetings.yaml, { extension: '→.html' })"
Alice.html: Hello, Alice.
Bob.html: Hello, Bob.
Carol.html: Hello, Carol.

When mapping file extensions as shown above, trailing slashes are generally ignored. However, you can supply a trailing slash as an extension if you want to explicitly map, for example, folder keys that end in trailing slashes. For example, "/→.json" would map a folder key like “data/” to “data.json”.