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

lcml

v1.1.3

Published

Low-Code Markup Language (DSL) for Values with Dynamic Expressions

Downloads

11

Readme

LCML -- JSON with expressions

npm npm bundle size npm type definitions dependencies

Low-Code Markup Language (DSL) presents values with Dynamic Expressions. It is a superset of human readable JSON.

[ 👯 Try it Now | 💻 GitHub | 📓 LCML Syntax | 📓 Integrating LCML ]

| Highlights | | |------------|-----| | 🤓 loose mode | 💪 error-tolerant (recover from errors) | | 🌲 parse and output AST | 👨‍🎓 type information is inferred | 🎼 output formatted JavaScript | 🔨 expression processing hooks

Have a Glimpse

| Written in LCML | Output JavaScript | Inferred Type Information | | ------------------------- | ------------------------------- | ------------------------- | | 3.14159 | 3.14159 | number | | [1, 2, {{ dice() }}] | [1, 2, dice()] | array with 2 numbers + 1 expression | | "hello {{ user.name }}" | "hello" + toString(user.name) | string | | {{ foo.bar }} | foo.bar | expression |

Here is a loooong LCML example:

// this whole text is written in LCML
// human-readable JSON with {{ expression }} inside:
{
  name: "John {{ lib.genLastName() }}",  // in string
  info: {{ lib.genInfo() }},  // as property value
  tags: ["aa", 123, false, {{ location.href }}],

  {{ lib.sym }}: "wow",  // as property key
}

Compiled with default options:

// output valid JavaScript code
{
  name: "John" + toString(lib.genLastName()),  // wrapped by "toString"
  info: lib.genInfo(),
  tags: ["aa", 123, false, location.href],

  [lib.sym]: "wow",
}

And every part's type information is inferred:

[] object
  [name] string
  [info] unknown
  [tags] array
    [0] string
    [1] number
    [2] boolean
    [3] unknown
  [[( lib.sym )]] string

LCML Syntax

LCML syntax is based on JSON syntax, with {{ expression }} and comments supported.

We support /* block comment */ and // line-comment

You can use {{ expression }} in many places:

  • in string
  • as array item
  • as property value
  • as property key
  • as the whole LCML

Loose Mode

When { loose: true } is passed to parse, these invalid LCML will become valid strings:

| LCML | Default Mode | Loose Mode (loose: true) | |------|--------------|------------| | {{ user.name }}, Welcome | Error: unexpected remainder | treated as string "{{ user.name }}, Welcome" | | Hello, {{ user.name }} | Error: invalid input (at "H") | treated as string "Hello, {{ user.name }}" | | /* corrupted */ {{ user | Error: expect end of expression | treated as string "{{ user" |

Loose Mode Rules:

  1. leading comments are ignored, then the remainder might be treated as string

  2. if the beginning of LCML input looks like a string, array or object, the loose mode will NOT work!

  3. { ignoreUnparsedRemainder: true } will not work, unless loose mode is suppressed (see rule #2)

  4. due to rule #2, corrupted input like { hello: will cause a Error, not string.

    • (dangerous) to treat it as string, set { onError: 'as-string' } -- this can be confusing! the parser still outputs a string but it is NOT Loose Mode's credit!
  5. if Loose Mode actually has functioned, parser will return { looseModeEnabled: true }

Some rarely-used notices related to the rule #4, FYI:

  • if { onError: 'as-string' } is set, to tell whether it has functioned, you shall check !!parseResult.error && parseResult.ast.type === 'string' && !parseResult.ast.quote instead of parseResult.looseModeEnabled

Integrating LCML

import { compile, CompileOptions } from 'lcml';
// compile = parse + toJS

const options: CompileOptions = {

  // loose: false,
  // onError: 'throw' | 'recover' | 'as-string',
  // ignoreUnparsedRemainder: false,
  // treatEmptyInput: 'as-undefined',
  
  // compact: false,
  // processExpression: (node, parents) => { return node.expression },
  // globalToStringMethod: "toString",
  
};

const result = compile('"Hello, {{ user.name }}"', options);

console.log(result.body);
// => 'Hello, ' + toString(user.name)

console.log(result.ast);
// => { type: "string", start: 0, end: 24 }

console.log(result.expressions);
// => [
//      {
//         start: 8,
//         end: 23,
//         expression: " user.name ",
//      }
//    ]

Global toString Method

In the generated code, you might see toString(something).

This happens when user use {{ expression }} inside a string literal.

Therefore, to ensure the generated JavaScript runnable, you shall compose your function like this:

function composedGetter() {
  // declare the toString method
  const toString = x => (typeof x === 'string' ? x : JSON.stringify(x));

  // provide other variables that user might reference
  const user = getUserInfo();
  const state = getState();

  // return (/* put the generated code here! */);
  return 'Hello, ' + toString(user.name);
}

You can set option globalToStringMethod in order to use other name instead of toString.

Process Embedded Expressions

As presented above, option processExpression can be a callback function receiving node and its parents, returns a JavaScript expression.

You can read node.expression, transpile it and return new expression. For example, use Babel to transpile the fashion ESNext syntax like pipeline operator.

The generated JavaScript code will be affected.

Beware: in the received node.expression, leading and trailing spaces are NOT trimmed.

processExpression: (node, parents) => {
    return node.expression;
}