cora 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
@@ -0,0 +1,7 @@
1
+ rvm:
2
+ - 1.9.2
3
+ - 1.9.3
4
+ - ruby-head
5
+
6
+ notifications:
7
+ irc: "irc.freenode.org#siriproxy"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cora.gemspec
4
+ gemspec
@@ -0,0 +1,8 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2, :cli => "--color" do
5
+ watch(%r{^spec/.+_spec\.rb})
6
+ watch(%r{^lib/.+\.rb}) { "spec" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
@@ -0,0 +1,12 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ desc 'Default: run specs.'
6
+ task :default => :spec
7
+
8
+ desc "Run specs"
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
11
+ # Put spec opts in a file named .rspec in root
12
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "cora/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "cora"
7
+ s.version = Cora::VERSION
8
+ s.authors = ["Jack Chen (chendo)"]
9
+ s.email = ["@chendo"]
10
+ s.homepage = ""
11
+ s.summary = "Example summary"
12
+ s.description = "Example description"
13
+
14
+ s.rubyforge_project = "cora"
15
+
16
+ s.add_development_dependency "rspec"
17
+ s.add_development_dependency "guard-rspec"
18
+ s.add_development_dependency "rake"
19
+
20
+ s.add_runtime_dependency "geocoder"
21
+
22
+ s.files = `git ls-files 2> /dev/null`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/* 2> /dev/null`.split("\n")
24
+ s.executables = `git ls-files -- bin/* 2> /dev/null`.split("\n").map{ |f| File.basename(f) }
25
+
26
+ s.require_paths = ["lib"]
27
+ end
@@ -0,0 +1,62 @@
1
+ require "cora/version"
2
+ require "cora/plugin"
3
+ require "cora/location"
4
+
5
+ class Cora
6
+
7
+ attr_reader :location
8
+
9
+ def plugins
10
+ @plugins ||= []
11
+ end
12
+
13
+ def process(text)
14
+ log "Processing '#{text}'"
15
+
16
+ if @callback
17
+ log "Active callback found, resuming"
18
+
19
+ # We must set the active callback to nil first, otherwise
20
+ # multiple callbacks within one listen block won't work
21
+ callback = @callback
22
+ @callback = nil
23
+ callback.call(text)
24
+ return true
25
+ end
26
+
27
+ plugins.each do |plugin|
28
+ log "Processing plugin #{plugin}"
29
+ return true if plugin.process(text)
30
+ end
31
+
32
+ log "No matches for '#{text}'"
33
+ no_matches
34
+ end
35
+
36
+ def respond(text, options = {})
37
+ end
38
+
39
+ def no_matches
40
+ end
41
+
42
+ def set_priority_plugin(plugin)
43
+ plugins.delete(plugin)
44
+ plugins.unshift(plugin)
45
+ end
46
+
47
+ def set_callback(&block)
48
+ @callback = block
49
+ end
50
+
51
+ def set_active_fiber(fiber)
52
+ @fiber = fiber
53
+ end
54
+
55
+ def set_location(latitude, longitude, extra = {})
56
+ @location = Location.new(latitude, longitude, extra)
57
+ end
58
+
59
+ def log(text)
60
+ $stderr.puts(text) if defined?(LOG)
61
+ end
62
+ end
@@ -0,0 +1,34 @@
1
+ require 'geocoder'
2
+
3
+ class Cora::Location
4
+
5
+ extend Forwardable
6
+
7
+ def_delegators :result, :address, :city, :state, :state_code, :country, :country_code, :postal_code
8
+
9
+ attr_reader :latitude, :longitude, :extra
10
+
11
+ def initialize(latitude, longitude, extra = {})
12
+ @latitude = latitude
13
+ @longitude = longitude
14
+ @extra = extra
15
+ end
16
+
17
+ # Returns nil if geolocation failed, or an instance of Geocoder::Result::Google
18
+ # See: http://rubydoc.info/github/alexreisner/geocoder/master/Geocoder/Result/Google
19
+ def result
20
+ results.first
21
+ end
22
+
23
+ # Returns an array of objects of class Geocoder::Result::Google (probably)
24
+ def results
25
+ @results ||= perform_reverse_geocode
26
+ end
27
+
28
+ private
29
+
30
+ def perform_reverse_geocode
31
+ Geocoder.search([latitude, longitude].join(','))
32
+ end
33
+
34
+ end
@@ -0,0 +1,97 @@
1
+ require 'fiber'
2
+ require 'forwardable'
3
+
4
+ class Cora::Plugin
5
+
6
+ # These could use some more work
7
+ CONFIRM_REGEX = /yes|yeah|yep|ok|confirm|affirmative|indeed|engage/i
8
+ DENY_REGEX = /no|nope|nah|cancel|negative/i
9
+
10
+ extend Forwardable
11
+
12
+ def_delegators :manager, :log, :location
13
+
14
+ attr_accessor :manager, :match_data
15
+ attr_reader :current_state
16
+
17
+ class << self
18
+
19
+ def listen_for(regex, options = {}, &block)
20
+ listeners[regex] = {
21
+ block: block,
22
+ within_state: ([options[:within_state]].flatten)
23
+ }
24
+ end
25
+
26
+ def listeners
27
+ @listeners ||= {}
28
+ end
29
+
30
+ end
31
+
32
+ def process(text)
33
+ listeners.each do |regex, entry|
34
+ if match = text.match(regex)
35
+ captures = match.captures
36
+ log "Matches #{regex}"
37
+
38
+ if entry[:within_state]
39
+ log "Applicable states: #{entry[:within_state].join(', ')}"
40
+ log "Current state: #{current_state}"
41
+
42
+ if entry[:within_state].include?(current_state)
43
+ log "Matches, executing block"
44
+
45
+ self.match_data = match
46
+ Fiber.new {
47
+ instance_exec(*captures, &entry[:block])
48
+ }.resume
49
+ return true
50
+ end
51
+ end
52
+ end
53
+
54
+ end
55
+ false
56
+ end
57
+
58
+ def listeners
59
+ self.class.listeners
60
+ end
61
+
62
+ def say(text, options={})
63
+ log "Say: #{text}"
64
+ manager.respond(text, options)
65
+ end
66
+
67
+ def ask(question, options={})
68
+ log "Ask: #{question}"
69
+
70
+ f = Fiber.current
71
+ options[:prompt_for_response] = true
72
+ manager.respond(question, options)
73
+ manager.set_callback do |text|
74
+ f.resume(text)
75
+ end
76
+
77
+ Fiber.yield
78
+ end
79
+
80
+ def confirm(question, options = {unmatched_message: "I'm sorry, I didn't understand that."}, &block)
81
+ while (response = ask(question))
82
+ if response.match(CONFIRM_REGEX)
83
+ return true
84
+ elsif response.match(DENY_REGEX)
85
+ return false
86
+ else
87
+ say options[:unmatched_message]
88
+ end
89
+ end
90
+ end
91
+
92
+ def set_state(state)
93
+ @current_state = state
94
+ manager.set_priority_plugin(self)
95
+ end
96
+
97
+ end
@@ -0,0 +1,3 @@
1
+ class Cora
2
+ VERSION = "0.0.4"
3
+ end
@@ -0,0 +1,223 @@
1
+ require 'spec_helper'
2
+ require 'support/test_plugin'
3
+
4
+ describe Cora do
5
+ context "integration test" do
6
+ let(:plugin) do
7
+ TestPlugin.new.tap { |plugin| plugin.manager = subject }
8
+ end
9
+
10
+ before do
11
+ subject.plugins << plugin
12
+ end
13
+
14
+ context "single state" do
15
+ it "responds to a simple test hook" do
16
+
17
+ subject.should_receive(:respond).with("test!", {})
18
+ subject.process("this is a test")
19
+
20
+ end
21
+ end
22
+
23
+ context "say" do
24
+ it "responds to a say with options" do
25
+ subject.should_receive(:respond).with("test options", {option: "option"})
26
+ subject.process("options")
27
+ end
28
+ end
29
+
30
+ context "multiple state" do
31
+ it "doesn't respond to listeners that don't have the required state" do
32
+ subject.should_receive(:no_matches)
33
+
34
+ subject.process("bar")
35
+ end
36
+
37
+ it "responds when in the correct state" do
38
+ subject.process("foo")
39
+
40
+ subject.should_receive(:respond).with("bar get", {})
41
+ subject.process("bar")
42
+ end
43
+ end
44
+
45
+ context "multiple plugins" do
46
+ class TestPlugin2 < Cora::Plugin
47
+
48
+ listen_for /test/ do
49
+ say "test2"
50
+ end
51
+
52
+ listen_for /bar/ do
53
+ say "bad bar"
54
+ end
55
+
56
+ end
57
+
58
+ before do
59
+ subject.plugins << TestPlugin2.new.tap { |plugin| plugin.manager = subject}
60
+ end
61
+
62
+ it "processes the plugins in order" do
63
+ subject.should_receive(:respond).with("test!", {})
64
+ subject.process("test")
65
+
66
+ subject.plugins.reverse!
67
+ subject.should_receive(:respond).with("test2", {})
68
+ subject.process("test")
69
+ end
70
+
71
+ it "when state is set, cora should ignore other plugins" do
72
+ subject.plugins.reverse! # So TestPlugin2's bar is checked first
73
+ subject.process("foo")
74
+ subject.should_receive(:respond).with("bar get", {})
75
+ subject.process("bar")
76
+ end
77
+ end
78
+
79
+ context "asking" do
80
+ # Now this is interesting. On further thought, we can't do
81
+ # answer = ask("What is your name?")
82
+ # since we need to relinquish the CPU somehow. Either we use blocks,
83
+ # but since we're on 1.9, we can use Fibers. I think?
84
+
85
+ it "gets input from the user and uses it intelligently" do
86
+ subject.should_receive(:respond).with("Who should I send it to?", prompt_for_response: true)
87
+ subject.process("send message")
88
+ subject.should_receive(:respond).with("Sending message to chendo", {})
89
+ subject.process("chendo")
90
+ end
91
+
92
+ it "can ask multiple questions" do
93
+ subject.should_receive(:respond).with("Question 1", prompt_for_response: true)
94
+ subject.process("ask multiple")
95
+
96
+ subject.should_receive(:respond).with("You said: Answer 1", {})
97
+ subject.should_receive(:respond).with("Question 2", prompt_for_response: true)
98
+ subject.process("Answer 1")
99
+
100
+ subject.should_receive(:respond).with("You said: Answer 2", {})
101
+ subject.should_receive(:respond).with("Question 3", prompt_for_response: true)
102
+ subject.process("Answer 2")
103
+
104
+ subject.should_receive(:respond).with("You said: Answer 3", {})
105
+ subject.process("Answer 3")
106
+ end
107
+
108
+ it "can ask questions with options" do
109
+ subject.should_receive(:respond).with("Question with option", {prompt_for_response: true, option: "option"})
110
+ subject.process("ask option")
111
+ subject.process("plamoni")
112
+
113
+ random_num = rand(1000000)
114
+ subject.should_receive(:respond).with("Question with option", {prompt_for_response: true, option: random_num})
115
+ subject.process("ask option #{random_num}")
116
+ subject.process("plamoni")
117
+ end
118
+ end
119
+
120
+ context "confirming" do
121
+ it "can confirm with affirmative responses" do
122
+ subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true)
123
+ subject.process("confirm something")
124
+ subject.should_receive(:respond).with("Confirmed", {})
125
+ subject.process("Yes")
126
+
127
+ subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true)
128
+ subject.process("confirm something")
129
+ subject.should_receive(:respond).with("Confirmed", {})
130
+ subject.process("Yeah")
131
+
132
+ subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true)
133
+ subject.process("confirm something")
134
+ subject.should_receive(:respond).with("Confirmed", {})
135
+ subject.process("Yep")
136
+
137
+ subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true)
138
+ subject.process("confirm something")
139
+ subject.should_receive(:respond).with("Confirmed", {})
140
+ subject.process("Yes please")
141
+ end
142
+
143
+ it "can deny with deny responses" do
144
+ subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true)
145
+ subject.process("confirm something")
146
+ subject.should_receive(:respond).with("Cancelled", {})
147
+ subject.process("No")
148
+
149
+ subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true)
150
+ subject.process("confirm something")
151
+ subject.should_receive(:respond).with("Cancelled", {})
152
+ subject.process("Nah")
153
+
154
+ subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true)
155
+ subject.process("confirm something")
156
+ subject.should_receive(:respond).with("Cancelled", {})
157
+ subject.process("Nope")
158
+
159
+ subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true)
160
+ subject.process("confirm something")
161
+ subject.should_receive(:respond).with("Cancelled", {})
162
+ subject.process("No thanks")
163
+ end
164
+
165
+ it "can call confirm recursively" do
166
+ subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true)
167
+ subject.process("nested confirm")
168
+ subject.should_receive(:respond).with("Confirmed", {})
169
+ subject.should_receive(:respond).with("What about inside itself?", prompt_for_response: true)
170
+ subject.process("Yes")
171
+ subject.should_receive(:respond).with("Confirmed2", {})
172
+ subject.process("Yes")
173
+ end
174
+
175
+ it "can call confirm sequentially" do
176
+ subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true).ordered
177
+ subject.process("sequential confirm")
178
+ subject.should_receive(:respond).with("Confirmed", {}).ordered
179
+ subject.should_receive(:respond).with("And a second time?", prompt_for_response: true).ordered
180
+ subject.process("Yes")
181
+ subject.should_receive(:respond).with("Cancelled2", {}).ordered
182
+ subject.should_receive(:respond).with("And a third time?", prompt_for_response: true).ordered
183
+ subject.process("No")
184
+ subject.should_receive(:respond).with("Confirmed3", {}).ordered
185
+ subject.process("Yes")
186
+ end
187
+
188
+ it "can reprompt if given invalid answer to confirmation" do
189
+ subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true)
190
+ subject.process("confirm something")
191
+ subject.should_receive(:respond).with("I'm sorry, I didn't understand that.", {}).ordered
192
+ subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true).ordered
193
+ subject.should_receive(:respond).with("Confirmed", {}).ordered
194
+ # subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true)
195
+ subject.process("Lamp")
196
+ subject.process("Yes")
197
+ end
198
+
199
+ it "can reprompt with custom message if given invalid answer to confirmation" do
200
+ subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true)
201
+ subject.process("confirm custom reprompt")
202
+ subject.should_receive(:respond).with("What you say!?", {}).ordered
203
+ subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true).ordered
204
+ subject.should_receive(:respond).with("Confirmed", {}).ordered
205
+ # subject.should_receive(:respond).with("Does confirm work?", prompt_for_response: true)
206
+ subject.process("Lamp")
207
+ subject.process("Yes")
208
+ end
209
+ end
210
+
211
+ context "captures" do
212
+ it "passes the captures to the block" do
213
+ subject.should_receive(:respond).with("Nice to meet you, Jackie Chan", {})
214
+ subject.process("my name is Jackie Chan")
215
+ end
216
+
217
+ it "lets you access the match_data for dynamic capture count" do
218
+ subject.should_receive(:respond).with("only repeat this", {})
219
+ subject.process("only repeat this, not this")
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH << File.dirname(__FILE__) + "/../lib"
2
+ require 'cora'
@@ -0,0 +1,96 @@
1
+
2
+ class TestPlugin < Cora::Plugin
3
+
4
+ listen_for /test/ do
5
+ say "test!"
6
+ end
7
+
8
+ listen_for /^options$/ do
9
+ say "test options", {option: "option"}
10
+ end
11
+
12
+ listen_for /^foo$/ do
13
+ say "foo"
14
+ set_state :waiting_for_bar
15
+ end
16
+
17
+ listen_for /send message/ do
18
+ receipent = ask "Who should I send it to?"
19
+ say "Sending message to #{receipent}"
20
+ end
21
+
22
+ listen_for /ask multiple/ do
23
+ answer = ask "Question 1"
24
+ say "You said: #{answer}"
25
+ answer = ask "Question 2"
26
+ say "You said: #{answer}"
27
+ answer = ask "Question 3"
28
+ say "You said: #{answer}"
29
+ end
30
+
31
+ listen_for /ask option[ ]?([0-9]*)/ do |numeric_option|
32
+ opt = "option"
33
+ opt = numeric_option.to_i if not numeric_option.empty?
34
+ answer = ask "Question with option", {option: opt}
35
+ end
36
+
37
+ listen_for /confirm something/ do
38
+ if confirm "Does confirm work?"
39
+ say "Confirmed"
40
+ else
41
+ say "Cancelled"
42
+ end
43
+ end
44
+
45
+ listen_for /confirm custom reprompt/ do
46
+ if confirm "Does confirm work?", unmatched_message: "What you say!?"
47
+ say "Confirmed"
48
+ else
49
+ say "Cancelled"
50
+ end
51
+ end
52
+
53
+
54
+ listen_for /nested confirm/ do
55
+ if confirm "Does confirm work?"
56
+ say "Confirmed"
57
+ if confirm "What about inside itself?"
58
+ say "Confirmed2"
59
+ else
60
+ say "Cancelled2"
61
+ end
62
+ else
63
+ say "Cancelled"
64
+ end
65
+ end
66
+
67
+ listen_for /sequential confirm/ do
68
+ if confirm "Does confirm work?"
69
+ say "Confirmed"
70
+ else
71
+ say "Cancelled"
72
+ end
73
+ if confirm "And a second time?"
74
+ say "Confirmed2"
75
+ else
76
+ say "Cancelled2"
77
+ end
78
+ if confirm "And a third time?"
79
+ say "Confirmed3"
80
+ else
81
+ say "Cancelled3"
82
+ end
83
+ end
84
+
85
+ listen_for /bar/, within_state: :waiting_for_bar do
86
+ say "bar get"
87
+ end
88
+
89
+ listen_for /my name is (\w+) (\w+)/i do |first_name, last_name|
90
+ say "Nice to meet you, #{first_name} #{last_name}"
91
+ end
92
+
93
+ listen_for /only repeat this/i do
94
+ say "#{match_data}"
95
+ end
96
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cora
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jack Chen (chendo)
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70356401591140 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70356401591140
25
+ - !ruby/object:Gem::Dependency
26
+ name: guard-rspec
27
+ requirement: &70356401590140 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70356401590140
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ requirement: &70356401588880 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70356401588880
47
+ - !ruby/object:Gem::Dependency
48
+ name: geocoder
49
+ requirement: &70356401588060 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70356401588060
58
+ description: Example description
59
+ email:
60
+ - ! '@chendo'
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - .gitignore
66
+ - .travis.yml
67
+ - Gemfile
68
+ - Guardfile
69
+ - Rakefile
70
+ - cora.gemspec
71
+ - lib/cora.rb
72
+ - lib/cora/location.rb
73
+ - lib/cora/plugin.rb
74
+ - lib/cora/version.rb
75
+ - spec/cora_spec.rb
76
+ - spec/spec_helper.rb
77
+ - spec/support/test_plugin.rb
78
+ homepage: ''
79
+ licenses: []
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project: cora
98
+ rubygems_version: 1.8.10
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: Example summary
102
+ test_files:
103
+ - spec/cora_spec.rb
104
+ - spec/spec_helper.rb
105
+ - spec/support/test_plugin.rb