GithubHelp home page GithubHelp logo

mozilla / mozjexl Goto Github PK

View Code? Open in Web Editor NEW

This project forked from technologyadvice/jexl

35.0 6.0 7.0 188 KB

Javascript Expression Language: Powerful context-based expression parser and evaluator

License: Other

JavaScript 100.00%
product-delivery mozilla-normandy

mozjexl's Introduction

Mozjexl CircleCI

Javascript Expression Language: Powerful context-based expression parser and evaluator Mozjexl is a fork of Jexl for use at Mozilla, specifically as a part of SHIELD and Normandy.

Quick start

Use it with promises or callbacks:

var context = {
    name: {first: 'Sterling', last: 'Archer'},
    assoc: [
        {first: 'Lana', last: 'Kane'},
        {first: 'Cyril', last: 'Figgis'},
        {first: 'Pam', last: 'Poovey'}
    ],
    age: 36
};

// Filter an array
mozjexl.eval('assoc[.first == "Lana"].last', context).then(function(res) {
    console.log(res); // Output: Kane
});

// Do math
mozjexl.eval('age * (3 - 1)', context, function(err, res) {
    console.log(res); // Output: 72
});

// Concatenate
mozjexl.eval('name.first + " " + name["la" + "st"]', context).then(function(res) {
    console.log(res); // Output: Sterling Archer
});

// Compound
mozjexl.eval('assoc[.last == "Figgis"].first == "Cyril" && assoc[.last == "Poovey"].first == "Pam"', context)
    .then(function(res) {
        console.log(res); // Output: true
    });

// Use array indexes
mozjexl.eval('assoc[1]', context, function(err, res) {
    console.log(res.first + ' ' + res.last); // Output: Cyril Figgis
});

// Use conditional logic
mozjexl.eval('age > 62 ? "retired" : "working"', context).then(function(res) {
    console.log(res); // Output: working
});

// Transform
mozjexl.addTransform('upper', function(val) {
    return val.toUpperCase();
});
mozjexl.eval('"duchess"|upper + " " + name.last|upper', context).then(function(res) {
    console.log(res); // Output: DUCHESS ARCHER
});

// Transform asynchronously, with arguments
mozjexl.addTransform('getStat', function(val, stat) {
    return dbSelectByLastName(val, stat); // Returns a promise
});
mozjexl.eval('name.last|getStat("weight")', context, function(err, res) {
    if (err) console.log('Database Error', err.stack);
    else console.log(res); // Output: 184
});

// Add your own (a)synchronous operators
// Here's a case-insensitive string equality
mozjexl.addBinaryOp('_=', 20, function(left, right) {
    return left.toLowerCase() === right.toLowerCase();
});
mozjexl.eval('"Guest" _= "gUeSt"').then(function(val) {
    console.log(res); // Output: true
});

Installation

For Node.js or Web projects, type this in your project folder:

yarn add mozjexl

Access Mozjexl the same way, backend or front:

import mozjexl from 'mozjexl';

All the details

Unary Operators

Operation Symbol
Negate !

Binary Operators

Operation Symbol
Add, Concat +
Subtract -
Multiply *
Divide /
Divide and floor //
Modulus %
Power of ^
Logical AND &&
Logical OR ||

Comparisons

Comparison Symbol
Equal ==
Not equal !=
Greater than >
Greater than or equal >=
Less than <
Less than or equal <=
Element in array or string in

A note about in:

The in operator can be used to check for a substring: "Cad" in "Ron Cadillac", and it can be used to check for an array element: "coarse" in ['fine', 'medium', 'coarse']. However, the == operator is used behind-the-scenes to search arrays, so it should not be used with arrays of objects. The following expression returns false: {a: 'b'} in [{a: 'b'}].

Ternary operator

Conditional expressions check to see if the first segment evaluates to a truthy value. If so, the consequent segment is evaluated. Otherwise, the alternate is. If the consequent section is missing, the test result itself will be used instead.

Expression Result
"" ? "Full" : "Empty" Empty
"foo" in "foobar" ? "Yes" : "No" Yes
{agent: "Archer"}.agent ?: "Kane" Archer

Native Types

Type Examples
Booleans true, false
Strings "Hello "user"", 'Hey there!'
Numerics 6, -7.2, 5, -3.14159
Objects {hello: "world!"}
Arrays ['hello', 'world!']

Groups

Parentheses work just how you'd expect them to:

