npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

samovar

v0.2.1

Published

A pure-JSON template language and renderer. Also supports YAML.

Downloads

4

Readme

Samovar

npm Dependencies Build Status Coverage Status JavaScript Standard Style

A pure-JSON template language and renderer. Also supports YAML.

Installation

yarn add samovar

Usage

const render = require('samovar')

const template = require('./template.json')
const data = require('./data.json')

const rendered = render(template, data)

console.log('Rendered: ' + JSON.stringify(rendered, null, 2))

Syntax

Template Strings (${} expressions)

You can use ES2015 template string syntax in all keys and string-typed values. Here's an example:

Template:

{
  "foo": "bar",
  "user${user.id}": {
    "name": "${user.firstName} ${user.lastName}"
  }
}

Data:

{
  "user": {
    "id": 42,
    "firstName": "John",
    "lastName": "Doe"
  }
}

Output:

{
  "foo": "bar",
  "user42": {
    "name": "John Doe"
  }
}

Two Modes

Assignment Mode (the <- operator)

In assignment mode, the return value of a control structure is assigned to an object key. It basically does "put this one thing here".

It's best explained using an example. Let's say you have an array of user information as data, and you want to produce an array containing just their names. In JavaScript, you'd probably use the array's map() function. Samovar has a _map_ control structure which actually calls map() under the hood. It assigns the current array element to the _ variable, so you can use it in the return value. Once you have the desired array, you assign it to the userNames key using the <- operator.

Template:

{
  "userNames": {
    "<-": {
      "_map_": "users",
      "to": "${_.firstName} ${_.lastName}"
    }
  }
}

Data:

{
  "users": [
    {
      "id": 42,
      "firstName": "John",
      "lastName": "Doe"
    },
    {
      "id": 43,
      "firstName": "Jane",
      "lastName": "Doe"
    }
  ]
}

Output:

{
  "userNames": [
    "John Doe",
    "Jane Doe"
  ]
}

Extension Mode (the ++ operator)

Sometimes, you want to inline multiple values into a parent object. For example, you may want to merge multiple arrays, or assign multiple key-value pairs to an object defined in the template. That's where extension mode comes in.

Using the same array of users above, you could produce an object that maps a user's ID to their full name. This time, you'd _map_ each user to a single-key object, which maps their ID to their full name. In JavaScript, if you wanted to merge all those single-key objects into one, you'd use Object.assign() starting from an empty object, and that's precisely what the ++ operator does.

Template:

{
  "usersById": {
    "++": {
      "_map_": "users",
      "to": {
        "${_.id}": "${_.firstName} ${_.lastName}"
      }
    }
  }
}

Data:

{
  "users": [
    {
      "id": 42,
      "firstName": "John",
      "lastName": "Doe"
    },
    {
      "id": 43,
      "firstName": "Jane",
      "lastName": "Doe"
    }
  ]
}

Output:

{
  "42": "John Doe",
  "43": "Jane Doe"
}

Additionally, you don't have to start from an empty object. Any keys that exist alongside ++ will get merged into the result.

You can also use ++ with arrays. For example, if you have two arrays of users, you can easily _map_ and then concatenate them, as illustrated below. Note that with arrays, you do have to create an intermediate object that holds the ++ key, which is flattened into the array. With objects, as shown above, you can just include ++ into the destination object itself.

Template:

{
  "userNames": [
    {
      "++": {
        "_map_": "users",
        "to": "${_.firstName} ${_.lastName}"
      }
    },
    {
      "++": {
        "_map_": "superusers",
        "to": "${_.firstName} ${_.lastName}"
      }
    }
  ]
}

Data:

{
  "users": [
    {
      "id": 42,
      "firstName": "John",
      "lastName": "Doe"
    },
    {
      "id": 43,
      "firstName": "Jane",
      "lastName": "Doe"
    }
  ],
  "superusers": [
    {
      "id": 0,
      "firstName": "Flying",
      "lastName": "Spaghetti Monster"
    }
  ]
}

Output:

{
  "userNames": [
    "John Doe",
    "Jane Doe",
    "Flying Spaghetti Monster"
  ]
}

Control Structures

In the examples above, we always used a _map_ structure. There's more where that came from.

Map

A _map_ structure models Array#map(), mapping each element of an array to the projection expression defined by the as option. You can reference the current array element as _ and the current index (zero-based) as _index.

Options:

  • _map_: input array
  • to: projection expression
  • as: additional identifier for the current array element
  • index: additional identifier for the current index

You would mainly use as and index in nested structures, as each structure will override the parent's _ and index references.

Filter

A _filter_ structure models Array#filter(), only returning array elements for which the expression given by the where option returns a true value. You can reference the current array element as _ and the current index (zero-based) as _index.

Options:

  • _filter_: input array
  • where: filter expression
  • as: additional identifier for the current array element
  • index: additional identifier for the current index

You would mainly use as and index in nested structures, as each structure will override the parent's _ and _index references.

Repeat

If you just need to repeat an expression a fixed number of times without providing an array as input, you can use the _repeat_ structure. Think of it as shorthand for _map_ with an array from 0 to the number of iterations minus one. The _index variable references the current index.

Options:

  • _repeat_: number of iterations
  • body: projection expression
  • index: additional identifier for the current index

You would mainly use index in nested structures, as each structure will override the parent's _index reference.

If

While _map_, _filter_, and _repeat_ always produce an array, you can use _if_ to write conditional structures that result in a scalar value. If the expression passed to _if_ results in a true value, the structure will evaluate to the expression in the then option; otherwise, the else option applies.

Options:

  • _if_: conditional expression
  • then: result if condition is true
  • else: result if condition is false

Deep Dive

To gain a deeper understanding, the tests can also serve as examples. To run them, clone the repository and run DEBUG=1 yarn test. This will render a series of templates and dump each of them to the console.

Of course, you're also welcome to look at the code and maybe even submit a pull request for a bug fix or a cool new feature. Thanks!

Usage with YAML

While YAML isn't supported out of the box, templates can easily be mapped between JSON and YAML. As a proof of concept, the demo uses JS-YAML to read a YAML template, render it to JSON, and turn that back into YAML.

Author

Tim De Pauw

License

MIT