{"id":18026392,"url":"https://github.com/benziahamed/tracery","last_synced_at":"2025-03-27T01:31:11.607Z","repository":{"id":56924446,"uuid":"84650793","full_name":"BenziAhamed/Tracery","owner":"BenziAhamed","description":"Powerful extensible content generation library inspired by Tracery.io","archived":false,"fork":false,"pushed_at":"2020-04-24T05:42:32.000Z","size":1055,"stargazers_count":48,"open_issues_count":1,"forks_count":11,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-22T21:05:19.930Z","etag":null,"topics":["content-generation","content-generator","swift","tracery","tracery-engine","tracery-grammar"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BenziAhamed.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-03-11T13:10:48.000Z","updated_at":"2022-11-29T19:59:03.000Z","dependencies_parsed_at":"2022-08-20T22:50:24.927Z","dependency_job_id":null,"html_url":"https://github.com/BenziAhamed/Tracery","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BenziAhamed%2FTracery","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BenziAhamed%2FTracery/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BenziAhamed%2FTracery/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BenziAhamed%2FTracery/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BenziAhamed","download_url":"https://codeload.github.com/BenziAhamed/Tracery/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245764647,"owners_count":20668455,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["content-generation","content-generator","swift","tracery","tracery-engine","tracery-grammar"],"created_at":"2024-10-30T08:06:35.564Z","updated_at":"2025-03-27T01:31:10.570Z","avatar_url":"https://github.com/BenziAhamed.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n \n ![Tracery - powerful content generation](https://raw.githubusercontent.com/BenziAhamed/Tracery/master/logo.png)\n \n# Contents\n- [Introduction](#introduction)\n    - [Installation](#installation)\n    - [Basic usage](#basic-usage)\n- [Tags](#tags)\n    - [Simple story](#simple-story)\n    - [Random numbers](#random-numbers)\n- [Modifiers](#modifiers)\n- [Methods](#methods)\n- [Calls](#calls)\n- [Advanced Usage](#advanced-usage)\n    - [Custom Content Selectors](#custom-content-selectors)\n        - [Pick First Item Selector](#pick-first-item-selector)\n        - [Custom Random Item Selector](#custom-random-item-selector)\n    - [Custom Candidate Provider](#custom-candidate-provider)\n        - [Weighted Distributions](#weighted-distributions)\n    - [Recursion](#recursion)\n        - [Rule Expansions](#rule-expansions)\n        - [Logging](#logging)\n        - [Chaining Evaluations](#chaining-evaluations)\n        - [Hierarchical Tag Storage](#hierarchical-tag-storage)\n    - [Control Flow](#control-flow)\n        - [if block](#if-block)\n        - [while block](#while-block)\n    - [Text Format](#text-format)\n- [Tracery Grammar](#tracery-grammar)\n- [Conclusion](#conclusion)\n \n# Introduction\n\n\n Tracery is a content generation library originally created by [@GalaxyKate](http://www.galaxykate.com/); you can find more information at [Tracery.io](http://www.tracery.io)\n \n\n This implementation, while heavily inspired by the original, has more features added.\n \n The content generation in Tracery works based on an input set of rules. The rules determine how content should be generated.\n \n## Installation\n \n### Manual\n- Clone or download this repository\n- To work with the playground, open Playgrounds/Tracery.playground\n- The project builds `iOS` and `macOS` framework targets, which can be linked to your projects\n\n### Cocoapods\nYou want to add pod 'Tracery', '~\u003e 0.0.2' similar to the following to your Podfile:\n\n```\ntarget 'MyApp' do\n  pod 'Tracery', '~\u003e 0.0.2'\nend\n```\nThen run a pod install inside your terminal, or from CocoaPods.app.\n\nAlternatively to give it a test run, run the command:\n\n`pod try Tracery`\n\n[top](#contents)\n****\n\n### Swift Package Manager\nUse Swift Package Manager support in Xcode 11 (File \u003e Swift Packages \u003e Add Package Dependency...) to add the Swift package to your targets. Or add Tracery to your Package.swift file with the following:\n\n```swift\n.package(url: \"https://github.com/BenziAhamed/Tracery.git\", from: \"0.0.2\")\n```\n\nThen, add Tracery as a dependency to your target(s):\n```swift\n.target(name: \"App\", dependencies: [..., \"Tracery\"])\n```\n\n## Basic usage\n \n\n\n\n```swift\nimport Tracery\n\n// create a new Tracery engine\n\nvar t = Tracery {[\n    \"msg\" : \"hello world\"\n]}\n\nt.expand(\"well #msg#\")\n\n// output: well hello world\n```\n\n\n\n\n We create an instance of the Tracery engine passing along a dictionary of rules. The keys to this dictionary are the rule names, and the value for each key represents the expansion of the rule.\n \n The we use Tracery to expand instances of specified rules\n\n Notice we provide as input a template string, which contains `#msg#`, that is the rule we wish to expand inside `#` marks. Tracery evaluates the template, recognizes a rule, and replaces it with its expansion.\n \n We can have multiple rules:\n\n\n```swift\nt = Tracery {[\n    \"name\": \"jack\",\n    \"age\": \"10\",\n    \"msg\": \"#name# is #age# years old\",\n]}\n\nt.expand(\"#msg#\") // jack is 10 years old\n```\n\n\n\n Notice how we specify to expand `#msg#`, which then triggers the expansion of `#name#` and `#age#` rules? Tracery can recursively expand rules until no further expansion is possible.\n \n A rule can have multiple candidates expansions.\n\n\n```swift\nt = Tracery {[\n    \"name\": [\"jack\", \"john\", \"jacob\"], // we can specify multiple values here\n    \"age\": \"10\",\n    \"msg\": \"#name# is #age# years old\",\n    ]}\n\nt.expand(\"#msg#\")\n\n// jacob is 10 years old  \u003c- name is randomly picked\n\nt.expand(\"#msg#\")\n\n// jack is 10 years old   \u003c- name is randomly picked\n\nt.expand(\"#name# #name#\")\n\n// will print out two different random names\n```\n\n\n\n In the snippet above, whenever Tracery sees the rule `#name#`, it will pick out one of the candidate values; in this example, name could be \"jack\" \"john\" or \"jacob\"\n \n This is what allows content generation. By specifying various candidates for each rule, every time `expand` is invoked yields a different result.\n \n Let us try to build a sentence based on a popular nursery rhyme.\n\n\n```swift\nt = Tracery {[\n    \"boy\" : [\"jack\", \"john\"],\n    \"girl\" : [\"jill\", \"jenny\"],\n    \"sentence\": \"#boy# and #girl# went up the hill.\"\n]}\n\nt.expand(\"#sentence#\")\n\n// output: john and jenny went up the hill\n```\n\n\n\n So we get the first part of the sentence, what if we wanted to add in a second line so that our final output becomes:\n \n \"john and jenny went up the hill, john fell down, and so did jenny too\"\n\n\n```swift\n// the following will fail\n// to produce the correct output\nt.expand(\"#boy# and #girl# went up the hill, #boy# fell down, and so did #girl#\")\n\n// sample output:\n// jack and jenny went up the hill, john fell down, and so did jill\n```\n\n\n\n The problem is that any occurence of a `#rule#` will be replaced by one of its candidate values. So when we write `#boy#` twice, it may get replaced with entirely different names.\n \n In order to remember values, we can use tags.\n \n\n[top](#contents)\n****\n\n# Tags\n \n Tags allow to persist the result of a rule expansion to a temporary variable.\n \n\n\n```swift\nt = Tracery {[\n    \"boy\" : [\"jack\", \"john\"],\n    \"girl\" : [\"jill\", \"jenny\"],\n    \"sentence\": \"[b:#boy#][g:#girl#] #b# and #g# went up the hill, #b# fell down, and so did #g#\"\n]}\n\nt.expand(\"#sentence#\")\n\n// output: jack and jill went up the hill, jack fell down, and so did jill\n```\n\n\n\n Tags are created using the format `[tagName:tagValue]`. In the above snippet we first create two tags, `b` and `g` to hold values of `boy` and `girl` names respectively. Later on we can use `#b#` and `#g#` as if they were new rules and we Tracery will recall their stored values as required for substitution.\n \n Tags can also simply contain a value, or a group of values. Tags can also appear inside `#rules#`. Tags are variable, they can be set any number of times.\n \n \n\n[top](#contents)\n****\n\n## Simple story\n \n Here is a more complex example that generates a _short_ story.\n \n\n\n```swift\nt = Tracery {[\n\n    \"name\": [\"Arjun\",\"Yuuma\",\"Darcy\",\"Mia\",\"Chiaki\",\"Izzi\",\"Azra\",\"Lina\"],\n    \"animal\": [\"unicorn\",\"raven\",\"sparrow\",\"scorpion\",\"coyote\",\"eagle\",\"owl\",\"lizard\",\"zebra\",\"duck\",\"kitten\"],\n    \"mood\": [\"vexed\",\"indignant\",\"impassioned\",\"wistful\",\"astute\",\"courteous\"],\n    \"story\": [\"#hero# traveled with her pet #heroPet#.  #hero# was never #mood#, for the #heroPet# was always too #mood#.\"],\n    \"origin\": [\"#[hero:#name#][heroPet:#animal#]story#\"]\n]}\n\nt.expand(\"#origin#\")\n\n// sample output:\n// Darcy traveled with her pet unicorn. Darcy was never vexed, for the unicorn was always too indignant.\n```\n\n\n\n \n\n[top](#contents)\n****\n\n## Random numbers\n \n Here's another example to generate a random number:\n \n\n\n```swift\nt.expand(\"[d:0,1,2,3,4,5,6,7,8,9] random 5-digit number: #d##d##d##d##d#\")\n\n// sample output:\n// random 5-digit number: 68233\n```\n\n\n\n \n In\n \n \u003e If a tag name matches a rule, the tag will take precedence and will always be evaluated.\n \n Now that we have the hang of things, we will look at rule modifiers.\n \n \n\n\n\n\n\n\n[top](#contents)\n****\n\n# Modifiers\n \n When expanding a rule, sometimes we may need to capitalize its output, or transform it in some way. The Tracery engine allows for defining rule extensions.\n \n One kind of rule extension is known as a modifier.\n \n\n\n```swift\nimport Tracery\n\nvar t = Tracery {[\n    \"city\": \"new york\"\n]}\n\n// add a bunch of modifiers\nt.add(modifier: \"caps\") { return $0.uppercased() }\nt.add(modifier: \"title\") { return $0.capitalized }\nt.add(modifier: \"reverse\") { return String($0.characters.reversed()) }\n\nt.expand(\"#city.caps#\")\n\n// output: NEW YORK\n\nt.expand(\"#city.title#\")\n\n// output: New York\n\nt.expand(\"#city.reverse#\")\n\n// output: kroy wen\n```\n\n\n\n The power of modifiers lies in the fact that they can be chained.\n\n\n```swift\nt.expand(\"#city.reverse.caps#\")\n\n// output: KROY WREN\n\nt.expand(\"There once was a man named #city.reverse.title#, who came from the city of #city.title#.\")\n// output: There once was a man named Kroy Wen, who came from the city of New York.\n```\n\n\n\n\n \n \u003e The original implementation at Tracery.io a couple of has modifiers that allows prefixing a/an to words, pluralization, caps etc. The library follows another approach and provides customization endopints so that one can add as many modifiers as required.\n \n The next rule expansion option is the ability to add custom rule methods.\n \n\n\n\n\n \n\n[top](#contents)\n****\n\n# Methods\n \n While modifiers would receive as input the current candidate value of a rule, methods can be used to define modifiers that can accept parameters.\n \n \n Methods are written and called in the same way as modifiers.\n \n\n\n```swift\nimport Tracery\n\nvar t = Tracery {[\n    \"name\": [\"Jack Hamilton\", \"Manny D'souza\", \"Rihan Khan\"]\n]}\n\nt.add(method: \"prefix\") { input, args in\n    return args[0] + input\n}\n\nt.add(method: \"suffix\") { input, args in\n    return input + args[0]\n}\n\nt.expand(\"#name.prefix(Mr. )#\") // Mr. Jack Hamilton\n```\n\n\n\n And like modifiers, they can be chained. In fact, any type of rule extension can be chained.\n\n\n```swift\nt.expand(\"#name.prefix(Mr. ).suffix( woke up tired.)#\") // Mr. Rihan Khan woke up tired.\n```\n\n\n\n\n The power of methods come from the fact that arguments to the method can themselves be rules (or tags). Tracery will expand these and  pass in the correct value to the method.\n\n\n```swift\nt = Tracery {[\n    \"count\": [1,2,3,4],\n    \"name\": [\"jack\", \"joy\", \"jason\"]\n]}\n\nt.add(method: \"repeat\") { input, args in\n    let count = Int(args[0]) ?? 1\n    return String(repeating: input, count: count)\n}\n\n// repeat a randomly selected name, a random number of times\nt.expand(\"#name.repeat(#count#)\")\n\n// repeat a tag's value 3 times\nt.expand(\"[name:benzi]#name.repeat(3)#\")\n```\n\n\n\n \n \u003e Notice how we create a tag called `name` which overrides the rule `name`. Tags always take precedence over rules.\n \n\n\n\n\n\n[top](#contents)\n****\n\n# Calls\n \n There is one more type of rule extension, which is a `call`. Unlike modifiers and methods that work with arguments, parameters and are expected to return some string value, calls do not need to do these.\n \n Calls have the same syntax as that of a modifier `#rule.call_something#`, except that they do not modify any results.\n \n Just to show how calls work, we will create one to track a rule expansion.\n \n\n\n```swift\nimport Tracery\n\nvar t = Tracery {[\n    \"f\": \"f\"\n    \"letter\" : [\"a\", \"b\", \"c\", \"d\", \"e\", \"#f.track#\"]\n]}\n\nt.add(call: \"track\") {\n    print(\"rule 'f' was expanded\")\n}\n\nt.expand(\"#letter#\")\n```\n\n\n\n \n In the code snippet above, the rule letter has 5 candidates, 4 of which are basically string values, but the fifth one is a rule. Yes, rules can be mixed in freely and can appear anywhere at all. So in this case, rule `f` can be expanded to the basic string `f`. Notice we also have added the track call.\n \n Now, whenever `letter` choose the rule `f` as a candidate for expansion, `.track` will be called.\n \n \n \u003e Rule extensions can be place on their own inside a pair `#`. For example, if we created a modifier that always adds 'yo' to its input, called it `yo`, and have a rule candidate like `#.yo#`, this evaluates to the string \"yo\"; the modifier is passed in the empty string as an input parameter since there were no rules to expand.\n \n At this point, we have pretty much covered the basics. The following sections cover more advanced topics that involved getting more control over the candidate selection process.\n \n\n\n\n\n\n[top](#contents)\n****\n\n# Advanced Usage\n \n\n[top](#contents)\n****\n\n## Custom Content Selectors\n \n We know that a rule can have multiple candidates. By default, Tracery chooses a candidate option randomly, but the selection process is guaranteed to be strictly uniform. \n \n \n That is to say, if there was a rule with 5 options, and that rule was evaluated 100 times, each of those 5 options would have been selected exactly 20 times.\n \n This is easy to demonstrate:\n \n\n\n\n```swift\nimport Tracery\n\nvar t = Tracery {[\n    \"option\": [ \"a\", \"b\", \"c\", \"d\", \"e\" ]\n]}\n\nvar tracker = [String: Int]()\n\nt.add(modifier: \"track\") { input in\n    let count = tracker[input] ?? 0\n    tracker[input] = count + 1\n    return input\n}\n\nfunc runOptionRule(times: Int, header: String) {\n    tracker.removeAll()\n    for _ in 0..\u003ctimes {\n        _ = t.expand(\"#option.track#\")\n    }\n    let sep = String(repeating: \"-\", count: header.characters.count)\n    print(sep)\n    print(header)\n    print(sep)\n    tracker.forEach {\n        print($0.key, $0.value)\n    }\n}\n\nrunOptionRule(times: 100, header: \"default\")\n    \n\n// output will be\n\n// b 20\n// e 20\n// a 20\n// d 20\n// c 20\n```\n\n\n\n\n This is all well and good, and the default implementation might be enough for most cases. However, you may come across the requirement to support deterministic selection of candidates for a rule. For e.g. you may wish to select candidates in sequence, or always pick the first available candidate, or use some pseudo-random generator to pick values for repeatability.\n \n To support these cases and more, Tracery provides the option to specify custom content selectors for each rule.\n \n \n### Pick First Item Selector\n \n Let us look at a simple example.\n \n\n\n\n```swift\n// create a selector that always picks the first\n// item from the available items\nclass AlwaysPickFirst : RuleCandidateSelector {\n    func pick(count: Int) -\u003e Int {\n        return 0\n    }\n}\n\n// attach this new selector to rule: option\nt.setCandidateSelector(rule: \"option\", selector: AlwaysPickFirst())\n\nrunOptionRule(times: 100, header: \"pick first\")\n\n// output will be:\n// a 100\n```\n\n\n\n \n As you can see, only `a` was selected. \n \n### Custom Random Item Selector\n \n For another example, let's create a custom random selector.\n \n\n\n```swift\nclass Arc4RandomSelector : RuleCandidateSelector {\n    func pick(count: Int) -\u003e Int {\n        return Int(arc4random_uniform(UInt32(count)))\n    }\n}\n\nt.setCandidateSelector(rule: \"option\", selector: Arc4RandomSelector())\n\n// do a new dry run\nrunOptionRule(times: 100, header: \"arc4 random\")\n\n// sample output, will vary when you try\n// b 18\n// e 25\n// a 20\n// d 15\n// c 22\n```\n\n\n\n\n Notice how the distribution of value selection changes when using `arc4random_uniform`. As the number of runs increases over time, `arc4random_uniform` will tend towards a uniform distribution, unlike the default implementation in Tracery, which even with 5 runs guarantees all 5 options are picked once.\n \n\n\n```swift\nt = Tracery {[\n    \"option\": [ \"a\", \"b\", \"c\", \"d\", \"e\" ]\n]}\n\nt.add(modifier: \"track\") { input in\n    let count = tracker[input] ?? 0\n    tracker[input] = count + 1\n    return input\n}\n\nrunOptionRule(times: 5, header: \"default\")\n\n// output will be\n// b 1\n// e 1\n// a 1\n// d 1\n// c 1\n```\n\n\n\n \n Now that we know fairly well how content selection works for a given rule, let us tackle the problem of weighted distributions.\n \n Say we need a particular candidate to be chosen 5 times more often than another candidate.\n \n One way of specifying this would be as follows:\n \n\n\n```swift\nt = Tracery {[\n    \"option\": [ \"a\", \"a\", \"a\", \"a\", \"a\", \"b\" ]\n    ]}\n\nt.add(modifier: \"track\") { input in\n    let count = tracker[input] ?? 0\n    tracker[input] = count + 1\n    return input\n}\n\nrunOptionRule(times: 100, header: \"default - weighted\")\n\n// sample output, will vary over runs\n// b 17 ~\u003e 20% of 100\n// a 83 ~\u003e 80% of 100, i.e. 5 times 20\n```\n\n\n\n \n This may work out for simple cases, but if you have more candidates, and more complex weight distribution rules, things can get messy quite quick.\n \n In order to provide more flexibility over candidate representation, Tracery allows custom candidate providers.\n \n\n[top](#contents)\n****\n\n## Custom Candidate Provider\n \n### Weighted Distributions\n \n\n\n\n\n```swift\n// This class implements two protocols\n// RuleCandidateSelector - which as we have seen before is used to\n//                         to select content in a custom way\n// RuleCandidatesProvider - the protocol which needs to be\n//                          adhered to to provide customised content\nclass ExampleWeightedCandidateSet : RuleCandidatesProvider, RuleCandidateSelector {\n    \n    // required for RuleCandidatesProvider\n    let candidates: [String]\n    \n    let runningWeights: [(total:Int, target:Int)]\n    let totalWeights: UInt32\n    \n    init(_ distribution:[String:Int]) {\n        distribution.values.map { $0 }.forEach {\n            assert($0 \u003e 0, \"weights must be positive\")\n        }\n        let weightedCandidates = distribution\n            .map { ($0, $1) }\n        candidates = weightedCandidates\n            .map { $0.0 }\n        runningWeights = weightedCandidates\n            .map { $0.1 }\n            .scan(0, +)\n            .enumerated()\n            .map { ($0.element, $0.offset) }\n        totalWeights = distribution\n            .values\n            .map { $0 }\n            .reduce(0) { $0.0 + UInt32($0.1) }\n    }\n    \n    // required for RuleCandidateSelector\n    func pick(count: Int) -\u003e Int {\n        let choice = Int(arc4random_uniform(totalWeights) + 1) // since running weight start at 1\n        for weight in runningWeights {\n            if choice \u003c= weight.total {\n                return weight.target\n            }\n        }\n        // we will never reach here\n        fatalError()\n    }\n    \n}\n\nt = Tracery {[\n    \"option\": ExampleWeightedCandidateSet([\"a\": 5, \"b\": 1])\n]}\n\nt.add(modifier: \"track\") { input in\n    let count = tracker[input] ?? 0\n    tracker[input] = count + 1\n    return input\n}\n\nrunOptionRule(times: 100, header: \"custom weighted\")\n\n// sample output, will vary by run\n// b 13\n// a 87\n// as before, option b is 5 times\n// more likely to chosen over a\n```\n\n\n\n By providing a custom cadidate provider as the expansion to a rule, we can get total control over listing candidates. In this implementation, instead of repeating `a` 5 times as before, we just need to specify `a` once, along with its intended weight for the overall distribution.\n \n This provides a very powerful mechanism to control what candidates are available for a rule to expand on. For example, you could write a custom cadidate provider that presents 50 candidates based on whether an external condition is met, or 100 otherwise. The possibilities are endless.\n \n In summary\n \n * Use _modifiers_ and _methods_ to modify the expanded rule\n * Custom *candidate selectors* can be specified at a rule level to control how rules get expanded\n * Using a custom *candidates provider* can give you more control over the expansion possibilities of a rule\n \n\n\n\n\n \n\n[top](#contents)\n****\n\n## Recursion\n\n### Rule Expansions\n \n It is possible to define recursive rules. When doing so, you must provide at least one rule candidate that exits out of the recursion.\n \n\n\n```swift\nimport Tracery\n\n// suppose we wish to generate a random binary string\n// if we write the rules as\n\nvar t = Tracery {[\n    \"binary\": [ \"0 #binary#\", \"1 #binary#\" ]\n]}\n\nt.expand(\"#binary#\")\n\n\n// will output:\n// ⛔️ stack overflow\n\n// Since there is no option to exit out of the rule expansion\n// We can add one explicitly\n\nt = Tracery {[\n    \"binary\": [ \"0#binary#\", \"1#binary#\", \"\" ]\n]}\n\nprint(t.expand(\"attempt 2: #binary#\"))\n\n// while this works, if we run this a couple of times\n// you will notice that the output is as follows:\n\n// all possible outputs:\n// attempt 2: 1\n// attempt 2: 0\n// attempt 2: 01\n// attempt 2: 10\n// attempt 2:       \u003c- empty\n\n// all possible outputs are limited because the built-in\n// candidate selector is guaranteed to quickly select the \"\"\n// candidate to maintain a strict uniform distribution\n// we can fix this\n\nt = Tracery {[\n    \"binary\": WeightedCandidateSet([\n        \"0#binary#\": 10,\n        \"1#binary#\": 10,\n                 \"\":  1\n    ])\n]}\n\nprint(t.expand(\"attempt 3: #binary#\"))\n\n// sample outputs:\n// attempt 3: 011101000010100001010\n// attempt 3: 1010110\n// attempt 3: 10101100101   and so on\n\n// Now we have more control, as we are stating that we are 20 times more\n// likely to continue with a binary rule than the exit\n```\n\n\n\n \n If you wish to have a random sequence of a speicific length, you may want to create a custom `RuleCandidateSelector`, or write up/generate a non-recursive set of rules.\n \n \u003e You can control how deep recursive rules can get expanded by changing the `Tracery.maxStackDepth` property.\n \n \n### Logging\n \n You can control logging behaviour by changing the `Tracery.logLevel` property.\n \n\n\n```swift\nTracery.logLevel = .verbose\n\n\nt = Tracery {[\n    \"binary\": WeightedCandidateSet([\n        \"0#binary#\": 10,\n        \"1#binary#\": 10,\n        \"\":  1\n        ])\n    ]}\n\nprint(t.expand(\"attempt 3: #binary#\"))\n\n// sample output:\n// attempt 3: 101010100011001001011\n// attempt 3: 001001011111\n// attempt 3: 1110010111121111\n// attempt 3: 10\n\n// this will print the entire trace that Tracery generates, you will see detailed output regarding rule validation, parsing, rule expansion - useful to debug and understand how rules are processed.\n```\n\n\n\n\n \n The available logging options are:\n \n* `none`\n* `errors` - prints any parsing errors (default)\n* `warnings` - prints warnings, e.g. highlights recursive rules, cylic references, possibly invalid rules, etc\n* `info` - prints informational messages, e.g. what state the tracery engine is in\n* `verbose` - prints trace level messages, you can get detailed notes on how the engine parses text, and evaluates rules\n \n \n### Chaining Evaluations\n \n Consider the following example:\n \n\n\n```swift\nt = Tracery {[\n    \"b\" : [\"0\", \"1\"],\n    \"num\": \"#b##b#\",\n    \"10\": \"one_zero\",\n    \"00\": \"zero_zero\",\n    \"01\": \"zero_one\",\n    \"11\": \"one_one\",\n]}\n\nt.expand(\"#num#\")\n\n// will print either 01, 10, 11 or 10\n\nt.add(modifier: \"eval\") { [unowned t] input in\n    // treat input as a rule and expand it\n    return t.expand(\"#\\(input)#\")\n}\n\nt.expand(\"#num.eval#\")\n\n// will now print one_zero, zero_zero, zero_one or one_one\n```\n\n\n\n \n We now have a mechanism to expand a rule based on the expansion results of another rule.\n \n### Hierarchical Tag Storage\n \n By default, tags have global scope. This means that a tag can be set anywhere and its value will be accessible by any rule at any level of rule expansion. We can restrict tag access using hierarchical storage.\n \n Rules are expandede using a stack. Each rule evaluation occurs at a specific depth on the stack. If a rule at level `n` expands to two sub-rules, the two sub-rules will be evaluated at level `n+1`. A tag's level `n` will be the same as that of the rule at level `n` which created it.\n \n When a rule at level `n` tries to expand a tag, `Tracery` will check if a tag exists at level `n`, or search levels n-1,...0 until its able to find a value.\n \n \n In the example below, we use hierarchical storage to push and pop matching open and close braces, at various levels of rule expansion. The matching close brace is _remembered_ when the rule sub-expansion finishes.\n \n\n\n```swift\nlet options = TraceryOptions()\noptions.tagStorageType = .heirarchical\n\nlet braceTypes = [\"()\",\"{}\",\"\u003c\u003e\",\"«»\",\"𛰫𛰬\",\"⌜⌝\",\"ᙅᙂ\",\"ᙦᙣ\",\"⁅⁆\",\"⌈⌉\",\"⌊⌋\",\"⟦⟧\",\"⦃⦄\",\"⦗⦘\",\"⫷⫸\"]\n    .map { braces -\u003e String in\n        let open = braces[braces.startIndex]\n        let close = braces[braces.index(after: braces.startIndex)]\n        return \"[open:\\(open)][close:\\(close)]\"\n}\n\nlet h = Tracery(options) {[\n    \"letter\": [\"A\",\"B\",\"C\",\"D\",\"E\",\"F\",\"G\",\"H\",\"I\",\"J\",\"K\",\"L\",\"M\",\"N\",\"O\",\"P\"],\n    \"bracetypes\": braceTypes,\n    \"brace\": [\n        // open with current symbol, \n        // create new symbol, open brace pair and evaluate a sub-rule call to brace\n        // finally close off with orginal symbol and matching close brace pair\n        \"#open##symbol# #origin##symbol##close# \",\n        \n        \"#open##symbol# #origin##symbol##close# #origin#\",\n\n        // exits recursion\n        \"\",\n    ],\n    \n    // start with a symbol and bracetype\n    \"origin\": [\"#[symbol:#letter#][#bracetypes#]brace#\"]\n]}\n\nh.expand(\"#origin#\")\n\n// sample outputs:\n// {L ⌜D D⌝ (P 𛰫O O𛰬 \u003cF ⦃C C⦄ F\u003e P) L}\n// ⁅M ᙅK Kᙂ ᙦE {O O} Eᙣ M⁆\n// ⌈C C⌉\n// \u003cK K\u003e\n\n```\n\n\n\n\n \n\n[top](#contents)\n****\n\n## Control Flow\n \n### if block\n \n If blocks are supported. You can use if blocks to check if a rule matches a condition, and based on that, output different content. The format is `[if condition then rule (else rule)]`. The `else` part is optional.\n \n A condition is expressed as: `rule condition_operator rule`. Both the left hand side and right hand side `rule`s are expanded, and their ouput is checked based on the `condition operator` specified.\n \n The following conditional operators are permitted:\n \n - `==` check if LHS equals RHS after expansion\n - `!=` check if LHS does not equal RHS after expansion\n - `in` check if LHS expanded is contained in RHS's expansion candidates\n - `not in` check if LHS expanded is not contained in RHS's expansion candidates\n \n \n\n\n```swift\nimport Foundation\nimport Tracery\n\n\nvar t = Tracery {[\n    \n    \"digit\" : [0,1,2,3,4,5,6,7,8,9],\n    \"binary\": [0,1],\n    \"is_binary\": \"is binary\",\n    \"not_binary\": \"is not binary\",\n    \n    // check if generated digit is binary\n    \"msg_if_binary\" : \"[d:#digit#][if #d# in #binary# then #d# #is_binary# else #d# #not_binary#]\",\n    \n    // ouput only if generated digit is zero\n    \"msg_if_zero\" : \"[d:#digit#][if #d# == 0 then #d# zero]\"\n]}\n\nt.expand(\"#msg_if_binary#\")\nt.expand(\"#msg_if_zero#\")\n```\n\n\n\n### while block\n \n While blocks can be used to create loops. It takes the form `[while condition do rule]`. As long as the `condition` evaluates to true, the `rule` specified in the `do` section gets expanded.\n \n\n\n\n```swift\n// print out a number that does not contain digits 0 or 1\nt.expand(\"[while #[d:#digit#]d# not in #binary# do #d#]\")\n```\n\n\n\n\n \n\n[top](#contents)\n****\n\n## Text Format\n \n Tracery can recognize rules defined in plain text files as well. The file must contain a set of rule definitions, with the rule specified inside square brackets, and its expansion candidates defined one per line. Here is a sample file:\n \n```\n[binary]\n0#binary#\n1#binary#\n#empty#\n \n[empty]\n```\n The above file is a basic binary number generator.\n Here's another one for fable names.\n \n```\n[fable]\n#the# #adjective# #noun#\n#the# #noun#\n#the# #noun# Who #verb# The #adjective# #noun#\n \n[the]\nThe\nThe Strange Story of The\nThe Tale of The\nA\nThe Origin of The\n \n[adjective]\nLonely\nMissing\nGreedy\nValiant\nBlind\n \n[noun]\nHare\nHound\nBeggar\nLion\nFrog\n \n[verb]\nFinds\nMeets\nTricks\nOutwits\n```\n \n This input file will generate output like:\n \n```\n A Greedy Frog\n The Beggar\n The Origin of The Hare Who Finds The Missing Lion\n The Strange Story of The Hound\n The Tale of The Blind Frog\n```\n \n You use the `Tracery.init(path:)` constructor to consume rules from a plain text file.\n \n\n\n\n\n \n\n[top](#contents)\n****\n\n# Tracery Grammar\n \n This section attempts to describe the grammar specification for Tracery.\n \n\n```\n rule_candidate -\u003e ( plain_text | rule | tag )*\n \n \n tag -\u003e [ tag_name : tag_value ]\n \n    tag_name -\u003e plain_text\n \n    tag_value -\u003e tag_value_candidate (,tag_value_candidate)*\n \n        tag_value_candidate -\u003e rule_candidate\n \n \n rule -\u003e # (tag)* | rule_name(.modifier|.call|.method)* | control_block* #\n \n    rule_name -\u003e plain_text\n \n    modifier -\u003e plain_text\n \n    call -\u003e plain_text\n \n    method -\u003e method_name ( param (,param)* )\n \n        method_name -\u003e plain_text\n \n        param -\u003e plain_text | rule\n \n \n \n control_block -\u003e if_block | while_block\n \n    condition_operator -\u003e == | != | in | not in\n \n    condition -\u003e rule condition_operator rule\n \n    if_block -\u003e [if condition then rule (else rule)]\n \n    while_block -\u003e [while condition do rule]\n \n \n \n```\n \n\n[top](#contents)\n****\n\n# Conclusion\n \n Tracery in Swift was developed by [Benzi](https://twitter.com/benziahamed).\n \n Original library in Javascript is available at [Tracery.io](http://www.tracery.io/).\n \n \n\n\n\u003e This README was auto-generated using [playme](https://github.com/BenziAhamed/playme)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenziahamed%2Ftracery","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenziahamed%2Ftracery","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenziahamed%2Ftracery/lists"}