Code Monkey home page Code Monkey logo

tracery-rs's Introduction

tracery

Rust implementation of the tracery generative grammar language.

Crates.io MIT licensed Documentation Coverage Status

This library is a Rust port/implementation of tracery, the generative grammar language designed and created by Kate Compton. Given a set of rules written in the tracery syntax, it will use them to procedurally generate strings of text. For more information about the tracery language, see Language Concepts.

Usage

Usage of the library can be divided into two areas: creation of grammars and the generation of output strings.

Grammar Creation

Grammars can be created using the grammar! macro, from an any iterable rust object of strings and associated lists of strings, or for compatibility with the original tracery, from a string representing a JSON map.

The grammar! macro

Accepts input in the form "key" => [ "list", "of", "rules" ] or, in the case of a key having only one rule, "key" => "rule". Equivalent to manually building a map and then calling Grammar::from_map

use tracery::grammar;
let g = grammar! {
    "origin" => "#tool# is #description#!",
    "tool" => "tracery",
    "description" => [ "fun", "awesome" ]
}?;

From a map/iterator

A grammar can be created from any object implementing, essentially, IntoIterator<Item = (Into<String>, Into<Vec<Into<String>>)>. For example, HashMap<String, Vec<String>> or BTreeMap<&str, &[&str]>.

let map = hashmap! {
    "origin" => vec![ "#tool# is #description#!" ],
    "tool" => vec![ "tracery" ],
    "description" => vec![ "fun", "awesome" ]
};
let g = tracery::from_map(map)?;

From a JSON string

For compatibility with the original tracery, a Grammar can be created from a string representing a JSON object. This feature is controlled by the tracery_json feature, which is enabled by default. It can be turned off if you do not require this functionality.

let json = r##"{
    "origin": [ "#tool# is #description#!" ],
    "tool": [ "tracery" ],
    "description": [ "fun", "awesome" ]
}"##;
let g = tracery::from_json(json)?;

Generating output strings

There are two methods for getting a generated output string from a created Grammar: execute and flatten. Generally, execute should be preferred if possible.

execute

execute takes two parameters: the rule to expand and an RNG to use during generation. The RNG can be any type implementing rand::Rng.

use tracery::grammar;
let mut g = grammar! {
    "origin" => "#tool# is #description#!",
    "tool" => "tracery",
    "description" => [ "fun", "awesome" ]
}?;
// Generate an output (either "tracery is fun!" or "tracery is awesome!")
let key = String::from("origin");
let output = g.execute(&key, &mut rand::thread_rng())?;

execute generates its output using the Grammar in-place. Since Grammars are allowed to modify their own rule stacks, execute must take a &mut self reference. This means that any modifications made during an execution will persist in the Grammar.

use tracery::grammar;
// This time, origin has a side-effect: it creates the rule 'aside'
let mut g = grammar! {
    "origin" => "#[aside:Rust is, too]tool# is #description#!",
    "tool" => "tracery",
    "description" => [ "fun", "awesome" ]
}?;
// Generate an output (either "tracery is fun!" or "tracery is awesome!")
let key = String::from("origin");
let output = g.execute(&key, &mut rand::thread_rng())?;
// The previous call to execute created the 'aside' rule
let key = String::from("aside");
// Generates the string "Rust is, too"
let output = g.execute(&key, &mut rand::thread_rng())?;

flatten

flatten, unlike execute, always operates on the default rule of the Grammar ("origin" by default), but like execute, takes an instance of rand::Rng to use during generation. In addition, flatten creates a clone of the Grammar to use during generation, then discards it, which means that any side-effects that occur will be discarded when it's done.

use tracery::grammar;
let g = grammar! {
    "origin" => "#tool# is #description#!",
    "tool" => "tracery",
    "description" => [ "fun", "awesome" ]
}?;
// Generate an output (either "tracery is fun!" or "tracery is awesome!")
let output = g.flatten(&mut rand::thread_rng())?;

