{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Tracery and Python\n", "\n", "by [Allison Parrish](http://www.decontextualize.com/)\n", "\n", "This is a tutorial on how to use [Tracery](http://www.crystalcodepalace.com/tracery.html) in Python.\n", "Tracery is a computer language for random text generation originally developed\n", "by [Kate Compton](https://twitter.com/galaxykate).\n", "\n", "The easiest way to use Tracery in Python is to install [pytracery](https://github.com/aparrish/pytracery), my Python port of Kate's original code. You can install it on the command line with `pip`:\n", "\n", " $ pip install tracery\n", "\n", "If you're running this in Jupyter Notebook, you can execute the cell below:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: tracery in /Users/allison/opt/miniconda3/envs/rwet-2022/lib/python3.8/site-packages (0.1.1)\r\n" ] } ], "source": [ "import sys\n", "!{sys.executable} -m pip install tracery" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then you need to import the `tracery` library:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import tracery" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You don't need Python to use Tracery! [Here's a version of this tutorial](http://air.decontextualize.com/tracery/) that you can use with any implementation of Tracery. I recommend Beau Gunderson's [Tracery writer](https://beaugunderson.com/tracery-writer/) as a kind of Tracery playground. You can also use Kate Compton's\n", "[Tracery tutorial](http://www.crystalcodepalace.com/traceryTut.html), which has a [visual editor](http://www.brightspiral.com/tracery/) or [Cheap Bots Done Quick](http://cheapbotsdonequick.com/), which has a built-in editor for writing Tracery grammars for Twitter bots with a minimum amount of fuss.\n", "\n", "You might be interested in reading [Nora Reed's explanation of how\n", "@nerdgarbagebot works](http://barrl.net/2801), which takes you through the\n", "process of ideating and implementing a Tracery grammar for a Twitter bot. (Nora\n", "Reed makes a lot of amazing bots with Tracery, including\n", "[@thinkpiecebot](https://twitter.com/thinkpiecebot).)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Rules and expansions\n", "\n", "A Tracery *grammar* is a series of rules that tell the computer how to put text\n", "together, piece by piece. Tracery grammars consist of a series of *rules* and\n", "*expansions*. The goal of writing a Tracery grammar is to write rules and\n", "expansions that, when followed by the computer, produce interesting (funny,\n", "insightful, poetic) text. The word for generating a text from a grammar is\n", "\"expand\"---we'll be talking a lot below about \"expanding\" the grammar into a\n", "text. (Hopefully the reasons for using this word will become clear!)\n", "\n", "In Python, Tracery rules and expansions are written as dictionaries, where the rules are keys and the expansions are values. Here's an example of a complete, but very boring, Tracery grammar:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "rules = {\n", " \"origin\": \"Hello, world!\"\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To generate text from this grammar, first create a Tracery `Grammar` object like so, passing the rules as the only parameter:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "grammar = tracery.Grammar(rules)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then call the `.flatten()` method of the `Grammar` object with `\"#origin#\"` as the only parameter. (I'll talk about what `#origin#` means in a second.)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Hello, world!'" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "grammar.flatten(\"#origin#\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This grammar can produce only one text: `Hello, world!`. Not very interesting,\n", "but helpful for the moment to illustrate how a grammar is put together and how to make it produce some output.\n", "\n", "Here's a Tracery grammar with two rules, written again as a dictionary, where each rule and its expansion are key/value pairs:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Hello, galaxy!'" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rules = {\n", " \"origin\": \"Hello, #noun#!\",\n", " \"noun\": \"galaxy\"\n", "}\n", "grammar = tracery.Grammar(rules)\n", "grammar.flatten(\"#origin#\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This grammar, again, can only ever produce one text: `Hello, galaxy!` But it\n", "accomplishes it in a slightly more sophisticated way. Notice in the expansion\n", "for the `origin` rule the following text:\n", "\n", " #noun#\n", "\n", "When the Tracery generator encounters text that looks like this---a word surrounded by\n", "`#` signs---it looks in the grammar for a rule with the same name as the word,\n", "and *replaces* the text with the expansion for that rule.\n", "\n", "Let's add a third rule to this grammar, just to see how it looks:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Howdy, galaxy!'" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rules = {\n", " \"origin\": \"#greeting#, #noun#!\",\n", " \"greeting\": \"Howdy\",\n", " \"noun\": \"galaxy\"\n", "}\n", "grammar = tracery.Grammar(rules)\n", "grammar.flatten(\"#origin#\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> EXERCISE: Add another rule for the punctuation at the end of the sentence, so\n", "> that the grammar produces the text \"Howdy, galaxy?\"\n", "\n", "## Adding alternatives\n", "\n", "The examples above are really boring, because they can only ever produce one\n", "output. In order for a grammar to be able to produce different outputs, we need\n", "to make the expansions of our rules have *alternatives* for the computer to\n", "choose between. Rules with alternatives look like this:\n", "\n", " \"rule\": [\"alternative one\", \"alternative two\", \"alternative three\"]\n", "\n", "That is: the value of the rule is a *list* of strings (instead of an individual string). When Tracery expands a rule whose value is a list, it will select one item from the list at random.\n", "\n", "Here's our \"Hello, world!\" grammar, now with multiple alternatives for what\n", "we're greeting:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Howdy, galaxy!'" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rules = {\n", " \"origin\": \"#greeting#, #noun#!\",\n", " \"greeting\": \"Howdy\",\n", " \"noun\": [\"world\", \"solar system\", \"galaxy\", \"local cluster\", \"universe\"]\n", "}\n", "grammar = tracery.Grammar(rules)\n", "grammar.flatten(\"#origin#\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Run the cell over and over again and you'll see different outputs. (Sometimes\n", "it'll look like it isn't working, but that's just because the computer randomly\n", "selected the same alternative twice in a row. It can happen!)\n", "\n", "Let's make this \"Hello, world!\" example *even more interesting* by adding\n", "alternatives for the `greeting` rule:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Hello, local cluster!'" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rules = {\n", " \"origin\": \"#greeting#, #noun#!\",\n", " \"greeting\": [\"Howdy\", \"Hello\", \"Greetings\", \"What's up\", \"Hey\", \"Hi\"],\n", " \"noun\": [\"world\", \"solar system\", \"galaxy\", \"local cluster\", \"universe\"]\n", "}\n", "grammar = tracery.Grammar(rules)\n", "grammar.flatten(\"#origin#\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sometimes for debugging purposes, it's nice to generate multiple outputs from the same grammar in one cell execution. To do this, `print()` the value of the `.flatten()` function in a `for` loop. (You don't have to re-create the `Grammar` object each time.)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "What's up, world!\n", "Greetings, solar system!\n", "Hey, local cluster!\n", "Howdy, local cluster!\n", "What's up, universe!\n" ] } ], "source": [ "rules = {\n", " \"origin\": \"#greeting#, #noun#!\",\n", " \"greeting\": [\"Howdy\", \"Hello\", \"Greetings\", \"What's up\", \"Hey\", \"Hi\"],\n", " \"noun\": [\"world\", \"solar system\", \"galaxy\", \"local cluster\", \"universe\"]\n", "}\n", "grammar = tracery.Grammar(rules)\n", "for i in range(5):\n", " print(grammar.flatten(\"#origin#\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember that in Python you can format dictionaries and lists with some flexibility. For example, your grammar might be a bit more readable if you write each option on a separate line:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"What's up, local cluster!\"" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rules = {\n", " \"origin\": \"#greeting#, #noun#!\",\n", " \"greeting\": [\n", " \"Howdy\",\n", " \"Hello\",\n", " \"Greetings\",\n", " \"What's up\",\n", " \"Hey\",\n", " \"Hi\"\n", " ],\n", " \"noun\": [\n", " \"world\",\n", " \"solar system\",\n", " \"galaxy\",\n", " \"local cluster\",\n", " \"universe\"\n", " ]\n", "}\n", "grammar = tracery.Grammar(rules)\n", "grammar.flatten(\"#origin#\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You don't always have to write the expansions as string literals and list literals. You can use a variable with a list assigned to it, for example—this is especially helpful if you have a long list of things that you plan to use in multiple grammars, or if you want to get the list of things from another source (e.g., a text file)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Modifiers\n", "\n", "Let's make a more sophisticated grammar that produces sentences in the format\n", "\"Dammit Jim, I'm a X, not a Y!\" popularized by the ground-breaking science\n", "fiction program, *Star Trek*. I happen to have a list of professions, which I'm going to put into a variable here. (I got this list from Darius Kazemi's [Corpora Project](https://github.com/dariusk/corpora/tree/master/data)—an excellent place to find lists of things. And they're already preformatted in a way that makes it easy to cut-and-paste them into your Tracery grammars.)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "professions = [\n", " \"accountant\",\n", " \"actor\",\n", " \"archeologist\",\n", " \"astronomer\",\n", " \"audiologist\",\n", " \"bartender\",\n", " \"curator\",\n", " \"detective\",\n", " \"economist\",\n", " \"editor\",\n", " \"engineer\",\n", " \"epidemiologist\",\n", " \"farmer\",\n", " \"flight attendant\",\n", " \"forest fire prevention specialist\",\n", " \"graphic designer\",\n", " \"hydrologist\",\n", " \"librarian\",\n", " \"mathematician\",\n", " \"middle school teacher\",\n", " \"nutritionist\",\n", " \"painter\",\n", " \"rancher\",\n", " \"referee\",\n", " \"reporter\",\n", " \"sailor\",\n", " \"sociologist\",\n", " \"stonemason\",\n", " \"surgeon\",\n", " \"tailor\",\n", " \"taxi driver\",\n", " \"teacher\",\n", " \"therapist\",\n", " \"tour guide\",\n", " \"umpire\",\n", " \"undertaker\",\n", " \"urban planner\",\n", " \"veterinarian\",\n", " \"web developer\",\n", " \"welder\",\n", " \"writer\",\n", " \"zoologist\"\n", "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The grammar for generating our Star Trek phrase might look like this:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"congratulations, George! I'm a graphic designer, not a audiologist!\"" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rules = {\n", " \"origin\": \"#interjection#, #name#! I'm a #profession#, not a #profession#!\",\n", " \"interjection\": [\"alas\", \"congratulations\", \"eureka\", \"fiddlesticks\",\n", " \"good grief\", \"hallelujah\", \"oops\", \"rats\", \"thanks\", \"whoa\", \"yes\"],\n", " \"name\": [\"Jim\", \"John\", \"Tom\", \"Steve\", \"Kevin\", \"Gary\", \"George\", \"Larry\"],\n", " \"profession\": professions\n", "}\n", "grammar = tracery.Grammar(rules)\n", "grammar.flatten(\"#origin#\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is pretty good, but there are problems. The first is that we typed in all\n", "of the interjections in lower case, but they're supposed to have the first\n", "letter capitalized (since they're at the beginning of the sentence). The second\n", "problem is that the grammar occasionally produces something like\n", "\n", " yes, George! I'm a economist, not a zoologist!\n", "\n", "\"A economist\" isn't right. It should be \"a*n* economist.\" English indefinite\n", "articles are tricky that way!\n", "\n", "There are several ways to solve these problems. We could just change all of our\n", "interjections to be capitalized, and add the appropriate article to the\n", "beginning of each profession. But (1) this will be time consuming and (2) it\n", "means that we won't ever be able to *re-use* those same rules with the\n", "unmodified versions of those rules. What to do?\n", "\n", "Thankfully, Tracery comes equipped with a series of *modifiers* that take the expansion of a rule and apply a transformation to it. The modifiers are included with pytracery, but they're in a separate module, so you need to import them in their own import statement:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "from tracery.modifiers import base_english" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And then you have to explicitly \"add\" them to the `Grammar` object after you create it, like so:\n", "\n", " grammar = tracery.Grammar(rules)\n", " grammar.add_modifiers(base_english)\n", "\n", "The two modifiers we're going to use are `.a`, which adds the appropriate indefinite article before the expansion of a rule, and `.capitalize`, which capitalizes the first letter of the expansion.\n", "\n", "Use the modifers by adding `.a` inside the `#` signs, *right after* the name of the rule. For example, change:\n", "\n", " #interjection#\n", "\n", "to\n", "\n", " #interjection.capitalize#\n", "\n", "Here's our \"Dammit Jim\" generator with the modifiers in place:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"Whoa, Kevin! I'm a flight attendant, not an editor!\"" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rules = {\n", " \"origin\": \"#interjection.capitalize#, #name#! I'm #profession.a#, not #profession.a#!\",\n", " \"interjection\": [\"alas\", \"congratulations\", \"eureka\", \"fiddlesticks\",\n", " \"good grief\", \"hallelujah\", \"oops\", \"rats\", \"thanks\", \"whoa\", \"yes\"],\n", " \"name\": [\"Jim\", \"John\", \"Tom\", \"Steve\", \"Kevin\", \"Gary\", \"George\", \"Larry\"],\n", " \"profession\": professions\n", "}\n", "grammar = tracery.Grammar(rules)\n", "grammar.add_modifiers(base_english)\n", "grammar.flatten(\"#origin#\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Nice! Another modifier you can use is `.s`, which turns the text in the expansion into its plural version. Using this, we can modify the above example to be a *Star Wars* meme instead of a *Star Trek* one:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"These aren't the librarians we're looking for.\"" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rules = {\n", " \"origin\": \"These aren't the #profession.s# we're looking for.\",\n", " \"profession\": professions\n", "}\n", "grammar = tracery.Grammar(rules)\n", "grammar.add_modifiers(base_english)\n", "grammar.flatten(\"#origin#\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The `origin` rule\n", "\n", "By convention, the \"starting\" rule of Tracery grammars is called `origin`. A lot of tools that use Tracery grammars follow this convention, and for ease of interoperability it's probably best if you do too. But you can actually use any name you want, as long as you use that name in the call to `.flatten()`. For example, we could rewrite the above example like so:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"These aren't the referees we're looking for.\"" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rules = {\n", " \"origin\": \"These aren't the #profession.s# we're looking for.\",\n", " \"profession\": professions\n", "}\n", "grammar = tracery.Grammar(rules)\n", "grammar.add_modifiers(base_english)\n", "grammar.flatten(\"#origin#\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Like any other rule, the \"starting\" rule can have multiple options. We could use this to, for example, create a grammar that outputs Star Wars memes half the time and Star Trek memes the other half:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "These aren't the audiologists we're looking for.\n", "Good grief, Larry! I'm a graphic designer, not a tour guide!\n", "Alas, George! I'm an audiologist, not an audiologist!\n", "These aren't the detectives we're looking for.\n", "Hallelujah, George! I'm a tailor, not a mathematician!\n", "These aren't the zoologists we're looking for.\n", "These aren't the hydrologists we're looking for.\n", "Eureka, Jim! I'm a graphic designer, not a rancher!\n", "These aren't the zoologists we're looking for.\n", "These aren't the bartenders we're looking for.\n" ] } ], "source": [ "rules = {\n", " \"origin\": [\"#interjection.capitalize#, #name#! I'm #profession.a#, not #profession.a#!\",\n", " \"These aren't the #profession.s# we're looking for.\"],\n", " \"interjection\": [\"alas\", \"congratulations\", \"eureka\", \"fiddlesticks\",\n", " \"good grief\", \"hallelujah\", \"oops\", \"rats\", \"thanks\", \"whoa\", \"yes\"],\n", " \"name\": [\"Jim\", \"John\", \"Tom\", \"Steve\", \"Kevin\", \"Gary\", \"George\", \"Larry\"],\n", " \"profession\": professions\n", "}\n", "grammar = tracery.Grammar(rules)\n", "grammar.add_modifiers(base_english)\n", "for i in range(10):\n", " print(grammar.flatten(\"#origin#\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Rules within rules within rules\n", "\n", "The grammars we've written together so far have replacement syntax (`#somethinglikethis#`) only in the expansions for the `origin` rule. But you can include that syntax in any expansion you want! This is a powerful tool for building sophisticated grammars that are built up from reusable parts. For example, this tiny model of English reuses the `noun` and `verb` rules in multiple places, thereby preventing repetition and increasing expressiveness." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A dichotomy worries against a dichotomy.\n", "An amoeba awakens the dichotomy that opens an amoeba.\n", "The corsage that worries ripens on a trombone.\n", "A trombone melts over an amoeba.\n", "The restaurant that ripens turns.\n", "The seagull on the seagull over the trombone on the trombone closes on the suburb on an amoeba.\n", "The restaurant that gathers the corsage unfurls.\n", "An amoeba bends a dichotomy.\n", "A corsage fails on the seagull that worries on the amoeba.\n", "The trombone that sways against a suburb fails.\n" ] } ], "source": [ "rules = {\n", " \"origin\": \"#nounphrase.capitalize# #verbphrase#.\",\n", " \"nounphrase\": [\"the #noun#\", \"the #noun#\", \"#noun.a#\", \"#noun.a#\", \"the #noun# that #verbphrase#\",\n", " \"the #noun# #prep# #nounphrase#\"],\n", " \"verbphrase\": [\"#verb#\", \"#verb# #nounphrase#\", \"#verb# #prep# #nounphrase#\"],\n", " \"noun\": [\"amoeba\", \"dichotomy\", \"seagull\", \"trombone\", \"corsage\", \"restaurant\", \"suburb\"],\n", " \"verb\": [\"awakens\", \"bends\", \"burns\", \"closes\", \"expands\", \"fails\", \"fractures\", \"gathers\",\n", " \"melts\", \"opens\", \"ripens\", \"scatters\", \"stops\", \"sways\", \"turns\", \"unfurls\", \"worries\"],\n", " \"prep\": [\"in\", \"on\", \"over\", \"against\"]\n", "}\n", "grammar = tracery.Grammar(rules)\n", "grammar.add_modifiers(base_english)\n", "for i in range(10):\n", " print(grammar.flatten(\"#origin#\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Loading alternatives from an external source\n", "\n", "All of the examples we've looked so far in this notebook have used literal strings and lists as the expansions for rules (i.e., the values for keys in the grammar dictionary). If you're using Tracery in Python, there are some techniques you can use for loading expansions from external data sources instead. This is a good option if you have a large number of alternatives for a particular expansion.\n", "\n", "Let's say that you have a text file which has one alternative per line, like [this list of adjectives from my plaintext example files repository](https://github.com/aparrish/plaintext-example-files/blob/master/adjs.txt). To use this, first download the file into the same directory as this notebook. Then execute the following cell to load the file in as a list, with one element per line in the file:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "adjs = open(\"adjs.txt\").read().split(\"\\n\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now you have an array of adjectives. Let's take a peek inside to make sure we've loaded the file correctly." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['bolstered',\n", " 'bonnie',\n", " 'bored',\n", " 'boundary',\n", " 'bounded',\n", " 'bounding',\n", " 'branched',\n", " 'brawling',\n", " 'brazen',\n", " 'breeding']" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "adjs[100:110]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Having loaded in this list, you can now use it as the expansion for a rule. To do this, put the variable name of the list as the rule expansion in the grammar. Here, I've adapted the code from the grammar above to incorporate a new `adj` rule, whose expansion is the list of adjectives. I've also added references to the `adj` rule in various expansions for the `nounphrase` and `verbphrase` rules:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The floral suburb is uninvited.\n", "A hands-off seagull is arrested.\n", "The seagull burns.\n", "The dichotomy in the suburb that ripens the suburb that closes on the trombone on a dichotomy scatters on a layered seagull.\n", "A mated amoeba melts a nonsense corsage.\n", "A seagull awakens against a voluptuous corsage.\n", "The trombone on the dichotomy burns.\n", "A graven suburb burns the trombone over the corsage against the medical trombone.\n", "An uncooperative amoeba awakens.\n", "The trombone on the dichotomy against the seagull against the dichotomy against the intern corsage is robust.\n" ] } ], "source": [ "rules = {\n", " \"origin\": \"#nounphrase.capitalize# #verbphrase#.\",\n", " \"nounphrase\": [\"the #noun#\", \"the #adj# #noun#\", \"#noun.a#\", \"#adj.a# #noun#\", \"the #noun# that #verbphrase#\",\n", " \"the #noun# #prep# #nounphrase#\"],\n", " \"verbphrase\": [\"#verb#\", \"#verb# #nounphrase#\", \"#verb# #prep# #nounphrase#\", \"is #adj#\"],\n", " \"noun\": [\"amoeba\", \"dichotomy\", \"seagull\", \"trombone\", \"corsage\", \"restaurant\", \"suburb\"],\n", " \"verb\": [\"awakens\", \"bends\", \"burns\", \"closes\", \"expands\", \"fails\", \"fractures\", \"gathers\",\n", " \"melts\", \"opens\", \"ripens\", \"scatters\", \"stops\", \"sways\", \"turns\", \"unfurls\", \"worries\"],\n", " \"prep\": [\"in\", \"on\", \"over\", \"against\"],\n", " \"adj\": adjs\n", "}\n", "grammar = tracery.Grammar(rules)\n", "grammar.add_modifiers(base_english)\n", "for i in range(10):\n", " print(grammar.flatten(\"#origin#\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To do this for other rules, copy the line of code above that loads in the adjectives, and change the filename to a different file with one entry per line. (Also make sure to make a different variable name!)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Next steps\n", "\n", "Congratulations, you now know the basics of writing a Tracery grammar and how to use them in Python.\n", "\n", "Tracery has a number of features that we didn't go into here, including the\n", "ability to *save* the output of a rule to be re-used later in the same\n", "expansion. See [Kate Compton's tutorial](http://cheapbotsdonequick.com/)\n", "for more information. You might be interested in [these advanced text generators](http://www.brightspiral.com/) that Kate Compton made with Tracery.\n", "\n", "If you're a Javascript programmer and you want to incorporate Tracery into your\n", "own projects, [the source code is available\n", "here](https://github.com/galaxykate/tracery) (also available as [a Node module](https://github.com/v21/tracery))." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 2 }