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

clerestory

v0.10.3

Published

Context-free grammar tool for text generation inspired by Tracery

Downloads

7

Readme

Clerestory

Clerestory is a procedural text generation tool based on the concept of context-free grammars heavily inspired by Kate Compton's Tracery and Ian Holmes's Bracery.

The aim is to provide a simple and expressive syntax which can work well in the context of a larger project where some state is managed from outside the text-generation grammar -- what the authors of Bronco call a yielding grammar.

This is still WIP and API/expression syntax may change significantly.

Why use this

The purpose of this project is to provide a lightweight, efficient, productive tool for ad hoc generation of procedural text, ranging from person or place names to item descriptions to entire dialogues or stories. The API provides ways to guide what text is generated, such as conditional rules and different distributions.

Key concepts

Grammar

The main artefact you will interact with is a Grammar, which is simply a set of symbols. Expanding a symbol on a grammar will produce text by evaluating that symbol's rules, parsing the resulting expression, and following references to any other symbols recursively until a final text is produced.

import { Grammar } from 'clerestory';

const rules = {
    pet: [ 'cat', 'dog', 'fish'],
    story: 'Yesterday I bought a #pet#.'
}
const animalGrammar = new Grammar(rules);
console.log(animalGrammar.expand('story'));
// "Yesterday I bought a fish."
console.log(animalGrammar.state.pet);
// "fish"
// now that the "pet" symbol has been expanded it will keep this value
// until you change it
animalGrammar.state.pet = 'mongoose';
console.log(animalGrammar.expand('story'));
// "Yesterday I bought a mongoose."

Symbols

A symbol can be thought of as simply a set of rules for producing a string of text. The simplest possible symbol just returns a single string, but symbols have a lot of other powerful features for guiding how text gets generated. You can easily add or change symbols on a grammar by accessing its state property. Continuing the example above:

// add a new symbol
animalGrammar.state.benefit = ['loyalty', 'personality', 'smell'];
// change an existing symbol
animalGrammar.state.story = 'I love my #pet# for its #benefit#.';
animalGrammar.expand('story');
// "I love my mongoose for its personality."

Note an important difference with Tracery: in Clerestory, symbols are "flattened" as soon as they are accessed, whether through a reference in another symbol or by outside code. Tracery differentiates between static variables and dynamic symbols that share the same namespace. Unlike Tracery, Clerestory does not have syntax for seeing variables via an expression, but does allow referencing a symbol without flattening it like #~symbol#.

const schrodinger = new Grammar({ cat: ['alive','dead']});
console.log(schrodinger.state.cat);
// "dead"
schrodinger.state.box = 'I looked in the box and the cat was #cat#.';
console.log(schrodinger.expand('box'));
// "I looked in the box and the cat was dead."

The expand method on a grammar or symbol will always reevaluate the symbol (though any other symbols referenced will keep their preexisting values).

console.log(schrodinger.expand('cat'));
// "alive"

You can also access the symbols on a grammar directly to modify or reset them:

schrodinger.symbols.cat.reset();
console.log(schrodinger.state.cat);
// "dead"

Rules

The possible expressions a symbol can produce are called rules. The cat symbol above has two rules: "alive" and "dead"; expanding it will choose one at random. The real power of a context-free grammar comes from the expression syntax, described below, which allows you to reference other symbols, modify symbol outputs, and evaluate conditional statements within these expressions.

Distributions

You can influence which rules a symbol will return by setting a different distribution on the symbol.

random is the default and will pick a rule at random each time. pop will select a rule at random and remove it from the stack. Note that if you run out of rules, the symbol will return an empty string. weighted allows you to set a weight on each rule to determine its relative chance of being picked. You will need to pass rules in object syntax like [{ text: 'cat', weight: 5 },{ text: 'ferret', weight: 1 }]. Weight should be a positive integer. popWeighted works by creating a number of copies of each rule equal to its weight, which are removed as they are used. shuffle acts like a "deck of cards". Rules are shuffled and one is drawn on each evaluation, and when they run out the used rules are reshuffled into a new "deck".

To specify a distribution, you need to use object-style symbol defintion:

const petGrammar = new Grammar({
    pet: { rules: ['cat','dog','fish'], distribution: 'shuffle' }
});

Functional rules

A powerful feature of Clerestory is that it supports passing a function as the "rules" for a symbol. This allows you to completely bypass the built-in rule selection functionality and generate abritrary expressions in pretty much any way you wish.

const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const dayGrammar = new Grammar({
    today: () => daysOfWeek[new Date().getDay()]
});

Expressions

Expressions are the individual bits of text produced by symbol rules and parsed by Clerestory. Expression syntax is very simple, largely inspired by Tracery.

In this expression:

Why hello there, #traveler#!
// Why hello there, Gandalf!

#traveler# is a reference to another symbol in the same grammar. Once referenced in this way, the symbol "traveler" will persist its generated value. If you want to reference a symbol without saving the output, use #~traveler# which will always expand the symbol without flattening it.

Bracery-style alternations are also supported:

[Hi|Hello|Yo], #traveler#!
// Hello, Gandalf!

You can even nest symbol references inside alternations:

[Hi #traveler#!|#traveler#, what's up?|Howdy!] How fares your quest?
// Gandalf, what's up? How fares your quest?

Clerestory also supports Tracery-style modifiers. These are generally simple text transformations which can be chained. A few useful defaults are included and you can add your own.

#animal#.s.uppercase
// CATS

If you want to include back-to-back symbol references without a space, you must use the | character:

#animal.s# eat #animal#|#nutrition#
// dogs eat dogfood

Conditionals

A powerful feature that Clerestory adds is conditional expressions.