License: MIT/Apache-2.0

tracery-rs's People

Contributors

caranatar avatar daniel-tp avatar

Stargazers

Nikita avatar  avatar jrdpastors avatar John Wiseman avatar  avatar cynthia avatar BrettW avatar noah04 avatar  avatar Daniel Kahlenberg avatar Jesús Leganés-Combarro avatar Dania Rifki avatar Tim Kersey avatar Xe Iaso avatar Berwyn Jamesson avatar

Watchers

 avatar

tracery-rs's Issues

`rand` dependency outdated

The rand crate was updated to the major 0.8 version [1]. Thus, using tracery-rs with rand::thread_rng from the new version yields the following compilation error:

error[E0277]: the trait bound `ThreadRng: rand_core::RngCore` is not satisfied
 --> src/main.rs:8:36
  |
8 |         .execute(&"origin".into(), &mut rand::thread_rng())
  |                                    ^^^^^^^^^^^^^^^^^^^^^^^ the trait `rand_core::RngCore` is not implemented for `ThreadRng`
  |
  = note: required because of the requirements on the impl of `rand::Rng` for `ThreadRng`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

Updating the version in tracery-rs/Cargo.toml fixes the issue. Moreover, cargo test also runs without any error.
If you agree that this would be a good solution to solve the issue, I can prepare a Pull Request with the change.

Minimal example

Setup

$ cargo --version
cargo 1.50.0 (f04e7fab7 2021-02-04)

$ rustc --version                  
rustc 1.50.0 (cb75ad5db 2021-02-10)

Cargo.toml

[package]
name = "whatever"
version = "0.1.0"
authors = [""]
edition = "2018"

[dependencies]
rand = "0.8.3"
tracery = "0.2.0"

main.rs

fn main() {
    let mut g = tracery::grammar! {
        "origin" => "Hello, World!",
    }
    .unwrap();

    let output = g
        .execute(&"origin".into(), &mut rand::thread_rng())
        .unwrap();
    dbg!(output);
}

Returns a ParseError when given an empty string

when given the following json, grammar.flatten() returns a ParseError with strange contents:

{
"origin": [""]
}

running with this simple code:

use std::fs;

fn main() {
    let json = fs::read_to_string("tracery2.json").unwrap();
    let g = tracery::from_json(json).unwrap();
    let output = g.flatten(&mut rand::thread_rng()).unwrap();
    println!("{}", output);
}

this gives

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/garden-of-life`
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseError(" --> 1:1\n  |\n1 | \n  | ^---\n  |\n  = expected rule")', src/main.rs:6:38

Configurable RNG

There should be a way to pass in an rng to be used in Grammar and Tag

Implement rule stacks

Currently, overriding an existing rule in an action is inconsistent and irreversible due to a lack of support for proper rule stacks.

  • Keys should map to a Vec<Vec<Rule>>, instead of mapping to a Vec<Rule>
  • The overrides parameter of Flatten needs to be removed, as it becomes unneccessary
  • Support for popping a key using [key:POP] needs to be added

Migrate README to Markdown

The original crate's README was in AsciiDoc. This format is not supported by crates.io and should be replaced with MarkDown

Minimize use of JSON in favor of native maps

Motivation

JSON makes sense as the input type for the original tracery, due to the fact that it's implemented in JavaScript. For ports of tracery, I'm of the opinion that the focus should be on native map types so that:

  1. the way the grammar is stored is irrelevant (e.g., if an application wants to store some grammars in RON, it shouldn't matter to the library)
  2. grammars can be more easily created and modified programmatically

Implementation

  • Support constructing Grammar from IntoIterator<...,...>
  • Add json feature
    • Gate tracery::from_json
    • Remove Grammar::from_json
    • Control serde dependencies

Add/fix doctests

The goal is to have working doctest examples on all public functions. In the original crate, they were either missing or ignored.

  • Get existing doctests working
  • Add new doctests for remaining functions

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.