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

joyce

v1.2.4

Published

An AST-based Expression Language

Downloads

65

Readme

Joyce

An AST-based Expression Language

Version Travis License

Joyce is a powerful expression language for dynamic data processing. It's particularly useful for processing configuration data.

Installation

Local Installation

npm i joyce

Global Installation

Use the global install if you want to use the joyce command-line tool.

npm i -g joyce

Usage

Joyce accepts any type of value. Expressions are represented inside strings as {{<expression>}}. Expressions are parsed and evaluated within the context of the originally given value.

A Basic Example

const Joyce = require('joyce');

Joyce({
    hello: 'world',
    isWorld: '{{hello == "world"}}'
});

// {
//     hello: 'world',
//     isWorld: true
// }

More Examples

Refer to unit tests for many many more examples.

const Joyce = require('joyce');

Joyce({
    foo: [ 1, 2, 3, 4 ],
    bar: '{{filter >= foo 3}}',
    baz: '{{join "-" foo}}',
    qux: '{{foo}}, a "note" to follow {{"foo"}}'
});

// {
//     foo: [ 1, 2, 3, 4 ],
//     bar: [ 3, 4 ],
//     baz: '1-2-3-4',
//     qux: 'bar, a note to follow foo'
// }

Command-Line Usage

echo '{"foo":"bar","bar":"{{== foo \"bar\"}}"}' | joyce

# {
#     "foo": "bar",
#     "bar": true
# }

Expressions

All expressions are strings and take the following form.

{{expression}}

Expression can be used at any position within a string.

plain text {{expression}} plain text

Multiple expressions can be used in a single string.

plain text {{expression}} plain text {{expression}} plain text

Nesting expressions is supported, but not arbitrarily. If you which to nest expressions you must use the eval operation (see examples below). Expressions can only be nested one level deep.

{{expression eval{"{{expression}}"}}}

For expressions containing a single operator argument, the operator may be in any position. This allows you to choose your own style depending on whether you prefer polish notation or infix notation.

{{operator ...operands}}
{{operand operator operand}}

For expressions containing an operation which acts upon an operator, the operation name must precede the operator.

{{operation operator ...operands}}

Operation names are case insensitive.

{{OPERATION operator ...operands}}

API

==

Loose equality

Joyce({
    foo: 123,
    bar: '{{foo == "123"}}',
    baz: '{{== foo "123"}}'
});

// {
//   foo: 123,
//   bar: true,
//   baz: true
// }

===

Strict equality

Joyce({
    foo: 123,
    bar: '{{foo === "123"}}',
    baz: '{{=== foo "123"}}'
});

// {
//   foo: 123,
//   bar: false,
//   baz: false
// }

!=

Loose inequality

Joyce({
    foo: 123,
    bar: '{{foo != "123"}}',
    baz: '{{!= foo "123"}}'
});

// {
//   foo: 123,
//   bar: false,
//   baz: false
// }

!==

Strict inequality

Joyce({
    foo: 123,
    bar: '{{foo !== "123"}}',
    baz: '{{!== foo "123"}}'
});

// {
//   foo: 123,
//   bar: true,
//   baz: true
// }

>

Greater than

Joyce({
    foo: 2,
    bar: '{{foo > 2}}',
    baz: '{{> foo 2}}'
});

// {
//   foo: 2,
//   bar: false,
//   baz: false
// }

>=

Greater than or equal to

Joyce({
    foo: 2,
    bar: '{{foo >= 2}}',
    baz: '{{>= foo 2}}'
});

// {
//   foo: 2,
//   bar: true,
//   baz: true
// }

<

Less than

Joyce({
    foo: 2,
    bar: '{{foo < 2}}',
    baz: '{{< foo 2}}'
});

// {
//   foo: 2,
//   bar: false,
//   baz: false
// }

<=

Less than or equal to

Joyce({
    foo: 2,
    bar: '{{foo <= 2}}',
    baz: '{{<= foo 2}}'
});

// {
//   foo: 2,
//   bar: true,
//   baz: true
// }

%

Modulus

Joyce({
    foo: 101,
    bar: '{{% foo 5}}',
    baz: '{{foo % 5}}'
});