Expression Result
(83 + 1) / 2 42
1 < 3 && (4 > 2 || 2 > 4) true

Identifiers

Access variables in the context object by just typing their name. Objects can be traversed with dot notation, or by using brackets to traverse to a dynamic property name.

Example context:

{
    name: {
        first: "Malory",
        last: "Archer"
    },
    exes: [
        "Nikolai Jakov",
        "Len Trexler",
        "Burt Reynolds"
    ],
    lastEx: 2
}
Expression Result
name.first Malory
name['la' + 'st'] Archer
exes[2] Burt Reynolds
exes[lastEx - 1] Len Trexler

Collections

Collections, or arrays of objects, can be filtered by including a filter expression in brackets. Properties of each collection can be referenced by prefixing them with a leading dot. The result will be an array of the objects for which the filter expression resulted in a truthy value.

Example context:

{
    employees: [
        {first: 'Sterling', last: 'Archer', age: 36},
        {first: 'Malory', last: 'Archer', age: 75},
        {first: 'Lana', last: 'Kane', age: 33},
        {first: 'Cyril', last: 'Figgis', age: 45},
        {first: 'Cheryl', last: 'Tunt', age: 28}
    ],
    retireAge: 62
}
Expression Result
employees[.first == 'Sterling'] [{first: 'Sterling', last: 'Archer', age: 36}]
employees[.last == 'Tu' + 'nt'].first Cheryl
employees[.age >= 30 && .age < 40] [{first: 'Sterling', last: 'Archer', age: 36},{first: 'Lana', last: 'Kane', age: 33}]
employees[.age >= 30 && .age < 40][.age < 35] [{first: 'Lana', last: 'Kane', age: 33}]
employees[.age >= retireAge].first Malory

Transforms

The power of Mozjexl is in transforming data, synchronously or asynchronously. Transform functions take one or more arguments: The value to be transformed, followed by anything else passed to it in the expression. They must return either the transformed value, or a Promise that resolves with the transformed value. Add them with mozjexl.addTransform(name, function).

mozjexl.addTransform('split', function(val, char) {
    return val.split(char);
});
mozjexl.addTransform('lower', function(val) {
    return val.toLowerCase();
});
Expression Result
"Pam Poovey"|lower|split(' ')[1] poovey
"password==guest"|split('=' + '=') ['password', 'guest']

Advanced Transforms

Using Transforms, Mozjexl can support additional string formats like embedded JSON, YAML, XML, and more. The following, with the help of the xml2json module, allows XML to be traversed just as easily as plain javascript objects:

var xml2json = require('xml2json');

mozjexl.addTransform('xml', function(val) {
    return xml2json.toJson(val, {object: true});
});

var context = {
    xmlDoc:
        "<Employees>" +
            "<Employee>" +
                "<FirstName>Cheryl</FirstName>" +
                "<LastName>Tunt</LastName>" +
            "</Employee>" +
            "<Employee>" +
                "<FirstName>Cyril</FirstName>" +
                "<LastName>Figgis</LastName>" +
            "</Employee>" +
        "</Employees>"
};

var expr = 'xmlDoc|xml.Employees.Employee[.LastName == "Figgis"].FirstName';

mozjexl.eval(expr, context).then(function(res) {
    console.log(res); // Output: Cyril
});

Context

Variable contexts are straightforward Javascript objects that can be accessed in the expression, but they have a hidden feature: they can include a Promise object, and when that property is used, Mozjexl will wait for the Promise to resolve and use that value!

API

mozjexl.Jexl

A reference to the Jexl constructor. To maintain separate instances of Jexl with each maintaining its own set of transforms, simply re-instantiate with new mozjexl.Jexl().

mozjexl.addBinaryOp({string} operator, {number} precedence, {function} fn)

Adds a binary operator to the Jexl instance. A binary operator is one that considers the values on both its left and right, such as "+" or "==", in order to calculate a result. The precedence determines the operator's position in the order of operations (please refer to lib/grammar.js to see the precedence of existing operators). The provided function will be called with two arguments: a left value and a right value. It should return either the resulting value, or a Promise that resolves to the resulting value.

mozjexl.addUnaryOp({string} operator, {function} fn)

Adds a unary operator to the Jexl instance. A unary operator is one that considers only the value on its right, such as "!", in order to calculate a result. The provided function will be called with one argument: the value to the operator's right. It should return either the resulting value, or a Promise that resolves to the resulting value.

mozjexl.addTransform({string} name, {function} transform)

