attentive 0.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +10 -0
- data/attentive.gemspec +31 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/data/contractions.tsv +134 -0
- data/data/slang.tsv +19 -0
- data/lib/attentive/composite_entity.rb +33 -0
- data/lib/attentive/cursor.rb +28 -0
- data/lib/attentive/entities/integer.rb +5 -0
- data/lib/attentive/entities/relative_date.rb +44 -0
- data/lib/attentive/entity.rb +65 -0
- data/lib/attentive/errors.rb +7 -0
- data/lib/attentive/listener.rb +55 -0
- data/lib/attentive/listener_collection.rb +45 -0
- data/lib/attentive/match.rb +22 -0
- data/lib/attentive/matcher.rb +82 -0
- data/lib/attentive/message.rb +24 -0
- data/lib/attentive/phrase.rb +19 -0
- data/lib/attentive/text.rb +57 -0
- data/lib/attentive/token.rb +58 -0
- data/lib/attentive/tokenizer.rb +161 -0
- data/lib/attentive/tokens/any_of.rb +23 -0
- data/lib/attentive/tokens/emoji.rb +17 -0
- data/lib/attentive/tokens/me.rb +17 -0
- data/lib/attentive/tokens/punctuation.rb +13 -0
- data/lib/attentive/tokens/regexp.rb +27 -0
- data/lib/attentive/tokens/whitespace.rb +22 -0
- data/lib/attentive/tokens/word.rb +8 -0
- data/lib/attentive/tokens.rb +45 -0
- data/lib/attentive/version.rb +3 -0
- data/lib/attentive.rb +20 -0
- metadata +206 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e5d4df859acd5086ec5d8144696af41186bdc33f
|
4
|
+
data.tar.gz: 2d5d5fb36e14abd9c2ae64e5cc42407238f97550
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f96291d7ea9b07676a59a958c6a42cc7ef5fad0e0f96a21eba177c6a7c763b052d49dd032f82431612bf89ee75dc9cfb1e068eab8ea1f797878b92d77488be4f
|
7
|
+
data.tar.gz: 626d7dc7de65850872234c5baa4ad921a2d661d5d50dc9e54539435dff1d1dc5b628413a86134ae2d680a1e88e794eed7bd1ea1dcb5dcafc96303819586f415b
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Bob Lail
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Attentive
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/attentive`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'attentive'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install attentive
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/attentive.
|
36
|
+
|
37
|
+
|
38
|
+
## License
|
39
|
+
|
40
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
41
|
+
|
data/Rakefile
ADDED
data/attentive.gemspec
ADDED
@@ -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 'attentive/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "attentive"
|
8
|
+
spec.version = Attentive::VERSION
|
9
|
+
spec.authors = ["Bob Lail"]
|
10
|
+
spec.email = ["bob.lailfamily@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A library for matching messages to natural-language listeners}
|
13
|
+
spec.homepage = "https://github.com/houston/attentive"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "thread_safe"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
26
|
+
spec.add_development_dependency "pry"
|
27
|
+
spec.add_development_dependency "minitest-reporters"
|
28
|
+
spec.add_development_dependency "minitest-reporters-turn_reporter"
|
29
|
+
spec.add_development_dependency "shoulda-context"
|
30
|
+
spec.add_development_dependency "timecop"
|
31
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "attentive"
|
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
|
data/bin/setup
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# List of English Contractions
|
2
|
+
# https://en.wikipedia.org/wiki/Wikipedia:List_of_English_contractions
|
3
|
+
|
4
|
+
ain't am not
|
5
|
+
aren't are not
|
6
|
+
can't can not
|
7
|
+
cannot can not
|
8
|
+
could've could have
|
9
|
+
couldn't could not
|
10
|
+
couldn't've could not have
|
11
|
+
didn't did not
|
12
|
+
doesn't does not
|
13
|
+
don't do not
|
14
|
+
hadn't had not
|
15
|
+
hadn't've had not have
|
16
|
+
hasn't has not
|
17
|
+
haven't have not
|
18
|
+
he'd he had he would
|
19
|
+
he'd've he would have
|
20
|
+
he'll he will he shall
|
21
|
+
he's he is he has
|
22
|
+
he'sn't he is not he has not
|
23
|
+
how'd how did how would
|
24
|
+
how'll how will
|
25
|
+
how's how is how has how does
|
26
|
+
I'd I would I had
|
27
|
+
I'd've I would have
|
28
|
+
I'll I shall I will
|
29
|
+
I'm I am
|
30
|
+
I've I have
|
31
|
+
I'ven't I have not
|
32
|
+
isn't is not
|
33
|
+
it'd it would it had
|
34
|
+
it'd've it would have
|
35
|
+
it'll it will it shall
|
36
|
+
it's it is it has
|
37
|
+
it'sn't it is not it has not
|
38
|
+
let's let us
|
39
|
+
ma'am madam
|
40
|
+
mightn't might not
|
41
|
+
mightn't've might not have
|
42
|
+
might've might have
|
43
|
+
mustn't must not
|
44
|
+
must've must have
|
45
|
+
needn't need not
|
46
|
+
not've not have
|
47
|
+
o'clock of the clock
|
48
|
+
oughtn't ought not
|
49
|
+
shan't shall not
|
50
|
+
she'd she had she would
|
51
|
+
she'd've she would have
|
52
|
+
she'll she shall she will
|
53
|
+
she's she is she has
|
54
|
+
she'sn't she is not she has not
|
55
|
+
should've should have
|
56
|
+
shouldn't should not
|
57
|
+
shouldn't've should not have
|
58
|
+
somebody'd somebody had somebody would
|
59
|
+
somebody'd've somebody would have
|
60
|
+
somebody'dn't've somebody would not have
|
61
|
+
somebody'll somebody shall somebody will
|
62
|
+
somebody's somebody is somebody has
|
63
|
+
someone'd someone had someone would
|
64
|
+
someone'd've someone would have
|
65
|
+
someone'll someone shall someone will
|
66
|
+
someone's someone is someone has
|
67
|
+
something'd something had something would
|
68
|
+
something'd've something would have
|
69
|
+
something'll something shall something will
|
70
|
+
something's something is something has
|
71
|
+
that'll that will
|
72
|
+
that's that is that has
|
73
|
+
there'd there had there would
|
74
|
+
there'd've there would have
|
75
|
+
there're there are
|
76
|
+
there's there is there has
|
77
|
+
they'd they would they had
|
78
|
+
they'dn't they would not
|
79
|
+
they'dn't've they would not have
|
80
|
+
they'd've they would have
|
81
|
+
they'd'ven't they would have not
|
82
|
+
they'll they shall they will
|
83
|
+
they'lln't've they will not have
|
84
|
+
they'll'ven't they will have not
|
85
|
+
they're they are
|
86
|
+
they've they have
|
87
|
+
they'ven't they have not
|
88
|
+
'tis it is
|
89
|
+
'twas it was
|
90
|
+
wasn't was not
|
91
|
+
we'd we had we would
|
92
|
+
we'd've we would have
|
93
|
+
we'dn't've we would not have
|
94
|
+
we'll we will
|
95
|
+
we'lln't've we will not have
|
96
|
+
we're we are
|
97
|
+
we've we have
|
98
|
+
weren't were not
|
99
|
+
what'll what shall what will
|
100
|
+
what're what are
|
101
|
+
what's what is what does what has
|
102
|
+
what've what have
|
103
|
+
when's when is when has
|
104
|
+
where'd where did
|
105
|
+
where's where is where does where has
|
106
|
+
where've where have
|
107
|
+
who'd who would who had
|
108
|
+
who'd've who would have
|
109
|
+
who'll who shall who will
|
110
|
+
who're who are
|
111
|
+
who's who is who has
|
112
|
+
who've who have
|
113
|
+
why'll why will
|
114
|
+
why're why are
|
115
|
+
why's why is why has
|
116
|
+
won't will not
|
117
|
+
won't've will not have
|
118
|
+
would've would have
|
119
|
+
wouldn't would not
|
120
|
+
wouldn't've would not have
|
121
|
+
y'all you all
|
122
|
+
y'all'd've you all would have
|
123
|
+
y'all'dn't've you all would not have
|
124
|
+
y'all'll you all will
|
125
|
+
y'all'lln't you all will not
|
126
|
+
y'all'll've you all will have
|
127
|
+
y'all'll'ven't you all will have not
|
128
|
+
you'd you had you would
|
129
|
+
you'd've you would have
|
130
|
+
you'll you shall you will
|
131
|
+
you're you are
|
132
|
+
you'ren't you are not
|
133
|
+
you've you have
|
134
|
+
you'ven't you have not
|
data/data/slang.tsv
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Slang / Shorthand
|
2
|
+
|
3
|
+
bye goodbye
|
4
|
+
gonna going to
|
5
|
+
hi hello
|
6
|
+
ol' old
|
7
|
+
'sup what is up
|
8
|
+
thanks thank you
|
9
|
+
wanna want to
|
10
|
+
mon monday
|
11
|
+
tue tuesday
|
12
|
+
tues tuesday
|
13
|
+
wed wednesday
|
14
|
+
thu thursday
|
15
|
+
thur thursday
|
16
|
+
thurs thursday
|
17
|
+
fri friday
|
18
|
+
sat saturday
|
19
|
+
sun sunday
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "attentive/entity"
|
2
|
+
|
3
|
+
module Attentive
|
4
|
+
class CompositeEntity < Entity
|
5
|
+
attr_reader :entities
|
6
|
+
|
7
|
+
@entities = []
|
8
|
+
class << self
|
9
|
+
attr_accessor :entities
|
10
|
+
|
11
|
+
def define(entity_name, *entities)
|
12
|
+
entity_klass = Class.new(Attentive::CompositeEntity)
|
13
|
+
entity_klass.token_name = entity_name
|
14
|
+
entity_klass.entities = entities.map { |entity| Entity[entity] }
|
15
|
+
Entity.register! entity_name, entity_klass
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(*args)
|
20
|
+
super
|
21
|
+
@entities = self.class.entities.map { |entity_klass| entity_klass.new(variable_name) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def matches?(cursor)
|
25
|
+
entities.each do |entity|
|
26
|
+
match = entity.matches?(cursor.dup)
|
27
|
+
return match if match
|
28
|
+
end
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Attentive
|
2
|
+
class Cursor
|
3
|
+
attr_reader :tokens, :pos
|
4
|
+
|
5
|
+
def initialize(tokens, pos=0)
|
6
|
+
@tokens = tokens
|
7
|
+
@pos = pos
|
8
|
+
end
|
9
|
+
|
10
|
+
def peek
|
11
|
+
tokens[pos]
|
12
|
+
end
|
13
|
+
|
14
|
+
def pop
|
15
|
+
@pos += 1
|
16
|
+
tokens[pos - 1]
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
tokens[pos..-1].join
|
21
|
+
end
|
22
|
+
|
23
|
+
def offset
|
24
|
+
peek.pos
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "attentive/entity"
|
2
|
+
require "date"
|
3
|
+
|
4
|
+
weekday_regexp = "(?<weekday>sunday|monday|tuesday|wednesday|thursday|friday|saturday)"
|
5
|
+
Attentive::Entity.define :"relative-date",
|
6
|
+
"today",
|
7
|
+
"tomorrow",
|
8
|
+
"yesterday",
|
9
|
+
weekday_regexp,
|
10
|
+
"next #{weekday_regexp}",
|
11
|
+
"last #{weekday_regexp}" do |match|
|
12
|
+
|
13
|
+
today = Date.today
|
14
|
+
|
15
|
+
next_wday = lambda do |wday|
|
16
|
+
days_until_wday = wday - today.wday
|
17
|
+
days_until_wday += 7 if days_until_wday < 0
|
18
|
+
today + days_until_wday
|
19
|
+
end
|
20
|
+
|
21
|
+
if match.matched?("weekday")
|
22
|
+
date = case weekday = match["weekday"]
|
23
|
+
when /^sun/ then next_wday[0]
|
24
|
+
when /^mon/ then next_wday[1]
|
25
|
+
when /^tue/ then next_wday[2]
|
26
|
+
when /^wed/ then next_wday[3]
|
27
|
+
when /^thu/ then next_wday[4]
|
28
|
+
when /^fri/ then next_wday[5]
|
29
|
+
when /^sat/ then next_wday[6]
|
30
|
+
else raise NotImplementedError, "Unrecognize weekday: #{weekday.inspect}"
|
31
|
+
end
|
32
|
+
|
33
|
+
date += 7 if match.to_s.start_with?("next")
|
34
|
+
date -= 7 if match.to_s.start_with?("last")
|
35
|
+
date
|
36
|
+
else
|
37
|
+
case match.to_s
|
38
|
+
when "today" then today
|
39
|
+
when "tomorrow" then today + 1
|
40
|
+
when "yesterday" then today - 1
|
41
|
+
else raise NotImplementedError, "Unrecognize match: #{match.inspect}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "attentive/tokenizer"
|
2
|
+
require "attentive/errors"
|
3
|
+
|
4
|
+
module Attentive
|
5
|
+
class Entity < Token
|
6
|
+
attr_reader :variable_name
|
7
|
+
|
8
|
+
|
9
|
+
@entities = {}
|
10
|
+
class << self
|
11
|
+
attr_accessor :phrases
|
12
|
+
attr_accessor :token_name
|
13
|
+
|
14
|
+
def [](entity_name)
|
15
|
+
@entities.fetch(entity_name)
|
16
|
+
rescue KeyError
|
17
|
+
raise Attentive::UndefinedEntityError.new("Undefined Entity #{entity_name.inspect}")
|
18
|
+
end
|
19
|
+
|
20
|
+
def define(entity_name, *phrases, &block)
|
21
|
+
entity_klass = Class.new(Attentive::Entity)
|
22
|
+
entity_klass.token_name = entity_name
|
23
|
+
entity_klass.phrases = phrases.map do |phrase|
|
24
|
+
Attentive::Tokenizer.tokenize(phrase, entities: true, regexps: true, ambiguous: false)
|
25
|
+
end
|
26
|
+
entity_klass.send :define_method, :_value_from_match, &block
|
27
|
+
register! entity_name, entity_klass
|
28
|
+
end
|
29
|
+
|
30
|
+
def register!(entity_name, entity_klass)
|
31
|
+
# TODO: raise already registered error
|
32
|
+
@entities[entity_name] = entity_klass
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
def initialize(variable_name, pos=0)
|
40
|
+
@variable_name = variable_name.to_sym
|
41
|
+
super pos
|
42
|
+
end
|
43
|
+
|
44
|
+
def ==(other)
|
45
|
+
self.class == other.class && self.variable_name == other.variable_name
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
"{{#{variable_name}:#{self.class.token_name}}}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def entity?
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def matches?(cursor)
|
57
|
+
self.class.phrases.each do |phrase|
|
58
|
+
match = Attentive::Matcher.new(phrase, cursor.dup).match!
|
59
|
+
return { variable_name => _value_from_match(match) } if match
|
60
|
+
end
|
61
|
+
false
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "attentive/text"
|
2
|
+
require "attentive/tokenizer"
|
3
|
+
require "set"
|
4
|
+
|
5
|
+
module Attentive
|
6
|
+
class Listener
|
7
|
+
attr_reader :phrases
|
8
|
+
|
9
|
+
def initialize(listeners, args, callback)
|
10
|
+
options = args.last.is_a?(::Hash) ? args.pop : {}
|
11
|
+
|
12
|
+
# Ugh!
|
13
|
+
context_options = options.fetch(:context, {})
|
14
|
+
@required_contexts = context_options.fetch(:in, %i{conversation})
|
15
|
+
@required_contexts = [] if @required_contexts == :any
|
16
|
+
@required_contexts = Set[*@required_contexts]
|
17
|
+
@prohibited_contexts = context_options.fetch(:not_in, %i{quotation})
|
18
|
+
@prohibited_contexts = Set[*@prohibited_contexts]
|
19
|
+
|
20
|
+
@listeners = listeners
|
21
|
+
@callback = callback
|
22
|
+
@phrases = tokenize_phrases!(args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def matches_context?(message)
|
26
|
+
return false unless message.contexts.superset? @required_contexts
|
27
|
+
return false unless message.contexts.disjoint? @prohibited_contexts
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop_listening!
|
32
|
+
listeners.delete self
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def call(e)
|
37
|
+
@callback.call(e)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
attr_reader :listeners
|
42
|
+
|
43
|
+
def tokenize_phrases!(phrases)
|
44
|
+
phrases.map do |phrase|
|
45
|
+
tokenize_phrase!(phrase)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def tokenize_phrase!(phrase)
|
50
|
+
# should this be {regexps: false}?
|
51
|
+
Attentive::Tokenizer.tokenize(phrase, entities: true, regexps: true, ambiguous: false)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "thread_safe"
|
2
|
+
require "delegate"
|
3
|
+
require "attentive/cursor"
|
4
|
+
require "attentive/listener"
|
5
|
+
require "attentive/matcher"
|
6
|
+
|
7
|
+
module Attentive
|
8
|
+
class ListenerCollection < SimpleDelegator
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super ThreadSafe::Array.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def listen_for(*args, &block)
|
15
|
+
Listener.new(self, args, block).tap do |listener|
|
16
|
+
push listener
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def overhear(*args, &block)
|
21
|
+
Listener.new(self, args, block).tap do |listener|
|
22
|
+
push listener
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
def hear(message)
|
29
|
+
listeners = select { |listener| listener.matches_context?(message) }
|
30
|
+
|
31
|
+
# Listen for any known phrase, starting with any token in the message.
|
32
|
+
matches = []
|
33
|
+
message.tokens.each_with_index do |token, i|
|
34
|
+
listeners.each do |listener|
|
35
|
+
listener.phrases.each do |phrase|
|
36
|
+
match = Attentive::Matcher.new(phrase, Cursor.new(message.tokens, i)).match!
|
37
|
+
matches.push match if match
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
matches
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Attentive
|
2
|
+
class Match
|
3
|
+
|
4
|
+
def initialize(phrase, attributes={})
|
5
|
+
@phrase = phrase.to_s
|
6
|
+
@match_data = attributes.fetch(:match_data, {})
|
7
|
+
end
|
8
|
+
|
9
|
+
def matched?(variable_name)
|
10
|
+
@match_data.key?(variable_name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](variable_name)
|
14
|
+
@match_data.fetch(variable_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@phrase
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|