alexa_generator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +38 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +2 -0
- data/Rakefile +7 -0
- data/alexa_generator.gemspec +28 -0
- data/lib/alexa_generator/intent.rb +51 -0
- data/lib/alexa_generator/intent_schema.rb +11 -0
- data/lib/alexa_generator/sample_utterance_template.rb +14 -0
- data/lib/alexa_generator/slot.rb +46 -0
- data/lib/alexa_generator/slot_binding.rb +14 -0
- data/lib/alexa_generator/version.rb +3 -0
- data/lib/alexa_generator/voice_interface.rb +105 -0
- data/lib/alexa_generator.rb +2 -0
- data/spec/alexa_generator/intent_spec.rb +37 -0
- data/spec/alexa_generator/sample_utterance_template_spec.rb +21 -0
- data/spec/alexa_generator/slot_spec.rb +30 -0
- data/spec/alexa_generator/voice_interface_spec.rb +146 -0
- data/spec/spec_helper.rb +6 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 650a7b5cb37d30de5a4feb18846c71ce321fa48e
|
4
|
+
data.tar.gz: c9b7b88fcd63212a071b6b770241cddf5bd87365
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5fa2035fe237d45fe6168855a4c0b2c3034a180d5ea42196b27fb2a69dab9c3171ac8e4a4c8bd3088a2e6214671cb88283a93f20e2a5ecb9d249ec449cc90e99
|
7
|
+
data.tar.gz: d3f16acdef5a3cf0c0c5b8f927aa1b572bad896c7ffa5e5ab6c2551dbb863b47ccafd49e257ea7666b0f78cf4b153bb2810b82c17651b21c077bbb304526f049
|
data/.gitignore
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/vendor/bundle
|
26
|
+
/lib/bundler/man/
|
27
|
+
|
28
|
+
# for a library or gem, you might want to ignore these files since the code is
|
29
|
+
# intended to run in multiple environments; otherwise, check them in:
|
30
|
+
Gemfile.lock
|
31
|
+
.ruby-version
|
32
|
+
.ruby-gemset
|
33
|
+
|
34
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
35
|
+
.rvmrc
|
36
|
+
|
37
|
+
# IntelliJ
|
38
|
+
*.iml
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Chris Mullins
|
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
|
+
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$:.push File.expand_path('../lib', __FILE__)
|
2
|
+
|
3
|
+
require "alexa_generator/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = 'alexa_generator'
|
7
|
+
gem.version = AlexaGenerator::VERSION
|
8
|
+
|
9
|
+
gem.summary = "Generates voice interfaces for Alexa apps based on templates."
|
10
|
+
|
11
|
+
gem.authors = ['Christopher Mullins']
|
12
|
+
gem.email = 'chris@sidoh.org'
|
13
|
+
gem.homepage = 'http://github.com/sidoh/alexa_generator'
|
14
|
+
|
15
|
+
gem.add_development_dependency('rspec', [">= 3.0.0"])
|
16
|
+
gem.add_development_dependency('rake')
|
17
|
+
|
18
|
+
ignores = File.readlines(".gitignore").grep(/\S+/).map(&:chomp)
|
19
|
+
dotfiles = %w[.gitignore]
|
20
|
+
|
21
|
+
all_files_without_ignores = Dir["**/*"].reject { |f|
|
22
|
+
File.directory?(f) || ignores.any? { |i| File.fnmatch(i, f) }
|
23
|
+
}
|
24
|
+
|
25
|
+
gem.files = (all_files_without_ignores + dotfiles).sort
|
26
|
+
|
27
|
+
gem.require_path = "lib"
|
28
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'alexa_generator/slot'
|
2
|
+
require 'alexa_generator/sample_utterance_template'
|
3
|
+
|
4
|
+
module AlexaGenerator
|
5
|
+
class Intent
|
6
|
+
attr_reader :name, :slots
|
7
|
+
|
8
|
+
class Builder
|
9
|
+
attr_reader :bindings, :utterance_templates
|
10
|
+
|
11
|
+
def initialize(name)
|
12
|
+
@name = name
|
13
|
+
@slots = []
|
14
|
+
@bindings = []
|
15
|
+
@utterance_templates = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_slot(name, type, &block)
|
19
|
+
builder = Slot.build(name, type, &block)
|
20
|
+
|
21
|
+
slot_bindings = builder.bindings.map { |x| SlotBinding.new(name, x) }
|
22
|
+
@bindings.concat(slot_bindings)
|
23
|
+
|
24
|
+
@slots.push(builder.create)
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_utterance_template(template)
|
28
|
+
add_utterance_templates(template)
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_utterance_templates(*templates)
|
32
|
+
templates.each { |x| @utterance_templates.push(SampleUtteranceTemplate.new(@name, x)) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def create
|
36
|
+
Intent.new(@name, @slots)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(name, slots)
|
41
|
+
@name = name
|
42
|
+
@slots = slots
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.build(name, &block)
|
46
|
+
builder = Builder.new(name)
|
47
|
+
block.call(builder)
|
48
|
+
builder
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module AlexaGenerator
|
2
|
+
class SampleUtteranceTemplate
|
3
|
+
attr_reader :intent_name, :template
|
4
|
+
|
5
|
+
def initialize(intent_name, template)
|
6
|
+
@intent_name = intent_name
|
7
|
+
@template = template
|
8
|
+
end
|
9
|
+
|
10
|
+
def referenced_slots
|
11
|
+
template.scan( /\{([a-z]+)\}/i ).map(&:first).map(&:to_sym)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module AlexaGenerator
|
2
|
+
class Slot
|
3
|
+
module SlotType
|
4
|
+
LITERAL = :LITERAL
|
5
|
+
NUMBER = :NUMBER
|
6
|
+
DATE = :DATE
|
7
|
+
TIME = :TIME
|
8
|
+
DURATION = :DURATION
|
9
|
+
end
|
10
|
+
|
11
|
+
class Builder
|
12
|
+
attr_reader :bindings
|
13
|
+
|
14
|
+
def initialize(name, type)
|
15
|
+
@name = name
|
16
|
+
@type = type
|
17
|
+
@bindings = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_binding(value)
|
21
|
+
add_bindings(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_bindings(*values)
|
25
|
+
values.map { |v| @bindings.push(v) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def create
|
29
|
+
Slot.new(@name, @type)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :name, :type
|
34
|
+
|
35
|
+
def initialize(name, type)
|
36
|
+
@name = name.to_sym
|
37
|
+
@type = type
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.build(name, type, &block)
|
41
|
+
builder = Builder.new(name, type)
|
42
|
+
block.call(builder)
|
43
|
+
builder
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module AlexaGenerator
|
2
|
+
class SlotBinding
|
3
|
+
attr_reader :slot_name, :value
|
4
|
+
|
5
|
+
def initialize(slot_name, value)
|
6
|
+
@slot_name = slot_name
|
7
|
+
@value = value
|
8
|
+
end
|
9
|
+
|
10
|
+
def bind_to_template!(template)
|
11
|
+
template.gsub! "{#{slot_name}}", "{#{value}|#{slot_name}}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'alexa_generator/intent_schema'
|
2
|
+
require 'alexa_generator/sample_utterance_template'
|
3
|
+
require 'alexa_generator/slot_binding'
|
4
|
+
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
module AlexaGenerator
|
8
|
+
class VoiceInterface
|
9
|
+
attr_reader :intents
|
10
|
+
|
11
|
+
class Builder
|
12
|
+
def initialize
|
13
|
+
@intents = []
|
14
|
+
@bindings = []
|
15
|
+
@utterance_templates = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_intent(name, &block)
|
19
|
+
builder = Intent.build(name, &block)
|
20
|
+
@bindings.concat(builder.bindings)
|
21
|
+
@utterance_templates.concat(builder.utterance_templates)
|
22
|
+
@intents.push(builder.create)
|
23
|
+
end
|
24
|
+
|
25
|
+
def create
|
26
|
+
VoiceInterface.new(@intents, @utterance_templates, @bindings)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(intents, utterance_templates, slot_bindings)
|
31
|
+
@intents = Hash[ intents.map {|x| [x.name, x]} ]
|
32
|
+
|
33
|
+
@utterance_templates = utterance_templates.group_by { |x| x.intent_name }
|
34
|
+
@slot_bindings = slot_bindings.group_by { |x| x.slot_name }
|
35
|
+
end
|
36
|
+
|
37
|
+
def intent_schema
|
38
|
+
{
|
39
|
+
intents: @intents.values.map do |intent|
|
40
|
+
{
|
41
|
+
intent: intent.name,
|
42
|
+
slots: intent.slots.map do |slot|
|
43
|
+
{
|
44
|
+
name: slot.name,
|
45
|
+
type: slot.type
|
46
|
+
}
|
47
|
+
end
|
48
|
+
}
|
49
|
+
end
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def sample_utterances(intent_name)
|
54
|
+
templates = @utterance_templates[intent_name] || []
|
55
|
+
utterances = Set.new
|
56
|
+
|
57
|
+
templates.each do |template|
|
58
|
+
# Consider only the slots that are referenced in this template
|
59
|
+
relevant_slots = template.referenced_slots
|
60
|
+
|
61
|
+
# Compute all possible value bindings for the relevant slots
|
62
|
+
slot_values = relevant_slots.
|
63
|
+
# Extract value bindings for each slot
|
64
|
+
map { |slot| @slot_bindings[slot] }
|
65
|
+
|
66
|
+
if slot_values.any?
|
67
|
+
slot_value_combinations = slot_values.first
|
68
|
+
|
69
|
+
if slot_values.count > 1
|
70
|
+
remaining_values = slot_values[1..-1]
|
71
|
+
slot_value_combinations = slot_value_combinations.product(*remaining_values)
|
72
|
+
else
|
73
|
+
slot_value_combinations = slot_value_combinations.map { |x| [x] }
|
74
|
+
end
|
75
|
+
|
76
|
+
slot_value_combinations.each do |value_binding|
|
77
|
+
raw_template = template.template.dup
|
78
|
+
|
79
|
+
# puts value_binding.inspect
|
80
|
+
|
81
|
+
value_binding.each do |binding|
|
82
|
+
# puts "----> #{binding}"
|
83
|
+
binding.bind_to_template!( raw_template )
|
84
|
+
end
|
85
|
+
|
86
|
+
utterances.add( raw_template )
|
87
|
+
end
|
88
|
+
# If there are no slot values, then just stuff the untouched template into utterances.
|
89
|
+
else
|
90
|
+
utterances.add( template.template )
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
utterances.sort.map do |utterance|
|
95
|
+
"#{intent_name} #{utterance}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.build(&block)
|
100
|
+
builder = Builder.new
|
101
|
+
block.call(builder)
|
102
|
+
builder.create
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AlexaGenerator::Slot do
|
4
|
+
context 'builder' do
|
5
|
+
it 'should return an instance of a builder' do
|
6
|
+
slot = AlexaGenerator::Intent.build(:IntentOne) { |x| }
|
7
|
+
|
8
|
+
expect(slot).to be_an_instance_of(AlexaGenerator::Intent::Builder)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should add slot bindings' do
|
12
|
+
intent = AlexaGenerator::Intent.build(:IntentOne) do |intent|
|
13
|
+
intent.add_slot(:SlotOne, AlexaGenerator::Slot::SlotType::LITERAL) do |slot|
|
14
|
+
slot.add_bindings('a', 'b', 'c')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
expect(intent.bindings).to be_an_instance_of(Array)
|
19
|
+
expect(intent.bindings.map(&:value)).to eq(['a', 'b', 'c'])
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should create an intent when create is called' do
|
23
|
+
builder = AlexaGenerator::Intent.build(:IntentOne) do |intent|
|
24
|
+
intent.add_slot(:SlotOne, AlexaGenerator::Slot::SlotType::LITERAL) do |slot|
|
25
|
+
slot.add_bindings('a', 'b', 'c')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
intent = builder.create
|
30
|
+
|
31
|
+
expect(intent.name).to eq(:IntentOne)
|
32
|
+
|
33
|
+
expect(intent.slots.count).to eq(1)
|
34
|
+
expect(intent.slots.first.name).to eq(:SlotOne)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AlexaGenerator::SampleUtteranceTemplate do
|
4
|
+
it 'should parse slots out of templates' do
|
5
|
+
template = AlexaGenerator::SampleUtteranceTemplate.new 'MyIntent', 'Intent, {SlotOne} and {SlotTwo}'
|
6
|
+
|
7
|
+
expect(template.referenced_slots).to eq([:SlotOne, :SlotTwo])
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should work with no slots' do
|
11
|
+
template = AlexaGenerator::SampleUtteranceTemplate.new 'MyIntent', 'Intent, do that thing'
|
12
|
+
|
13
|
+
expect(template.referenced_slots).to eq([])
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should not detect bound slots' do
|
17
|
+
template = AlexaGenerator::SampleUtteranceTemplate.new 'MyIntent', 'Intent, do {that|Thing}'
|
18
|
+
|
19
|
+
expect(template.referenced_slots).to eq([])
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AlexaGenerator::Slot do
|
4
|
+
context 'builder' do
|
5
|
+
it 'should return an instance of a builder' do
|
6
|
+
slot = AlexaGenerator::Slot.build(:SlotOne, AlexaGenerator::Slot::SlotType::LITERAL) { |x| }
|
7
|
+
|
8
|
+
expect(slot).to be_an_instance_of(AlexaGenerator::Slot::Builder)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should add slot bindings' do
|
12
|
+
slot = AlexaGenerator::Slot.build(:SlotOne, AlexaGenerator::Slot::SlotType::LITERAL) do |slot|
|
13
|
+
slot.add_bindings('a', 'b', 'c')
|
14
|
+
end
|
15
|
+
|
16
|
+
expect(slot.bindings).to eq(['a', 'b', 'c'])
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should create a slot when create is called' do
|
20
|
+
builder = AlexaGenerator::Slot.build(:SlotOne, AlexaGenerator::Slot::SlotType::LITERAL) do |slot|
|
21
|
+
slot.add_bindings('a', 'b', 'c')
|
22
|
+
end
|
23
|
+
|
24
|
+
slot = builder.create
|
25
|
+
|
26
|
+
expect(slot.name).to eq(:SlotOne)
|
27
|
+
expect(slot.type).to eq(AlexaGenerator::Slot::SlotType::LITERAL)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
describe AlexaGenerator::VoiceInterface do
|
5
|
+
context 'builder' do
|
6
|
+
it 'should build a valid voice interface' do
|
7
|
+
iface = AlexaGenerator::VoiceInterface.build do |iface|
|
8
|
+
iface.add_intent(:IntentOne) do |intent|
|
9
|
+
intent.add_slot(:SlotOne, AlexaGenerator::Slot::SlotType::LITERAL) do |slot|
|
10
|
+
slot.add_binding('value1')
|
11
|
+
end
|
12
|
+
|
13
|
+
intent.add_utterance_template('test {SlotOne} test')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
expect(iface).to be_an_instance_of(AlexaGenerator::VoiceInterface)
|
18
|
+
expect(iface.intent_schema).to eq(
|
19
|
+
{
|
20
|
+
intents: [
|
21
|
+
{
|
22
|
+
intent: :IntentOne,
|
23
|
+
slots: [
|
24
|
+
{
|
25
|
+
name: :SlotOne,
|
26
|
+
type: AlexaGenerator::Slot::SlotType::LITERAL
|
27
|
+
}
|
28
|
+
]
|
29
|
+
}
|
30
|
+
]
|
31
|
+
})
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should produce bound utterances' do
|
35
|
+
iface = AlexaGenerator::VoiceInterface.build do |iface|
|
36
|
+
iface.add_intent(:MyIntent) do |intent|
|
37
|
+
intent.add_slot(:SlotOne, AlexaGenerator::Slot::SlotType::LITERAL) do |slot|
|
38
|
+
slot.add_binding('make me a sandwich')
|
39
|
+
slot.add_binding('fix my motorcycle')
|
40
|
+
end
|
41
|
+
|
42
|
+
intent.add_slot(:SlotTwo, AlexaGenerator::Slot::SlotType::NUMBER) do |slot|
|
43
|
+
slot.add_binding('one')
|
44
|
+
slot.add_binding('two')
|
45
|
+
end
|
46
|
+
|
47
|
+
intent.add_slot(:SlotThree, AlexaGenerator::Slot::SlotType::TIME) do |slot|
|
48
|
+
slot.add_binding('6 a.m.')
|
49
|
+
slot.add_binding('noon')
|
50
|
+
end
|
51
|
+
|
52
|
+
intent.add_utterance_template('Alexa, please {SlotOne} {SlotTwo} at {SlotThree}')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
actual = iface.sample_utterances(:MyIntent)
|
57
|
+
|
58
|
+
expect(actual.count).to eq(8)
|
59
|
+
expect(actual).to include('MyIntent Alexa, please {fix my motorcycle|SlotOne} {one|SlotTwo} at {noon|SlotThree}')
|
60
|
+
expect(actual).to include('MyIntent Alexa, please {fix my motorcycle|SlotOne} {two|SlotTwo} at {noon|SlotThree}')
|
61
|
+
expect(actual).to include('MyIntent Alexa, please {make me a sandwich|SlotOne} {one|SlotTwo} at {noon|SlotThree}')
|
62
|
+
expect(actual).to include('MyIntent Alexa, please {make me a sandwich|SlotOne} {two|SlotTwo} at {noon|SlotThree}')
|
63
|
+
expect(actual).to include('MyIntent Alexa, please {fix my motorcycle|SlotOne} {one|SlotTwo} at {6 a.m.|SlotThree}')
|
64
|
+
expect(actual).to include('MyIntent Alexa, please {fix my motorcycle|SlotOne} {two|SlotTwo} at {6 a.m.|SlotThree}')
|
65
|
+
expect(actual).to include('MyIntent Alexa, please {make me a sandwich|SlotOne} {one|SlotTwo} at {6 a.m.|SlotThree}')
|
66
|
+
expect(actual).to include('MyIntent Alexa, please {make me a sandwich|SlotOne} {two|SlotTwo} at {6 a.m.|SlotThree}')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'with no templates' do
|
71
|
+
intents = [ AlexaGenerator::Intent.new( :MyIntent, [] ) ]
|
72
|
+
utterance_templates = [
|
73
|
+
AlexaGenerator::SampleUtteranceTemplate.new( :MyIntent, 'Alexa, please do that one thing')
|
74
|
+
]
|
75
|
+
slot_bindings = []
|
76
|
+
|
77
|
+
it 'should produce utterances' do
|
78
|
+
actual = AlexaGenerator::VoiceInterface.new(intents, utterance_templates, slot_bindings).sample_utterances(:MyIntent)
|
79
|
+
expected = [ "#{utterance_templates.first.intent_name} #{utterance_templates.first.template}" ]
|
80
|
+
|
81
|
+
expect(actual).to eq(expected)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with a single template' do
|
86
|
+
intents = [
|
87
|
+
AlexaGenerator::Intent.new(
|
88
|
+
:MyIntent, [ AlexaGenerator::Slot.new(:SlotOne, AlexaGenerator::Slot::SlotType::LITERAL) ]
|
89
|
+
)
|
90
|
+
]
|
91
|
+
utterance_templates = [
|
92
|
+
AlexaGenerator::SampleUtteranceTemplate.new( :MyIntent, 'Alexa, please {SlotOne}')
|
93
|
+
]
|
94
|
+
slot_bindings = [
|
95
|
+
AlexaGenerator::SlotBinding.new( :SlotOne, 'make me a sandwich' ),
|
96
|
+
AlexaGenerator::SlotBinding.new( :SlotOne, 'fix my motorcycle' )
|
97
|
+
]
|
98
|
+
|
99
|
+
it 'should produce bound utterances' do
|
100
|
+
actual = AlexaGenerator::VoiceInterface.new(intents, utterance_templates, slot_bindings).sample_utterances(:MyIntent)
|
101
|
+
|
102
|
+
expect(actual.count).to eq(slot_bindings.count)
|
103
|
+
expect(actual).to include('MyIntent Alexa, please {make me a sandwich|SlotOne}')
|
104
|
+
expect(actual).to include('MyIntent Alexa, please {fix my motorcycle|SlotOne}')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'with multiple templates' do
|
109
|
+
intents = [
|
110
|
+
AlexaGenerator::Intent.new(
|
111
|
+
:MyIntent, [
|
112
|
+
AlexaGenerator::Slot.new(:SlotOne, AlexaGenerator::Slot::SlotType::LITERAL),
|
113
|
+
AlexaGenerator::Slot.new(:SlotTwo, AlexaGenerator::Slot::SlotType::NUMBER),
|
114
|
+
AlexaGenerator::Slot.new(:SlotThree, AlexaGenerator::Slot::SlotType::TIME),
|
115
|
+
],
|
116
|
+
)
|
117
|
+
]
|
118
|
+
utterance_templates = [
|
119
|
+
AlexaGenerator::SampleUtteranceTemplate.new( :MyIntent, 'Alexa, please {SlotOne} {SlotTwo} at {SlotThree}')
|
120
|
+
]
|
121
|
+
slot_bindings = [
|
122
|
+
AlexaGenerator::SlotBinding.new( :SlotOne, 'make me a sandwich' ),
|
123
|
+
AlexaGenerator::SlotBinding.new( :SlotOne, 'fix my motorcycle' ),
|
124
|
+
|
125
|
+
AlexaGenerator::SlotBinding.new( :SlotTwo, 'one' ),
|
126
|
+
AlexaGenerator::SlotBinding.new( :SlotTwo, 'two' ),
|
127
|
+
|
128
|
+
AlexaGenerator::SlotBinding.new( :SlotThree, '6 a.m.' ),
|
129
|
+
AlexaGenerator::SlotBinding.new( :SlotThree, 'noon' ),
|
130
|
+
]
|
131
|
+
|
132
|
+
it 'should produce bound utterances' do
|
133
|
+
actual = AlexaGenerator::VoiceInterface.new(intents, utterance_templates, slot_bindings).sample_utterances(:MyIntent)
|
134
|
+
|
135
|
+
expect(actual.count).to eq(8)
|
136
|
+
expect(actual).to include('MyIntent Alexa, please {fix my motorcycle|SlotOne} {one|SlotTwo} at {noon|SlotThree}')
|
137
|
+
expect(actual).to include('MyIntent Alexa, please {fix my motorcycle|SlotOne} {two|SlotTwo} at {noon|SlotThree}')
|
138
|
+
expect(actual).to include('MyIntent Alexa, please {make me a sandwich|SlotOne} {one|SlotTwo} at {noon|SlotThree}')
|
139
|
+
expect(actual).to include('MyIntent Alexa, please {make me a sandwich|SlotOne} {two|SlotTwo} at {noon|SlotThree}')
|
140
|
+
expect(actual).to include('MyIntent Alexa, please {fix my motorcycle|SlotOne} {one|SlotTwo} at {6 a.m.|SlotThree}')
|
141
|
+
expect(actual).to include('MyIntent Alexa, please {fix my motorcycle|SlotOne} {two|SlotTwo} at {6 a.m.|SlotThree}')
|
142
|
+
expect(actual).to include('MyIntent Alexa, please {make me a sandwich|SlotOne} {one|SlotTwo} at {6 a.m.|SlotThree}')
|
143
|
+
expect(actual).to include('MyIntent Alexa, please {make me a sandwich|SlotOne} {two|SlotTwo} at {6 a.m.|SlotThree}')
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: alexa_generator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christopher Mullins
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-07-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.0.0
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description:
|
42
|
+
email: chris@sidoh.org
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- .gitignore
|
48
|
+
- Gemfile
|
49
|
+
- LICENSE
|
50
|
+
- README.md
|
51
|
+
- Rakefile
|
52
|
+
- alexa_generator.gemspec
|
53
|
+
- lib/alexa_generator.rb
|
54
|
+
- lib/alexa_generator/intent.rb
|
55
|
+
- lib/alexa_generator/intent_schema.rb
|
56
|
+
- lib/alexa_generator/sample_utterance_template.rb
|
57
|
+
- lib/alexa_generator/slot.rb
|
58
|
+
- lib/alexa_generator/slot_binding.rb
|
59
|
+
- lib/alexa_generator/version.rb
|
60
|
+
- lib/alexa_generator/voice_interface.rb
|
61
|
+
- spec/alexa_generator/intent_spec.rb
|
62
|
+
- spec/alexa_generator/sample_utterance_template_spec.rb
|
63
|
+
- spec/alexa_generator/slot_spec.rb
|
64
|
+
- spec/alexa_generator/voice_interface_spec.rb
|
65
|
+
- spec/spec_helper.rb
|
66
|
+
homepage: http://github.com/sidoh/alexa_generator
|
67
|
+
licenses: []
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.0.14
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: Generates voice interfaces for Alexa apps based on templates.
|
89
|
+
test_files: []
|