Adds a transform function to this Jexl instance. See the Transforms section above for information on the structure of a transform function.

mozjexl.addTransforms({{}} map)

Adds multiple transforms from a supplied map of transform name to transform function.

mozjexl.getTransform({string} name)

Returns {function|undefined}. Gets a previously set transform function, or undefined if no function of that name exists.

mozjexl.eval({string} expression, {{}} [context], {function} [callback])

Returns {Promise<*>}. Evaluates an expression. The context map and callback function are optional. If a callback is specified, it will be called with the standard signature of {Error} first argument, and the expression's result in the second argument. Note that if a callback function is supplied, the returned Promise will already have a .catch() attached to it.

mozjexl.removeOp({string} operator)

Removes a binary or unary operator from the Jexl instance. For example, "^" can be passed to eliminate the "power of" operator.

Hacking

$ yarn install
$ yarn test

Precommit hook

Mozjexl provides a config for Therapist. Install it with Pip, and then run therapist install in this repo to set it up. It will automatically format your Javascript with Prettier, and run ESLint checks before committing your code.

Building a JSM for use in mozilla-central

$ yarn build

The result will be in vendor/mozjexl.jsm. This is for use in mozilla-central/toolkit, so it is not minified as it will be compressed in the omnijar file.

License

Mozjexl is licensed under the MIT license. Please see LICENSE.txt for full details.

Credits

Jexl was designed and created at TechnologyAdvice.

mozjexl's People

Contributors

bors[bot] avatar dmose avatar eliekhoury avatar glasserc avatar gregglind avatar mythmon avatar rehandalal avatar tomfrost avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

mozjexl's Issues

Ready to use frontend script

In the original project, there was a "ready to use" frontend script, in the form of a minified js file :

For the frontend, drop dist/jexl.min.js into your project and include it on your page with:
<script src="path/to/jexl.min.js"></script>

I would be a really appreciated gesture, since not everyone uses node.js (or even likes it) and since it is pretty heavy to install/configure, even more since the "trendy tools" change every month or so...

Would it be possible for you to generate a "standalone" javascript file (minified or not) to be used in a frontend only context?

Add Bors

Bors, the automated merge bot used on mozilla/normandy and other projects, should be added to this repo.

Replace Gulp

Gulp is out of style, and doesn't match our Product Delivery's other projects. We should replace it with something else, such as Webpack.

Add ESLint

Not everything can be handled by Prettier (#2), so we also need eslint for the rest of the linting issues.

Convert to Yarn

Right now we use NPM to install dependencies. We should switch to Yarn.

Update readme

The readme has a few problems.

  • Uses NPM (Which is a problem once #6 is fixed)
  • Has Markdown syntax errors that don't render on Github
  • Points to Travis CI status
  • Isn't updated for mozjexl

Define a process for building a mozjexl blob for Firefox

Firefox has (at toolkit/components/utils/mozjexl.js) a vendored version of this library available as compiled output. The original process which produced this vendored version is gone now and there isn't anything in place to replace it. It would be good to have a mechanism in this repository to build a mozjexl.js so that we could update it when there is a new release.

Per discussion with @mythmon via IRC, the historic process for building the mozjexl output is visible in https://github.com/mozilla/normandy/blob/43f0ce22ec1cb73a061f95236aa9b79ff7fb8410/recipe-client-addon/webpack.config.js, but it is likely to have bit-rotted.

Refs https://bugzilla.mozilla.org/show_bug.cgi?id=1520362, which represents an approach to solving the problem from within the mozilla-central repository, which is probably the better strategy long-term but more work.

At the moment, there's no pressing need for this (or for the mozilla-central) approach, but eventually we'll need one or the other.

generated mozjexl.jsm won't sometimes won't load as JSM

The newly generated jsm from #21 (which hasn't yet landed in mozilla-central), won't load in some circumstances, because of an error in the webpack configuration file that caused two different symbols that should be named identically to be named slightly differently.

This is also being tracked as bug 1779626.

Add CircleCI

Circle CI automation should be enabled and configured for this repo.

Run on a modern version of Node

Right now mozjexl runs on Node 4, and fails on newer versions of Node. We should update to a more recent version of node, and ideally add build matrix support for all supported versions of Node to our CI.

Move language documentation out of README

The README is getting awfully crowded. A lot of the contents there, especially the language documentation, should move to another place for documentation. Readthedocs is nice, but other files in the repo or a github wiki would also work.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.