expando 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.
@@ -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: