├── .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 | [](https://www.npmjs.com/package/alexa-utterances/)
4 | [](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 |
--------------------------------------------------------------------------------