filigree-text
v0.1.0
Published
A text expansion toolkit
Downloads
3
Readme
Filigree
A language for generating text using text expansion rules, similar to Tracery. Use this to build bots or generate poetry!
Try it
Write Filigree grammars in your browser with the online editor. Source code for that is here.
There's also an example project you can clone on Glitch, which is nice because it remembers your grammars. Source code for that is also here on Github, and you can see its output page here on Glitch.
Table of contents
- Simple input grammar
- Detailed input grammar
- Install
- Command-line use
- Usage in Javascript
- Advanced use: Rule modifiers
- Determinism
- Developing
Simple input grammar
# Use <ruleName> to reference another rule.
# Use [a/b/c] to randomly choose from multiple options.
tweet = <username>: [What a nice day!/I am hungry for a <snack>]
username = @<fruit>
snack = [
<fruit>
<fruit> pie
<fruit> pudding
]
fruit = [apple/banana/cherry]
Generating the "tweet" rule gives outputs like this:
@apple: What a nice day!
@cherry: I am hungry for a banana pudding!
@cherry: I am hungry for a apple pudding!
Detailed input grammar
# A Filigree file is a list of text-expansion rules.
# Their order in the file does not matter.
# Define a rule. The right side is like a string but without quote marks.
job = candlestick maker
# Quote marks have no special meaning; they're just part of your text
reply = "That's nice," he replied.
# Randomly choose one of several options with square brackets and "/"
name = Captain [Moss/Pebble/Twig]
# Reference another rule with angled brackets
greeting = Hello to <name> who is a <job>.
# To make things occur rarely, use empty choices.
# The more empty choices, the less often the other choices will appear.
greeting2 = Hi [there /]<name>. # "there " happens 1/2 of the time
greeting3 = Hi [there ////]<name>. # "there " happens 1/5 of the time
# Choices can contain nested choices and references
greeting4 = [Hi [there /]<name>/Hello], how are you today?
# Or, choices can be written across multiple lines instead of using "/" on a single line.
# You can't mix these; choose either "/" or multi-line.
# Multi-line choices ignore blank lines, which means you can't use them to
# make rare choices like [rare////].
# Indentation and whitespace around each line is ignored.
berries = [
blueberry
strawberry
cherry
]
fruits = [
apple
banana
cherry
<berries>
]
slogan = Have a [
snack
bite
] today! # multi-line choice inside a longer phrase
# This is not allowed -- the brackets have to be on their own lines:
# foo = [bar
# baz
# quuz]
# Trailing whitespace on a line is removed.
# To make whitespace matter, put it inside choice brackets.
# You can also make a zero-length string this way.
threespaces = [ ]
wordAndSpace = [hello ]
nothing = []
# Rules can reference themselves but make sure it's possible for them to eventually stop!
# This returns "yo yo yo yo!" with a random number of repeats.
repeat = yo[ <repeat>/!]
a = b # Comments start with "#" and can go on the same line as a rule.
c = d # Indentation is allowed and has no meaning
multiline = line1\nline2\nline3 # use "\n" to make a new line in the rule output
# Each rule declaration must be on its own line. TODO: allow them to span lines
# Blank lines are ignored.
# The equals sign must have one space on each side. TODO: fix this
# Rule names can contain letters, numbers, dashes, and underscores, but not spaces. Capitalization matters.
# References can't contain anything nested inside the angle brackets, only plain old rule names
# (no "<[cat/dog]Name>")
# If you see angle brackets in the output, it means you're referencing a rule that doesn't exist.
# (like "Hello <thisRuleDoesNotExist>")
# If a rule name is defined more than once, the last one wins.
# Besides that, rules can be declared in any order.
# Set your text editor to make-file or Python language mode for approximate syntax highlighting.
Install
To install the command-line tool:
npm install --global filigree-text
To use it in a specific javascript project:
npm install --save filigree-text
Command-line use
filigree <filename> [-s|-r|-j|-g]
Print out debugging info about a filigree file.
With no options, prints a list of the rules in the file.
-s, --source print source
-r, --repr print source in repr mode (for debugging)
-j, --json print raw rule JSON
-g, --graph generate a graph of rules and their dependencies in mermaid format
filigree <filename> <ruleName> [-n|-s|-r|-j]
Generate text from a rule in a filigree file.
-n, --num number of strings to generate. (default: 1)
--seed <s> random seed for deterministic output
If these options are provided, the specific rule is printed in detail
instead of generating text.
-s, --source print source
-r, --repr print source in repr mode (for debugging)
-j, --json print raw rule JSON
Example:
filigree tweets.filigree # see a list of the rules in the file
filigree tweets.filigree tweet -n 5 # generate text from the "tweet" rule 5 times
To see a graph of your rules and their dependencies, you'll need to install mermaid.cli.
npm install --global mermaid.cli # installs "mmdc" mermaid graph renderer
filigree yourfile.filigree -g > graph.mmd
mmdc -i graph.mmd -o graph.png
The graph only shows rules, not individual pieces of rules.
Usage in Javascript
Filigree is written in Typescript and comes with Typescript definitions. It runs in the browser and in node.js.
import Filigree from 'filigree-text';
let source = `
name = [Joe|Sue]
greeting = Hello <name>!
`;
// parse your filigree source
let seed = 'abc';
let fil = new Filigree(source, seed); // seed is optional, omit it for different results every time
if (fil.err) {
// if the source is has syntax errors, they will be shown here
console.error(fil.error.message);
process.exit(1)
}
// generate a string from the "greeting" rule
console.log(fil.generate('greeting');
// you can also supply a "wrapper" function to add markup around the results
// as the rules are put together.
let wrapperFn = (rule : string, text : string) : string =>
`<div style="padding:10px; display:inline; border: 1px dotted red; border-radius:5px;">
<sup>${rule}</sup>
${text}
</div>`;
console.log(fil.generate('greeting', wrapperFn));
// you can reset the random seed to get the same result as before
fil.seed('abc');
console.log(fil.generate('greeting');
Advanced use: Rule modifiers
Modifiers are small functions that change the string produced by a rule, for example to make it uppercase.
greeting = hello
fruit = [apple/banana]
sentence = <greeting.titlecase> world, have some <fruit.s>!
# you can apply multiple modifiers in a row
loudSentence = <sentence.trim.uppercase>!!!
Modifiers can only be used inside a <rule>
reference.
Built in modifiers
// grammar
s // bat -> bats, box -> boxes
a // box -> a box, apple -> an apple
// whitespace
trim // " hello " -> "hello" (remove leading and trailing spaces)
trimleft // " hello " -> "hello "
trimright // " hello " -> " hello"
mergespaces // " a b " -> " a b " (combine consecutive spaces into a single space)
// case
uppercase // exAMPLE INput -> EXAMPLE INPUT
lowercase // exAMPLE INput -> example input
titlecase // exAMPLE INput -> Example Input (capitalize first letter of each word)
sentencecase // exAMPLE INput -> Example INput (capitalize first character only)
// fun
inception // hello -> H E L L O
wackycase // hello world -> hElLo wOrLd
Adding your own custom modifiers
let fil = new Filigree(source);
// Add your own functions to the fil.modifiers object
// They should accept a string and return a string.
fil.modifiers.loud = (input : string) => {
return input.toUpperCase() + '!!!!';
}
Determinism
If there are any choices in you grammar (square brackets), there is an element of randomness. Normally you will get different random outputs each time you call generate
.
You can provide a random seed which will result in the same sequence of randomness every time.
If you omit the seed, you will get completely different results each time.
// you can provide a seed when creating a Filigree, but in this case we don't.
let fil = new Filigree('start = [a/b/c/d]');
// different results every time you run this file
fil.generate('start'); // a
fil.generate('start'); // c
fil.generate('start'); // a
// set a seed to begin a repeatable sequence of randomness
fil.seed('whatever');
fil.generate('start'); // d
fil.generate('start'); // b
fil.generate('start'); // a
// set the seed again to obtain the same sequence again
fil.seed('whatever');
fil.generate('start'); // d
fil.generate('start'); // b
fil.generate('start'); // a
Any change to your Filigree rule source code will have a butterfly effect which results in new, different results for a certain seed. In other words, the output is only repeatable if the Filigree rules stay the same. Also each call to generate
moves further into the sequence of random numbers, so for maximum repeatability you should set the seed just before every call to generate
.
When making random choices, Filigree avoids choosing the same item twice in a row (either within a rule or across multiple calls to generate
). This works best if you don't reset the seed often, since that clears the memory of which items have been recently chosen.
Developing
The parser is made using Nearley and is specified in filigreeGrammar.ne.
Running npm run build
will do these steps:
- Compile the Nearley parser using
nearleyc
, outputtingfiligreeGrammar.ts
- Generate a railroad diagram of the syntax to
railroad.html
- Run
tsc
to compile all the typescript fromsrc/
intodist/
Compiling the parser only has to happen if the grammar itself changes. Normally the parser is just used, not compiled, in day-to-day use. Compiling has to happen on the command line but using the parser can happen in the browser.
Files and dependencies:
(run npm run depgraph
to update this image)