alexa_generator 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|