// {
//   foo: 100,
//   bar: 1,
//   baz: 1
// }

+

Add

Joyce({
    foo: 1,
    bar: '{{+ foo 1}}',
    baz: '{{foo + 1}}'
});

// {
//   foo: 1,
//   bar: 2,
//   baz: 2
// }

Concat

Joyce({
    foo: 'a',
    bar: '{{foo + "b"}}',
    baz: '{{+ foo "b"}}'
});

// {
//   foo: 'a',
//   bar: 'ab',
//   baz: 'ab'
// }

-

Subtract

Joyce({
    foo: 1,
    bar: '{{foo - 1}}',
    baz: '{{- foo 1}}'
});

// {
//   foo: 1,
//   bar: 0,
//   baz: 0
// }

*

Multiply

Joyce({
    foo: 100,
    bar: '{{foo * 5}}',
    baz: '{{* foo 5}}'
});

// {
//   foo: 100,
//   bar: 500,
//   baz: 500
// }

/

Divide

Joyce({
    foo: 100,
    bar: '{{foo / 5}}',
    baz: '{{/ foo 5}}'
});

// {
//   foo: 100,
//   bar: 20,
//   baz: 20
// }

Operators

filter

Filter an array.

Joyce({
    foo: [ 1, 2, 3, 4, 5 ],
    bar: '{{filter % foo 2}}'
});

// {
//   foo: [ 1, 2, 3, 4, 5 ],
//   bar: [ 1, 3, 5 ]
// }
Joyce({
    foo: [ 1, 2, 3, 4 ],
    bar: '{{filter <= foo 2}}'
});

// {
//   foo: [ 1, 2, 3, 4 ],
//   bar: [ 1, 2 ]
// }

join

Join an array

Joyce({
    foo: [ "a", "b", "c" ],
    bar: '{{join "-" foo}}'
});

// {
//   foo: [ "a", "b", "c" ],
//   bar: "a-b-c"
// }

When only one arg is given, array elements will be joined by an empty string.

Joyce({
    foo: [ "a", "b", "c" ],
    bar: '{{join foo}}'
});

// {
//   foo: [ "a", "b", "c" ],
//   bar: "abc"
// }

every

Returns true if every element in the given array match the given predicate, otherwise returns false.

Call signature is {{every operator array comparison-value}}.

Joyce({
    foo: [ 1, 2, 3, 4, 5 ],
    bar: '{{every < foo 6}}'
});

// {
//   foo: [ 1, 2, 3, 4, 5 ],
//   bar: true
// }
Joyce({
    foo: [ 1, 2, 3, 4, 5 ],
    bar: '{{every > foo 4}}'
});

// {
//   foo: [ 1, 2, 3, 4, 5 ],
//   bar: false
// }

some

Returns true if some of the elements in the given array match the given predicate, otherwise returns false.

Call signature is {{some operator array comparison-value}}.

Joyce({
    foo: [ 1, 2, 3, 4, 5 ],
    bar: '{{some > foo 4}}'
});

// {
//   foo: [ 1, 2, 3, 4, 5 ],
//   bar: true
// }

find

Returns first element in a given array which matches a given predicate.

Call signature is {{find operator array comparison-value}}.

Joyce({
    foo: [ 1, 2, 3, 4, 5 ],
    bar: '{{find > foo 3}}'
});

// {
//   foo: [ 1, 2, 3, 4, 5 ],
//   bar: 4
// }

map

Performs a binary operation on each element in a given array and return a new array.

Call signature is {{map operator array subject-value}}.

Joyce({
    foo: [ "file1", "file2", "file3" ],
    bar: '{{map + foo ".png"}}'
});

// {
//   foo: [ "file1", "file2", "file3" ],
//   bar: [ "file1.png", "file2.png", "file3.png" ]
// }

sum

(aliased as concat)

Adds or concats the elements of a given array.

Joyce({
    foo: [ 1, 2, 3, 4, 5 ],
    bar: '{{sum foo}}'
});

// {
//   foo: [ 1, 2, 3, 4, 5 ],
//   bar: 15
// }
Joyce({
    foo: [ "a", "b", "c" ],
    bar: '{{sum foo}}'
});

