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: []
         |