├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - '0.10' 7 | - '0.12' 8 | - '1' 9 | - '2' 10 | - '3' 11 | - '4' 12 | - '5' 13 | - '6' 14 | - '7' 15 | 16 | matrix: 17 | include: 18 | - node_js: '0.10' 19 | env: BROWSER_NAME=chrome BROWSER_VERSION=latest 20 | - node_js: '0.10' 21 | env: BROWSER_NAME=chrome BROWSER_VERSION=29 22 | - node_js: '0.10' 23 | env: BROWSER_NAME=firefox BROWSER_VERSION=latest 24 | - node_js: '0.10' 25 | env: BROWSER_NAME=opera BROWSER_VERSION=latest 26 | - node_js: '0.10' 27 | env: BROWSER_NAME=safari BROWSER_VERSION=latest 28 | - node_js: '0.10' 29 | env: BROWSER_NAME=safari BROWSER_VERSION=7 30 | - node_js: '0.10' 31 | env: BROWSER_NAME=safari BROWSER_VERSION=6 32 | - node_js: '0.10' 33 | env: BROWSER_NAME=safari BROWSER_VERSION=5 34 | - node_js: '0.10' 35 | env: BROWSER_NAME=ie BROWSER_VERSION=11 36 | - node_js: '0.10' 37 | env: BROWSER_NAME=ie BROWSER_VERSION=10 38 | - node_js: '0.10' 39 | env: BROWSER_NAME=ie BROWSER_VERSION=9 40 | 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Next 2 | 3 | * Your contribution here. 4 | 5 | ### 0.2.1 6 | 7 | * Updated tap module to `6.1.1` - [@mreinstein](https://github.com/mreinstein). 8 | 9 | ### 0.2.0 10 | 11 | * [#5](https://github.com/alexa-js/alexa-utterances/pull/5): Added custom slot type support - [@MaxwellPayne](https://github.com/MaxwellPayne). 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to alexa-utterances 2 | 3 | You're encouraged to submit [pull requests](https://github.com/alexa-js/alexa-utterances/pulls), [propose features and discuss issues](https://github.com/alexa-js/alexa-utterances/issues). 4 | 5 | In the examples below, substitute your Github username for `contributor` in URLs. 6 | 7 | ### Fork the Project 8 | 9 | Fork the [project on Github](https://github.com/alexa-js/alexa-utterances) and check out your copy. 10 | 11 | ``` 12 | git clone https://github.com/contributor/alexa-utterances.git 13 | cd alexa-utterances 14 | git remote add upstream https://github.com/alexa-js/alexa-utterances.git 15 | ``` 16 | 17 | ### Run Tests 18 | 19 | Ensure that you can build the project and run tests. 20 | 21 | ``` 22 | npm install 23 | npm test 24 | ``` 25 | 26 | ## Contribute Code 27 | 28 | ### Create a Topic Branch 29 | 30 | Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. 31 | 32 | ``` 33 | git checkout master 34 | git pull upstream master 35 | git checkout -b my-feature-branch 36 | ``` 37 | 38 | ### Write Tests 39 | 40 | Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Tests live under [test](test). 41 | 42 | We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. 43 | 44 | ### Write Code 45 | 46 | Implement your feature or bug fix. 47 | 48 | Make sure that `npm test` completes without errors. 49 | 50 | ### Write Documentation 51 | 52 | Document any external behavior in the [README](README.md). 53 | 54 | ### Commit Changes 55 | 56 | Make sure git knows your name and email address: 57 | 58 | ``` 59 | git config --global user.name "Your Name" 60 | git config --global user.email "contributor@example.com" 61 | ``` 62 | 63 | Writing good commit logs is important. A commit log should describe what changed and why. 64 | 65 | ``` 66 | git add ... 67 | git commit 68 | ``` 69 | 70 | ### Push 71 | 72 | ``` 73 | git push origin my-feature-branch 74 | ``` 75 | 76 | ### Make a Pull Request 77 | 78 | Go to https://github.com/alexa-js/alexa-utterances and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. 79 | 80 | Add more commits or amend your previous commit with any changes. 81 | 82 | ``` 83 | git commit --amend 84 | git push origin my-feature-branch -f 85 | ``` 86 | 87 | ### Rebase 88 | 89 | If you've been working on a change for a while, rebase with upstream/master. 90 | 91 | ``` 92 | git fetch upstream 93 | git rebase upstream/master 94 | git push origin my-feature-branch -f 95 | ``` 96 | 97 | ### Check on Your Pull Request 98 | 99 | Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above. 100 | 101 | ### Be Patient 102 | 103 | It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there! 104 | 105 | ## Thank You 106 | 107 | Please do know that we really appreciate and value your time and work. We love you, really. 108 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2017 Mike Reinstein 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # alexa-utterances 2 | 3 | [![NPM](https://img.shields.io/npm/v/alexa-utterances.svg)](https://www.npmjs.com/package/alexa-utterances/) 4 | [![build Status](https://travis-ci.org/alexa-js/alexa-utterances.svg?branch=master)](https://travis-ci.org/alexa-js/alexa-utterances) 5 | 6 | Generate expanded Amazon Alexa utterances from a template string. 7 | 8 | When building apps for Alexa or Echo, it's important to declare many permutations of text, in order to improve the voice recognition rate. 9 | 10 | Manually generating these combinations is tedious. This module allows you to generate many (hundreds or even thousands) of sample utterances using just a few samples that get auto-expanded. Any number of sample utterances may be passed in the utterances array. Below are some sample utterances macros and what they will be expanded to. 11 | 12 | ### Usage 13 | 14 | installation: 15 | ``` 16 | npm install alexa-utterances 17 | ``` 18 | 19 | running tests: 20 | ``` 21 | npm test 22 | ``` 23 | 24 | ### API 25 | 26 | ```javascript 27 | var result = utterances(template, slots, dictionary, exhaustiveUtterances); 28 | ``` 29 | 30 | **template** a string to generate utterances from 31 | 32 | **slots** a hash of slots to fill for the given utterance 33 | 34 | **dictionary** a hash of lookup values to expand 35 | 36 | **exhaustiveUtterances** if true, builds a full cartesian product of all shortcut values and slot sample values; if false, builds a smaller list of utterances that has the full cartesian product of all shortcut values, with slot sample values filled in; default = false 37 | 38 | **result** an array of strings built from the template 39 | 40 | 41 | 42 | #### example 43 | 44 | ```javascript 45 | var dictionary = { adjustments: [ 'dim', 'brighten' ] }; 46 | var slots = { Adjustment: 'LITERAL' }; 47 | var template = '{adjustments|Adjustment} the light'; 48 | 49 | var result = utterances(template, slots, dictionary); 50 | 51 | // result: 52 | // [ '{dim|Adjustment} the light', '{brighten|Adjustment} the light' ] 53 | ``` 54 | 55 | #### slots 56 | 57 | The slots object is a simple Name:Type mapping. The type must be one of Amazon's supported slot types: LITERAL, NUMBER, DATE, TIME, DURATION. You can use custom slot types, but you cannot integrate them with the slots object here and must instead do so with an [alternate syntax](#custom-slot-types). 58 | 59 | 60 | #### Using a Dictionary 61 | 62 | Several intents may use the same list of possible values, so you want to define them in one place, not in each intent schema. Use the app's dictionary. 63 | 64 | ```javascript 65 | var dictionary = { "colors": [ "red", "green", "blue" ] }; 66 | ... 67 | "I like {colors|COLOR}" 68 | ``` 69 | 70 | #### Multiple Options mapped to a Slot 71 | ```javascript 72 | "my favorite color is {red|green|blue|NAME}" 73 | => 74 | "my favorite color is {red|NAME}" 75 | "my favorite color is {green|NAME}" 76 | "my favorite color is {blue|NAME}" 77 | ``` 78 | 79 | #### Generate Multiple Versions of Static Text 80 | 81 | This lets you define multiple ways to say a phrase, but combined into a single sample utterance 82 | 83 | ```javascript 84 | "{what is the|what's the|check the} status" 85 | => 86 | "what is the status" 87 | "what's the status" 88 | "check the status" 89 | ``` 90 | 91 | #### Auto-Generated Number Ranges 92 | 93 | When capturing a numeric slot value, it's helpful to generate many sample utterances with different number values 94 | 95 | ```javascript 96 | "buy {2-5|NUMBER} items" 97 | => 98 | "buy {two|NUMBER} items" 99 | "buy {three|NUMBER} items" 100 | "buy {four|NUMBER} items" 101 | "buy {five|NUMBER} items" 102 | ``` 103 | 104 | Number ranges can also increment in steps 105 | 106 | ```javascript 107 | "buy {5-20 by 5|NUMBER} items" 108 | => 109 | "buy {five|NUMBER} items" 110 | "buy {ten|NUMBER} items" 111 | "buy {fifteen|NUMBER} items" 112 | "buy {twenty|NUMBER} items" 113 | ``` 114 | 115 | #### Optional Words 116 | 117 | ```javascript 118 | "what is your {favorite |}color" 119 | => 120 | "what is your color" 121 | "what is your favorite color" 122 | ``` 123 | 124 | #### Custom Slot Types 125 | 126 | You may want to work with [Custom Slot Types](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/defining-the-voice-interface#custom-slot-types) registered in your interaction model. You can use a special syntax to leave a curly-braced slot name unparsed. For example, if you have defined in your skill a `FRUIT_TYPE` with the values `Apple`, `Orange` and `Lemon` for the slot `Fruit`, you can keep `Fruit` a curly-braced literal as follows 127 | 128 | ```javascript 129 | "{my|your} {favorite|least favorite} snack is {-|Fruit}" 130 | => 131 | "my favorite snack is {Fruit}" 132 | "your favorite snack is {Fruit}" 133 | "my least favorite snack is {Fruit}" 134 | "your least favorite snack is {Fruit}" 135 | ``` 136 | 137 | ### Contributing 138 | 139 | See [CONTRIBUTING](CONTRIBUTING.md) 140 | 141 | ### Copyright and License 142 | 143 | Copyright (c) 2015-2017 Mike Reinstein, MIT License, see [LICENSE](LICENSE). 144 | 145 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Combinatorics = require('js-combinatorics'); 2 | var Numbered = require('numbered'); 3 | 4 | 5 | // Util functions for generating schema and utterances 6 | // =================================================== 7 | // Convert a number range like 5-10 into an array of english words 8 | function expandNumberRange(start, end, by) { 9 | by = by || 1; //incrementing by 0 is a bad idea 10 | var converted = []; 11 | for (var i=start; i<=end; i+=by) { 12 | converted.push( Numbered.stringify(i).replace(/-/g,' ') ); 13 | } 14 | return converted; 15 | } 16 | 17 | // Determine if a curly brace expression is a Slot name literal 18 | // Returns true if expression is of the form {-|Name}, false otherwise 19 | function isSlotLiteral(braceExpression) { 20 | return braceExpression.substring(0, 3) == "{-|"; 21 | } 22 | 23 | // Recognize shortcuts in utterance definitions and swap them out with the actual values 24 | function expandShortcuts(str, slots, dictionary) { 25 | // If the string is found in the dictionary, just provide the matching values 26 | if (typeof dictionary=="object" && typeof dictionary[str]!="undefined") { 27 | return dictionary[str]; 28 | } 29 | // Numbered ranges, ex: 5-100 by 5 30 | var match = str.match(/(\d+)\s*-\s*(\d+)(\s+by\s+(\d+))?/); 31 | if (match) { 32 | return expandNumberRange(+match[1],+match[2],+match[4]); 33 | } 34 | return [str]; 35 | } 36 | 37 | var slotIndexes = []; 38 | function expandSlotValues (variations, slotSampleValues) { 39 | var i; 40 | 41 | var slot; 42 | for (slot in slotSampleValues) { 43 | 44 | var sampleValues = slotSampleValues[slot]; 45 | 46 | var idx = -1; 47 | if (typeof slotIndexes[slot] !== "undefined") { 48 | idx = slotIndexes[slot]; 49 | } 50 | 51 | var newVariations = []; 52 | 53 | // make sure we have enough variations that we can get through the sample values 54 | // at least once for each alexa-app utterance... this isn't strictly as 55 | // minimalistic as it could be. 56 | // 57 | // our *real* objective is to make sure that each sampleValue gets used once per 58 | // intent, but each intent spans multiple utterances; it would require heavy 59 | // restructuring of the way the utterances are constructed to keep track of 60 | // whether every slot was given each sample value once within an Intent's set 61 | // of utterances. So we take the easier route, which generates more utterances 62 | // in the output (but still many less than we would get if we did the full 63 | // cartesian product). 64 | if (variations.length < sampleValues.length) { 65 | var mod = variations.length; 66 | var xtraidx = 0; 67 | while (variations.length < sampleValues.length) { 68 | variations.push (variations[xtraidx]); 69 | xtraidx = (xtraidx + 1) % mod; 70 | } 71 | } 72 | 73 | variations.forEach (function (variation, j) { 74 | var newVariation = []; 75 | variation.forEach (function (value, k) { 76 | if (value == "slot-" + slot) { 77 | idx = (idx + 1) % sampleValues.length; 78 | slotIndexes[slot] = idx; 79 | 80 | value = sampleValues[idx]; 81 | } 82 | 83 | newVariation.push (value); 84 | }); 85 | newVariations.push (newVariation); 86 | }); 87 | 88 | variations = newVariations; 89 | } 90 | 91 | return variations; 92 | } 93 | 94 | // Generate a list of utterances from a template 95 | function generateUtterances(str, slots, dictionary, exhaustiveUtterances) { 96 | var placeholders=[], utterances=[], slotmap={}, slotValues=[]; 97 | // First extract sample placeholders values from the string 98 | str = str.replace(/\{([^\}]+)\}/g, function(match,p1) { 99 | 100 | if (isSlotLiteral(match)) { 101 | return match; 102 | } 103 | 104 | var expandedValues=[], slot, values = p1.split("|"); 105 | // If the last of the values is a SLOT name, we need to keep the name in the utterances 106 | if (values && values.length && values.length>1 && slots && typeof slots[values[values.length-1]]!="undefined") { 107 | slot = values.pop(); 108 | } 109 | values.forEach(function(val,i) { 110 | Array.prototype.push.apply(expandedValues,expandShortcuts(val,slots,dictionary)); 111 | }); 112 | if (slot) { 113 | slotmap[slot] = placeholders.length; 114 | } 115 | 116 | // if we're dealing with minimal utterances, we will delay the expansion of the 117 | // values for the slots; all the non-slot expansions need to be fully expanded 118 | // in the cartesian product 119 | if (!exhaustiveUtterances && slot) 120 | { 121 | placeholders.push( [ "slot-" + slot ] ); 122 | slotValues[slot] = expandedValues; 123 | } 124 | else 125 | { 126 | placeholders.push( expandedValues ); 127 | } 128 | 129 | return "{"+(slot || placeholders.length-1)+"}"; 130 | }); 131 | // Generate all possible combinations using the cartesian product 132 | if (placeholders.length>0) { 133 | var variations = Combinatorics.cartesianProduct.apply(Combinatorics,placeholders).toArray(); 134 | 135 | if (!exhaustiveUtterances) 136 | { 137 | variations = expandSlotValues (variations, slotValues); 138 | } 139 | 140 | // Substitute each combination back into the original string 141 | variations.forEach(function(values) { 142 | // Replace numeric placeholders 143 | var utterance = str.replace(/\{(\d+)\}/g,function(match,p1){ 144 | return values[p1]; 145 | }); 146 | // Replace slot placeholders 147 | utterance = utterance.replace(/\{(.*?)\}/g,function(match,p1){ 148 | return (isSlotLiteral(match)) ? match : "{"+values[slotmap[p1]]+"|"+p1+"}"; 149 | }); 150 | utterances.push( utterance ); 151 | }); 152 | } 153 | else { 154 | utterances = [str]; 155 | } 156 | 157 | // Convert all {-|Name} to {Name} to accomodate slot literals 158 | for (var idx in utterances) { 159 | utterances[idx] = utterances[idx].replace(/\{\-\|/g, "{"); 160 | } 161 | 162 | return utterances; 163 | } 164 | 165 | 166 | module.exports = generateUtterances; 167 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alexa-utterances", 3 | "version": "0.2.1", 4 | "description": "generate expanded Amazon Alexa utterances from a template string", 5 | "main": "index.js", 6 | "scripts": { 7 | "prepublish": "npm test", 8 | "test": "tap ./test" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/alexa-js/alexa-utterances.git" 13 | }, 14 | "keywords": [ 15 | "alexa", 16 | "amazon", 17 | "echo", 18 | "utterance", 19 | "intent" 20 | ], 21 | "author": "Matt Kruse (http://MattKruse.com)", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/alexa-js/alexa-utterances/issues" 25 | }, 26 | "homepage": "https://github.com/alexa-js/alexa-utterances#readme", 27 | "dependencies": { 28 | "js-combinatorics": "^0.5.0", 29 | "numbered": "^1.0.0" 30 | }, 31 | "devDependencies": { 32 | "tap": "^8.0.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var utterances = require('../'); 2 | var test = require('tap').test; 3 | 4 | 5 | test('basic usage', function (t) { 6 | var dictionary = {}; 7 | var slots = {}; 8 | var template = 'do the thing'; 9 | 10 | var result = utterances(template, slots, dictionary); 11 | t.deepEqual(result, [ 'do the thing' ]); 12 | t.end(); 13 | }); 14 | 15 | 16 | test('optional terms', function (t) { 17 | var dictionary = {}; 18 | var slots = {}; 19 | var template = 'do {it |}'; 20 | 21 | var result = utterances(template, slots, dictionary); 22 | t.deepEqual(result, [ 'do it ', 'do ' ]); 23 | t.end(); 24 | }); 25 | 26 | 27 | test('dictionary expansion', function (t) { 28 | var dictionary = { adjustments: [ 'dim', 'brighten' ] }; 29 | var slots = { Adjustment: 'LITERAL' }; 30 | var template = '{adjustments|Adjustment} the light'; 31 | 32 | var result = utterances(template, slots, dictionary); 33 | t.deepEqual(result, [ '{dim|Adjustment} the light', 34 | '{brighten|Adjustment} the light' 35 | ]); 36 | t.end(); 37 | }); 38 | 39 | 40 | test('number range expansion', function (t) { 41 | var dictionary = {}; 42 | var slots = { Brightness: 'NUMBER' }; 43 | var template = 'set brightness to {1-3|Brightness}'; 44 | 45 | var result = utterances(template, slots, dictionary); 46 | t.deepEqual(result, [ "set brightness to {one|Brightness}", 47 | "set brightness to {two|Brightness}", 48 | "set brightness to {three|Brightness}" 49 | ]); 50 | t.end(); 51 | }); 52 | 53 | test('exhaustive vs non-exhaustive expansion', function (t) { 54 | var dictionary = { "movie_names": ["star wars", "inception", "gattaca", "the matrix"] }; 55 | var slots = { "MOVIE": "LITERAL" }; 56 | var template = "{foo|bar|baz} {foo|bar|baz} {movie_names|MOVIE}"; 57 | var result = utterances(template, slots, dictionary); 58 | t.deepEqual(result, [ 59 | "foo foo {star wars|MOVIE}", 60 | "bar foo {inception|MOVIE}", 61 | "baz foo {gattaca|MOVIE}", 62 | "foo bar {the matrix|MOVIE}", 63 | "bar bar {star wars|MOVIE}", 64 | "baz bar {inception|MOVIE}", 65 | "foo baz {gattaca|MOVIE}", 66 | "bar baz {the matrix|MOVIE}", 67 | "baz baz {star wars|MOVIE}" 68 | ]); 69 | 70 | var result2 = utterances(template, slots, dictionary, true); 71 | t.deepEqual(result2, [ 72 | "foo foo {star wars|MOVIE}", 73 | "bar foo {star wars|MOVIE}", 74 | "baz foo {star wars|MOVIE}", 75 | "foo bar {star wars|MOVIE}", 76 | "bar bar {star wars|MOVIE}", 77 | "baz bar {star wars|MOVIE}", 78 | "foo baz {star wars|MOVIE}", 79 | "bar baz {star wars|MOVIE}", 80 | "baz baz {star wars|MOVIE}", 81 | "foo foo {inception|MOVIE}", 82 | "bar foo {inception|MOVIE}", 83 | "baz foo {inception|MOVIE}", 84 | "foo bar {inception|MOVIE}", 85 | "bar bar {inception|MOVIE}", 86 | "baz bar {inception|MOVIE}", 87 | "foo baz {inception|MOVIE}", 88 | "bar baz {inception|MOVIE}", 89 | "baz baz {inception|MOVIE}", 90 | "foo foo {gattaca|MOVIE}", 91 | "bar foo {gattaca|MOVIE}", 92 | "baz foo {gattaca|MOVIE}", 93 | "foo bar {gattaca|MOVIE}", 94 | "bar bar {gattaca|MOVIE}", 95 | "baz bar {gattaca|MOVIE}", 96 | "foo baz {gattaca|MOVIE}", 97 | "bar baz {gattaca|MOVIE}", 98 | "baz baz {gattaca|MOVIE}", 99 | "foo foo {the matrix|MOVIE}", 100 | "bar foo {the matrix|MOVIE}", 101 | "baz foo {the matrix|MOVIE}", 102 | "foo bar {the matrix|MOVIE}", 103 | "bar bar {the matrix|MOVIE}", 104 | "baz bar {the matrix|MOVIE}", 105 | "foo baz {the matrix|MOVIE}", 106 | "bar baz {the matrix|MOVIE}", 107 | "baz baz {the matrix|MOVIE}" 108 | ]); 109 | t.end(); 110 | }); 111 | 112 | test('raw curly braces for custom slot types', function (t) { 113 | var dictionary = {}; 114 | var slots = {"Artist": "CUSTOM_TYPE"}; 115 | var template = "{my|your} {favorite|least favorite} fruit is {-|Fruit}"; 116 | 117 | var result = utterances(template, slots, dictionary); 118 | t.deepEqual(result, [ 119 | "my favorite fruit is {Fruit}", 120 | "your favorite fruit is {Fruit}", 121 | "my least favorite fruit is {Fruit}", 122 | "your least favorite fruit is {Fruit}" 123 | ]); 124 | t.end(); 125 | }); 126 | --------------------------------------------------------------------------------