// {
//   foo: [ "a", "b", "c" ],
//   bar: "abc"
// }

concat

(alias for sum)

product

Multiply the elements of a given array.

Joyce({
    foo: [ 1, 2, 3, 4, 5 ],
    bar: '{{product foo}}'
});

// {
//   foo: [ 1, 2, 3, 4, 5 ],
//   bar: 96
// }

keys

Returns an array containing a given object's keys.

Joyce({
    foo: {
        baz: "bim",
        bam: "boom"
    },
    bar: '{{keys foo}}'
});

// {
//   foo: {
//     baz: "bim",
//     bam: "boom"
//   },
//   bar: [ "baz", "bam" ]
// }

values

Returns an array containing a given object's values.

Joyce({
    foo: {
        baz: "bim",
        bam: "boom"
    },
    bar: '{{values foo}}'
});

// {
//   foo: {
//     baz: "bim",
//     bam: "boom"
//   },
//   bar: [ "bim", "boom" ]
// }

ternary

Express simple conditions as a ternary.

Joyce({
    foo: true,
    bar: '{{foo ? "foo is true" : "foo is false"}}'
});

// {
//   foo: true,
//   bar: "foo is true"
// }

eval

Nest joyce expressions with eval.

Note: If the expression passed to eval contains spaces you must quote the expression.

Joyce({
    foo: true,
    bar: '{{foo ? eval{"foo is {{boom}}"} : eval{"foo is {{splat}}"}}}',
    boom: 'bam',
    splat: 'squash'
});

// {
//   foo: true,
//   bar: "foo is bam",
//   boom: 'bam',
//   splat: 'squash'
// }

Operators

For operations which accept operator arugments, the follower operators are defined.

===
==
>
>
<
<
>=
<=
%
+
+
-
*
/

Explict Type Casting

There are times when you will want to explicitly cast arguments as a certain type.

For instance, consider the following.

Joyce({
    "==": "foo",
    bar: '{{== == "foo"}}'
});

// {
//   foo: "==",
//   bar: false
// }

Bar should be true, but Joyce has cast the == as an operation instead of a reference.

This is easily fixed.

Joyce({
    "==": "foo",
    bar: '{{ref{"=="} == "foo"}}'
});

// {
//   foo: "==",
//   bar: true
// }

Note that we not only wrap the == argument in ref{} but we also quote it. Anything in which you would have to use array-notation in JavaScript when referencing a property must be quoted.

All argument types can be explictly cast if you find that the need arises.

The casting notation is always: type{value}

The following types are supported:

  • ref
  • str
  • num
  • obj
  • arr
  • bool

Deep Property Resolution

You can evaluate deeply nested values in Joyce by referencing a keypath.

Joyce({
    sis: {
        boom: {
            bah: [ 'foo', 'bar', 'baz' ]
        }
    },
    bar: '{{sis.boom.bah[1] == "bar"}}'
});

// {
//   sis: {
//     boom: {
//       bah: [ 'foo', 'bar', 'baz' ]
//     }
//   },
//   bar: true
// }

Why an AST-based EL?

Security matters.

Most ELs are open to security vulnerabilities because they depend on dynamic code evaluation. Like any EL, Joyce also depends on dynamic code evaluation, but what differentiates Joyce from most other ELs is that it does very little direct evaluation of user input. Almost all user input in parsed into tightly restricted AST forms. Joyce uses astring to emit code from the resulting AST and it is only these curated code strings which are evaluated.

This mediation via AST provides a level of control and sanitization that makes Joyce one of the safest ELs out there.

In the interest of full transparency, there is one code path in Joyce which will result in direct evaluation of unsanitized user input: deep property evaluation (i.e. {{foo.bar.baz}}). Measures have been taken to ensure that this code path is not exploitable. See unit tests for coverage of this possible attack vector.

Using Joyce with Config Files

You have options...

'use strict';
const globble = require('globble');
const Joyce = require('Joyce');

(async () => {
    const data = await globble('./config/**', { cwd: __dirname, clobber: true });
    console.log(Joyce(data));
})();

Or...

cat foo.json | joyce > result.json

Or, use rc or answers or any other option out there for gathering config data and then just pass it to Joyce.

License

MIT