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

meristem

v2.2.0

Published

Procedural text generation using a context-free grammar

Downloads

6

Readme

Meristem

npm version Build Status Coverage Status

A lightweight (no dependencies!) Javascript library for procedural text generation using a context-free grammar. Like template literals, or Python's string formatting, but significantly more flexible.

Examples of usageDocumentationChangelog

How To Use

First, npm install meristem, and require it in your code:

Format = require('meristem').Format
WeightedRandom = require('meristem').WeightedRandom

Meristem privides three constructors: Format, WeightedRandom, and FrozenRandom.

Format:

When creating a Format, you will generally pass the constructor a string, which we'll call the format string, and an object, which we'll call the definitions, or definitions object.

When you call a Format's .expand method, it will return the format string, but with any parenthetical 'nonterminals' replaced with the values assosciated with them in definitions object. The following code:

let f = new Format('(month) is the cruelest month', { month: 'April' })
console.log(f.expand()) //

...logs April is the cruelest month1

If you pass an object to Format.expand, it will be used instead of the Format's own definitions object. This also you to use a format with no definitions specified:

let f = new Format('(month) is the cruelest month,', { month: 'April' })
console.log(f.expand()) //logs 'April is the cruelest month,'
console.log(f.expand(), { month: 'May' }) //logs 'May is the cruelest month,'

Format.expand also operates recursively: if a nonterminal's definition is itself a string with parentheticals in it, they will be treated as nonterminals as well. For example:

const Format = require('./Format')

let definitions = {
  aprilDescription: 'the cruelest month',
  'things the cruelest month does':
    'breeding  / Lilacs out of the dead land, mixing / Memory and desire, stirring / Dull roots with (season) rain',
  season: 'spring',
}
const burialOfTheDead = new Format(
  'April is (aprilDescription), (things the cruelest month does)',
  definitions
)
console.log(burialOfTheDead.expand())

...logs April is the cruelest month, breeding Lilacs out of the dead land, mixing Memory and desire, stirring Dull roots with spring rain1

What if the values in the definitions object aren't strings?

Well, when a nonterminal's value is a Format, its expand method will be called, and then the result will be treated just as any other string would. This is could be useful if you want to nest Formats but don't want the inner ones to use the same definitions as the outer.

However, a more common situation is for the value to be a WeightedRandom.

WeightedRandom:

The purpose of a WeightedRandom is to choose randomly from a list of options, while allowing you to set weights, making some options more likely than others.

The WeightedRandom constructor takes an arbitrary number of arrays, each consisting of some value as the first element, and a numerical weight assosciated with it as the second. It has a .choose method, which, when called, returns a random one of those values, with probability correspning to the assosciated weight. For example

// What are the roots that clutch, what branches grow
// Out of this stony rubbish?
const wRand = new WeightedRandom(['Lilacs', 1], ['Hyacinths', 2], ['That corpse you planted last year in your garden', 1]})
console.log(wRand.choose())

will log Lilacs 1/4 of the time, That corpse you planted last year in your garden1 1/4 of the time, and Hyacinths 2/4 of the time. (Since the weights total to 4.)

Alternatively, you may pass the WeightedRandom an object with numerical values, in which case the .choose method will returns a random key from that object, with probability correspning to the assosciated value. For example

const wRand = new WeightedRandom({
  Lilacs: 1,
  Hyacinths: 2,
  'That corpse you planted last year in your garden': 1,
})
console.log(wRand.choose())

is equivalent to the previous example. This is slighly more succinct, but does not allow for non-string options.

Using a WeightedRandom in a format:

When a nonterminal's value is a WeightedRandom, the WeightedRandom's .choose method is called, and the result expanded if relevant and inserted in the result, just as any other string would be. This allow you to generate random text using a format, like so:

const randomCard = new WeightedRandom(
  ['the drowned Phoenician Sailor', 2],
  ['Belladonna, The Lady of the Rocks', 1]
)
const divination = new Format('Here, said she, is your card, (card)', {
  card: randomCard,
})

console.log(divination.expand())

...logs Here, said she, is your card, the drowned Phoenician Sailor or Here, said she, is your card, Belladonna, The Lady of the Rocks1

FrozenRandom:

FrozenRandom extends WeightedRandom, and behaves identically the first time its .choose method is called. However, every subsequent call of .choose will return the same result as the first. For example,

const randomFlowers = new FrozenRandom( ['hyacinth', 3], ['lilac', 1] })
const introductions = new Format(
  'You gave me (flower)s first a year ago; / They called me the (flower) girl.',
  { d: day, w: weather }
)

will log You gave me hyacinths first a year ago; / They called me the hyacinth girl.1 or You gave me lilacs first a year ago; / They called me the lilac girl., but won't ever return You gave me hyacinths first a year ago; / They called me the lilac girl.

Once you call .reset, however, the FrozenRandom will choose randomly again the next time .choose is called.


Footnotes

1: Eliot, Thomas S., The Waste Land, 19222

2: Perhaps it's not really neccessary to cite in this context, but I like footnotes, as you can see.3

3: Although this may be going a bit too far. 3 4

4: Relevant xkcd