expando 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2d9e0e4d3a50bb75dea3ac215b6bedc116c5d21a
4
+ data.tar.gz: 2397629047792d35cc5171cc9808bb514e30157e
5
+ SHA512:
6
+ metadata.gz: 816a5d9adc5f1a066b5385132276c892d192f15daf50d53b18040e4490b4d3f5ff595058a8d7115c71669af49c94b746c25004a0a67b9ec0f76be203205ca794
7
+ data.tar.gz: b7e3f739d3be12cad151a26da0106797c81a37d9e8e4d8967bcf06c0fac05642524b2adbd55331da29ed3993611ba22c21df2fe6621b5b40f229a2013380c14b
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1 @@
1
+ expando
@@ -0,0 +1 @@
1
+ ruby-2.2.0
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # gem 'api-ai-ruby', git: 'git@github.com:voxable-labs/api-ai-ruby.git'
4
+ # Uncomment the below when developing on api-ai-ruby locally
5
+ gem 'api-ai-ruby', :path => '../../api-ai-ruby'
6
+
7
+ # Specify your gem's dependencies in expando.gemspec
8
+ gemspec
@@ -0,0 +1,2 @@
1
+ # Expando
2
+
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'expando'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.expand_path( File.dirname( __FILE__ ) + '/../lib' )
4
+
5
+ require 'expando'
6
+ require 'gli'
7
+
8
+ include GLI::App
9
+
10
+ version Expando::VERSION
11
+
12
+ program_desc 'A tool for managing files written in the Expando markup language.'
13
+
14
+ config_file File.join(ENV['HOME'],'.expando.rc.yaml')
15
+
16
+ flag :client_access_token, desc: 'Api.ai Client Access Token'
17
+ flag :developer_access_token, desc: 'Api.ai Developer Access Token'
18
+ flag :intents_path, desc: 'The path to the directory containing intent files'
19
+ flag :entities_path, desc: 'The path to the directory containing entity files'
20
+
21
+ # Make credentials globally accessible
22
+ pre do | global_options, command, options, args |
23
+ # TODO: Why are these being repeated in global_options?
24
+ global_options[:credentials] = {
25
+ client_access_token: global_options[:client_access_token],
26
+ developer_access_token: global_options[:developer_access_token]
27
+ }
28
+ end
29
+
30
+ desc 'Update entities and/or intents'
31
+ long_desc <<-DESC
32
+ Update Expando's entities and intents on Api.ai based on the contents of the
33
+ files in the entities and intents directories, respectively.
34
+ DESC
35
+ command [ :update, :u ] do | c |
36
+ c.desc "Update Expando's entities"
37
+ c.long_desc <<-DESC
38
+ Updates the specified entities. The entity names can be specified after this
39
+ argument in a comma delimited list, and should match the file name of the
40
+ specific entity in the entities directory. If no entity name is specified,
41
+ all entities are updated.
42
+ DESC
43
+ c.arg 'entity, entity[, entity]*'
44
+ c.command :entities do | entities |
45
+ entities.action do | global_options, options, args |
46
+ # TODO: update all by default
47
+ args.each do | entity |
48
+ updater = Expando::EntityUpdater.new( entity,
49
+ client_keys: global_options[:credentials],
50
+ intents_path: global_options[:intents_path],
51
+ entities_path: global_options[:entities_path])
52
+ updater.update!
53
+ end
54
+ end
55
+ end
56
+
57
+ c.desc "Update Api.ai intents"
58
+ c.long_desc <<-DESC
59
+ Updates the specified intents. The intent names can be specified after this
60
+ argument in a comma delimited list, and should match the file name of the
61
+ specific intent in the intents directory. If no intent name is specified,
62
+ all intents are updated.
63
+ DESC
64
+ c.arg 'intent, intent[, intent]*'
65
+ c.command :intents do | intents |
66
+ intents.action do | global_options, options, args |
67
+ # TODO: update all by default
68
+ args.each do | intent |
69
+ updater = Expando::IntentUpdater.new( intent,
70
+ client_keys: global_options[:credentials],
71
+ intents_path: global_options[:intents_path],
72
+ entities_path: global_options[:entities_path])
73
+ updater.update!
74
+ end
75
+ end
76
+ end
77
+
78
+ # TODO: Default to both in sequence
79
+ #c.default_command :entities
80
+ end
81
+
82
+ exit run(ARGV)
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,8 @@
1
+ test:
2
+ override:
3
+ - RAILS_ENV=test bundle exec rspec -r rspec_junit_formatter --format RspecJunitFormatter -o $CIRCLE_TEST_REPORTS/rspec/junit.xml
4
+ deployment:
5
+ staging:
6
+ branch: /.*/
7
+ commands:
8
+ - bundle exec ./bin/expando update entities app
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'expando/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'expando'
8
+ spec.version = Expando::VERSION
9
+ spec.authors = ['Matt Buck']
10
+ spec.email = ['matt@voxable.io']
11
+
12
+ spec.summary = 'The Expando reference implementation.'
13
+ spec.description = 'A translation language for defining user utterance examples in conversational interfaces.'
14
+ spec.homepage = 'http://voxable.io'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = 'bin'
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.9'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency 'rspec', '~> 3.4.0'
24
+ spec.add_development_dependency 'rspec_junit_formatter', '~> 0.2.2'
25
+ spec.add_development_dependency 'climate_control', '~> 0.0.3'
26
+
27
+ spec.add_runtime_dependency 'api-ai-ruby', '~> 1.1.0'
28
+ spec.add_runtime_dependency 'gli', '~> 2.13.4'
29
+ spec.add_runtime_dependency 'colorize', '~> 0.7.7'
30
+ spec.add_runtime_dependency 'awesome_print', '~> 1.6.1'
31
+ end
@@ -0,0 +1,12 @@
1
+ require 'expando/version'
2
+ require 'expando/updater'
3
+ require 'expando/entity_updater'
4
+ require 'expando/intent_updater'
5
+ require 'expando/expander'
6
+ require 'api-ai-ruby'
7
+ require 'colorize'
8
+ require 'awesome_print'
9
+
10
+ module Expando
11
+ # Your code goes here...
12
+ end
@@ -0,0 +1,60 @@
1
+ module Expando
2
+ # Responsible for updating entity objects on Api.ai based on the contents of
3
+ # files in `/entities`.
4
+ class EntityUpdater < Updater
5
+ # !@attribute name
6
+ # @return [String] the name of the entity to be updated
7
+ attr_accessor :name
8
+
9
+ # !@attribute entities_path
10
+ # @return [String] the path to the directory containing the entities text files
11
+ attr_accessor :entities_path
12
+
13
+ # Initialize a new `EntityUpdater`.
14
+ #
15
+ # @see Updater#initialize
16
+ def initialize( * )
17
+ super
18
+ end
19
+
20
+ # Update the named entity on Api.ai.
21
+ #
22
+ # @return [Hash] if request successful. This is the response body.
23
+ # @return [ApiAiRuby::RequestError] if request is in error.
24
+ def update!
25
+ entity = [{ name: @name.to_s, entries: expanded_entries }]
26
+
27
+ response = @client.update_entities_request( entity )
28
+
29
+ handle_response( response, :entity )
30
+ end
31
+
32
+ private
33
+
34
+ # @return [Array<String>] The expanded list of entries and their synonyms.
35
+ def expanded_entries
36
+ sorted_entries.inject( [] ) do | entries, ( entry_value, synonyms ) |
37
+ entries << { value: entry_value, synonyms: [ entry_value ] + synonyms }
38
+ end
39
+ end
40
+
41
+ # @return [Hash] Properly sorted entries. Each key is the entry's name, and
42
+ # the value is the list of synonyms for that entry.
43
+ def sorted_entries
44
+ sorted = Hash.new( [] )
45
+
46
+ expanded_entities.each do | line |
47
+ entry_value, *synonyms = *line.split(',').collect{ |s| s.strip }
48
+ sorted[ entry_value ] = sorted[ entry_value ] + synonyms
49
+ end
50
+
51
+ sorted
52
+ end
53
+
54
+ # @return [Array<String>] The expanded list of entities.
55
+ def expanded_entities
56
+ entity_file_path = File.join( @entities_path, @name.to_s + '.txt')
57
+ Expander.expand! file_lines( entity_file_path )
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,59 @@
1
+ # The `Expander` module is responsible for taking an array of intents or utterances,
2
+ # and expanding any strings containing the following tokens:
3
+ #
4
+ # {I|we} heard you {love|hate} computers
5
+ #
6
+ # into the following Cartesian product:
7
+ #
8
+ # I heard you love computers
9
+ # I heard you hate computers
10
+ # we heard you love computers
11
+ # we heard you hate computers
12
+ #
13
+ # This greatly reduces the complexity of creating performant speech interfaces.
14
+ module Expando
15
+ module Expander
16
+ TOKEN_REGEX = /(?<!\\)\((.*?)\)/
17
+
18
+ module_function
19
+
20
+ # Generate a new `Expander`.
21
+ #
22
+ # @param [Array<String>] lines The text to scan and expand.
23
+ # @return [Array] The expanded text.
24
+ def expand!( lines )
25
+ expanded_lines = []
26
+
27
+ lines.each do |line|
28
+ tokens = line.scan TOKEN_REGEX
29
+
30
+ # Don't perform expansion if no tokens are present.
31
+ if tokens.empty?
32
+ expanded_lines << line
33
+ next
34
+ end
35
+
36
+ expanded_tokens = []
37
+ tokens.each_with_index do |token, index|
38
+ expanded_tokens[index] = token[0].split( '|' )
39
+ end
40
+
41
+ # Produce Cartesian product of all tokenized values.
42
+ token_product = expanded_tokens[ 0 ].product( *expanded_tokens[ 1..-1 ] )
43
+
44
+ # Generate new expanded lines.
45
+ token_product.each do |replacement_values|
46
+ expanded_line = line
47
+
48
+ replacement_values.each do |value|
49
+ expanded_line = expanded_line.sub( /\{(.*?)\}/, value )
50
+ end
51
+
52
+ expanded_lines << expanded_line
53
+ end
54
+ end
55
+
56
+ expanded_lines
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,95 @@
1
+ require 'json'
2
+
3
+ module Expando
4
+ # Responsible for updating intent objects on Api.ai based on the contents of
5
+ # files in `/intents`.
6
+ class IntentUpdater < Updater
7
+ # !@attribute name
8
+ # @return [String] the name of the intent to be updated
9
+ attr_accessor :name
10
+
11
+ # !@attribute intents_path
12
+ # @return [String] the path to the directory containing the intent source files
13
+ attr_accessor :intents_path
14
+
15
+ # Initialize a new `IntentUpdater`.
16
+ #
17
+ # @see Updater#initialize
18
+ def initialize( * )
19
+ super
20
+ end
21
+
22
+ # Update the named entity on Api.ai.
23
+ #
24
+ # @return [Hash] if request successful. This is the response body.
25
+ # @return [ApiAiRuby::RequestError] if request is in error.
26
+ def update!
27
+ response = @client.update_intent_request( intent_json )
28
+
29
+ handle_response( response, :intent )
30
+ end
31
+
32
+ private
33
+
34
+ # Construct a proper JSON object for updating the intent, based on its current
35
+ # state plus the expanded templates
36
+ #
37
+ # @return [Hash] The constructed JSON of for the intent.
38
+ def intent_json
39
+ @id = intent_id
40
+
41
+ log_message "Fetching latest version of #{ @name } intent"
42
+ json = @client.get_intent_request( @id )
43
+
44
+ json[ :userSays ] = expanded_utterances
45
+
46
+ # TODO: Make this a separate, tested method
47
+ responses = File.readlines File.join( @intents_path, '..', 'responses', @name.to_s + '.txt' )
48
+ responses = responses.collect { |response| response.chomp }
49
+
50
+ responsesJson = json[ :responses ]
51
+ responsesJson[ 0 ][ :speech ] = responses
52
+ json[:responses] = responsesJson
53
+
54
+ # Clean up portions of the JSON response that we don't need in the request
55
+ %w{templates state priority webhookUsed}.each { |key| json.delete( key.to_sym ) }
56
+
57
+ json
58
+ end
59
+
60
+ # Fetch the ID of the intent with this name on Api.ai.
61
+ #
62
+ # @return [String] The ID of the intent with this `@name` on Api.ai.
63
+ def intent_id
64
+ log_message "Fetching id of #{ @name } intent"
65
+ intents = @client.get_intents_request
66
+
67
+ matching_intent = intents.select { |intent| intent[ :name ] == @name.to_s }
68
+
69
+ if matching_intent.empty?
70
+ # TODO: Consult Exceptional Ruby for a better way to do this
71
+ raise "There is no intent named #{@name}"
72
+ else
73
+ return matching_intent.first[ :id ]
74
+ end
75
+ end
76
+
77
+ # @return [Array<String>] The expanded list of intent utterances.
78
+ def expanded_utterances
79
+ intent_utterance_file_path = File.join( @intents_path, @name.to_s + '.txt')
80
+ # TODO: Test
81
+ utterances = Expander.expand! file_lines( intent_utterance_file_path )
82
+
83
+ utterances.collect do |utterance|
84
+ {
85
+ data: [
86
+ text: utterance
87
+ ],
88
+ # TODO: Make this an aption
89
+ isTemplate: false
90
+ }
91
+ end
92
+ end
93
+ end
94
+ end
95
+
@@ -0,0 +1,114 @@
1
+ module Expando
2
+ class Updater
3
+ # The default location of intent source files
4
+ DEFAULT_INTENTS_PATH = File.join( Dir.getwd, '../../intents')
5
+
6
+ # The default location of entity source files
7
+ DEFAULT_ENTITIES_PATH = File.join( Dir.getwd, '../../entities' )
8
+
9
+ # Initialize a new `Updater`.
10
+ #
11
+ # @param [Symbol] name The name of the intent or entity to update. (default: `nil`)
12
+ # @param [String] entities_path The path to the directory containing the
13
+ # entities text files. (default: `'entities'`)
14
+ # @param [String] intents_path The path to the directory containing the
15
+ # intents source files. (default: 'intents')
16
+ # @param [Hash] client_keys A hash of Api.ai credentials.
17
+ # @option client_keys [String] :developer_access_token The Api.ai developer
18
+ # access token.
19
+ # @option client_keys [String] :client_access_token The Api.ai client access
20
+ # token.
21
+ # @return [Updater] The new `Updater`.
22
+ def initialize( name = nil, intents_path: DEFAULT_INTENTS_PATH, entities_path: DEFAULT_ENTITIES_PATH, client_keys: {})
23
+ @name = name
24
+ @intents_path = intents_path
25
+ @entities_path = entities_path
26
+
27
+ @client = ApiAiRuby::Client.new( credentials( client_keys ) )
28
+ end
29
+
30
+ private
31
+
32
+ # Generate a credentials hash for Api.ai from environment variables or passed
33
+ # arguments, whichever is provided.
34
+ #
35
+ # @param [Hash] client_keys A hash of Api.ai credentials.
36
+ # @option client_keys [String] :developer_access_token The Api.ai developer
37
+ # access token.
38
+ # @option client_keys [String] :client_access_token The Api.ai client access
39
+ # token.
40
+ # @return [Hash] The Api.ai client credentials.
41
+ def credentials(client_keys)
42
+ developer_access_token = ENV['API_AI_DEVELOPER_ACCESS_TOKEN'] || client_keys[:developer_access_token]
43
+ client_access_token = ENV['API_AI_CLIENT_ACCESS_TOKEN'] || client_keys[:client_access_token]
44
+
45
+ {
46
+ client_access_token: client_access_token,
47
+ developer_access_token: developer_access_token,
48
+ }
49
+ end
50
+
51
+ # Properly handle the response from Api.ai.
52
+ #
53
+ # @param [Hash] response The response from `ApiAiRuby::Client`.
54
+ # @param [Symbol] type Either `:intent` or `:entity`, depending on what is
55
+ # being updated.
56
+ # @return [void]
57
+ def handle_response( response, type )
58
+ begin
59
+ if successful?( response )
60
+ log_completion_message(type )
61
+ else
62
+ puts failed_update_message(type )
63
+ ap response
64
+ end
65
+ rescue Exception => e
66
+ puts e.message
67
+ puts e.backtrace.inspect
68
+
69
+ abort( failed_update_message )
70
+ end
71
+ end
72
+
73
+ # Determine if the query was successful.
74
+ #
75
+ # @param [Hash] response The raw response from Api.ai
76
+ # @return [Boolean] `true` if successful, `false` otherwise.
77
+ def successful?( response )
78
+ response && response[ :status ] && ( response[ :status ][ :code ] == 200 )
79
+ end
80
+
81
+ # Generate a failed entity update message.
82
+ #
83
+ # @param [Symbol] type The type of update (`:entity` or `:intent`).
84
+ # @return [String] The failed update message.
85
+ def failed_update_message( type )
86
+ '• '.colorize( :blue ) + "#{ @name } #{ type.to_s } update failed:".colorize(:red )
87
+ end
88
+
89
+ # Output a log message.
90
+ #
91
+ # @param [String] The message.
92
+ # @return [void]
93
+ def log_message( message )
94
+ puts '• '.colorize( :blue ) + message
95
+ end
96
+
97
+ # Output a successful update message.
98
+ #
99
+ # @param [Symbol] type The type of update (`:entity` or `:intent`).
100
+ # @return [void]
101
+ def log_completion_message( type )
102
+ puts "• ".colorize( :blue ) + "#{ @name } #{ type.to_s } successfully updated!".colorize( :green )
103
+ puts "\nExpando:".colorize( :magenta ) + " Api.ai agent updated."
104
+ end
105
+
106
+ # Read a file into an array of strings.
107
+ #
108
+ # @param [String] file_path The path to the file to convert.
109
+ # @return [Array<String>] An array of all of the lines in the file.
110
+ def file_lines( file_path )
111
+ File.read( file_path ).lines.collect{ |line| line.chomp }
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,3 @@
1
+ module Expando
2
+ VERSION = '0.1.0'
3
+ end
metadata ADDED
@@ -0,0 +1,192 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: expando
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matt Buck
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-05-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.4.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.4.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec_junit_formatter
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.2.2
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.2.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: climate_control
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.0.3
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.0.3
83
+ - !ruby/object:Gem::Dependency
84
+ name: api-ai-ruby
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.1.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.1.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: gli
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 2.13.4
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 2.13.4
111
+ - !ruby/object:Gem::Dependency
112
+ name: colorize
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.7.7
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.7.7
125
+ - !ruby/object:Gem::Dependency
126
+ name: awesome_print
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 1.6.1
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 1.6.1
139
+ description: A translation language for defining user utterance examples in conversational
140
+ interfaces.
141
+ email:
142
+ - matt@voxable.io
143
+ executables:
144
+ - console
145
+ - expando
146
+ - setup
147
+ extensions: []
148
+ extra_rdoc_files: []
149
+ files:
150
+ - ".gitignore"
151
+ - ".rspec"
152
+ - ".ruby-gemset"
153
+ - ".ruby-version"
154
+ - Gemfile
155
+ - README.md
156
+ - Rakefile
157
+ - bin/console
158
+ - bin/expando
159
+ - bin/setup
160
+ - circle.yml
161
+ - expando.gemspec
162
+ - lib/expando.rb
163
+ - lib/expando/entity_updater.rb
164
+ - lib/expando/expander.rb
165
+ - lib/expando/intent_updater.rb
166
+ - lib/expando/updater.rb
167
+ - lib/expando/version.rb
168
+ homepage: http://voxable.io
169
+ licenses: []
170
+ metadata: {}
171
+ post_install_message:
172
+ rdoc_options: []
173
+ require_paths:
174
+ - lib
175
+ required_ruby_version: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ required_rubygems_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ requirements: []
186
+ rubyforge_project:
187
+ rubygems_version: 2.4.6
188
+ signing_key:
189
+ specification_version: 4
190
+ summary: The Expando reference implementation.
191
+ test_files: []
192
+ has_rdoc: