natter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b2654979a551d969bac208c18667ce2c7c74d7fc
4
+ data.tar.gz: 5726246cb0c3fdde38783f6e4d77f97a46a3cc7c
5
+ SHA512:
6
+ metadata.gz: 89a96014d6139600ed0728d18dc030dc5f753e2e1a4676cd7dd522684b0fd0b11785c84661b192ba8cf69cfff84bbc9ec888b55983396e711fafb4179e70b6dd
7
+ data.tar.gz: f6fd7870a3f35019341f6403791fc96c1a94c35c34d95ed0d3e3a099d2cd41d1ca9fb3b0573bc075821ef063d5778046c2cdf9445ad57dd1d5a82baf46a3d0cb
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in natter.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Garry Pettet
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,39 @@
1
+ # Natter
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/natter`. 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 'natter'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install natter
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. 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]/natter.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,40 @@
1
+ module Natter
2
+ # Public: An entity is an attribute of an intent.
3
+ class Entity
4
+ attr_accessor :name, :type
5
+ attr_reader :value
6
+
7
+ # Public: Constructor.
8
+ #
9
+ # name - Must be unique within an individual intent.
10
+ # type - One of the pre-defined EntityType constants (default: 'generic')
11
+ # value - Will never be nil when generated by the parser but may be nil
12
+ # for some rule definitions (default: nil).
13
+ def initialize(name, type = EntityType::GENERIC, value = nil)
14
+ @name = name
15
+ @type = type
16
+ @value = value
17
+ end
18
+
19
+ # Public: Returns the value of this entity.
20
+ # If @type is `generic` then we just return @value, otherwise we will need
21
+ # to verify/compute the value to return.
22
+ #
23
+ # Returns object or nil (if invalid @value)
24
+ def value
25
+ case @type
26
+ when EntityType::TIME
27
+ return Chronic.parse(@value) # returns Time or nil
28
+ else
29
+ return @value
30
+ end
31
+ end
32
+ end
33
+
34
+ # Public: Exists only to expose its constants which are used to define
35
+ # Entity types.
36
+ class EntityType
37
+ GENERIC = 'generic'
38
+ TIME = 'time'
39
+ end
40
+ end
@@ -0,0 +1,28 @@
1
+ module Natter
2
+ # Public: Represents an intent derived by the parser from an utterance.
3
+ class Intent
4
+ # The string name of this intent.
5
+ attr_accessor :name
6
+ # The name of the skill that this intent belongs to.
7
+ attr_accessor :skill
8
+ # How confident the parser is that this is the correct intent (from 0.0 - 1.0).
9
+ attr_accessor :confidence
10
+ # An array of entities discovered in the utterance. May be empty.
11
+ attr_accessor :entities
12
+
13
+ # Public: Constructor.
14
+ #
15
+ # name - The String name of this intent. Does not have to be unique.
16
+ # skill - The name of the skill that this intent belongs to. Defaults
17
+ # to the global namespace/skill (default: '')
18
+ # confidence - Float value between 0-1 reflects how confident the parser is
19
+ # that this intent is correct (default: 1.0).
20
+ # entities - An array of Natter::Entity objects (default: nil).
21
+ def initialize(name, skill = '', confidence = 1.0, entities = nil)
22
+ @name = name
23
+ @skill = skill
24
+ @confidence = confidence
25
+ @entities = entities || []
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,306 @@
1
+ module Natter
2
+ # Public: The parser is the main workhorse, responsible for deriving the
3
+ # intent from an utterance.
4
+ class Parser
5
+ def initialize
6
+ @known_utterances = Hash.new # key = utterance, value = Intent
7
+ @contractions = init_contractions # key = contraction, value = expansion
8
+ @intent_cache = Hash.new # key = utterance, value = Intent
9
+ @rules = Hash.new # key = rule regex pattern, value = Rule object
10
+ end
11
+ # Public: Adds a regex-based Rule to the parser.
12
+ #
13
+ # rule - The Natter::Rule to add.
14
+ def add_rule(rule)
15
+ raise ArgumentError, "Expected Natter::Rule but got `#{rule}`" unless rule.is_a?(Rule)
16
+ if @rules.has_key?(rule.pattern)
17
+ raise ArgumentError, "Regex pattern already defined by " +\
18
+ "#{@rules[rule.pattern].identifier}: #{rule.pattern}"
19
+ end
20
+ @rules[rule.pattern] = rule
21
+ end
22
+
23
+ # Public: Adds a pre-computed utterance/intent pair to the parser.
24
+ # Used when a specific utterance(s) match a predetermined intent. This saves
25
+ # overhead as there is no regex processing required. These utterances are
26
+ # evaluated before the regex rules.
27
+ # Multiple examples can be added at once.
28
+ # Adding an utterance that already exists will overwrite the old one.
29
+ #
30
+ # example - A Hash where:
31
+ # key = A single utterance or array of utterances
32
+ # value = Natter::Intent
33
+ #
34
+ # Examples
35
+ #
36
+ # add_utterance('hello' => Intent.new('greeting'))
37
+ # add_utterance(['what time is it', 'what is the time'] => Intent.new('currentTime'))
38
+ # add_utterance(
39
+ # 'night night' => Intent.new('goodnight'),
40
+ # 'lock the door' => Intent.new('lock')
41
+ # )
42
+ #
43
+ # Returns nothing.
44
+ def add_utterance(example)
45
+ raise ArgumentError, "Expected {utterance => Intent} or {[utterances] => Intent}" unless example.is_a?(Hash)
46
+ example.map do |utterance, intent|
47
+ if utterance.kind_of?(Array)
48
+ utterance.each { |phrase| @known_utterances[phrase] = intent }
49
+ else
50
+ @known_utterances[utterance] = intent
51
+ end
52
+ end
53
+ end
54
+
55
+ # Public: Analyse an utterance and return any matching intents.
56
+ #
57
+ # utterance - The natural language string to analyse
58
+ # use_cache - If true then we will check a cache of previously returned
59
+ # utterance/intent pairs to return rather than re-parsing.
60
+ # (default: true)
61
+ #
62
+ # Returns an Intent, an array of Intents or nil if the intent cannot be
63
+ # determined.
64
+ def parse(text, use_cache = true)
65
+ raise ArgumentError, "Cannot parse thin air!" unless text.length > 0
66
+
67
+ # Store the original string for later
68
+ original = text
69
+
70
+ # Tidy up the string for parsing
71
+ utterance = purify(original)
72
+
73
+ if @known_utterances.has_key?(utterance)
74
+ return @known_utterances[utterance]
75
+ end
76
+
77
+ if use_cache && @intent_cache.has_key?(utterance)
78
+ return @intent_cache[utterance]
79
+ end
80
+
81
+ intents = []
82
+ @rules.each do |pattern, rule|
83
+ m = utterance.match(rule.pattern)
84
+ if m == nil
85
+ next
86
+ else
87
+ intent = intent_from_match(rule, m)
88
+ if intent then intents << intent end
89
+ end
90
+ end
91
+
92
+ if intents.empty? then return nil end
93
+
94
+ # Calculate the confidence of each intent
95
+ intents = determine_confidences(intents)
96
+
97
+ # Cache the matches
98
+ @intent_cache[utterance] = intents
99
+
100
+ return intents
101
+ end
102
+
103
+ # Internal: Determines the confidence of each intent in the passed array
104
+ # and then sorts them based on the calculated confidence values.
105
+ # Basically, if we have more than one intent then whichever intent has the
106
+ # greatest number of entities is likely to be the best match.
107
+ #
108
+ # intents - An array of Intent objects.
109
+ #
110
+ # Returns a sorted (by confidence) array of Intent objects. Mutates original
111
+ # array.
112
+ def determine_confidences(intents)
113
+ # Handle where there's only one matching intent
114
+ if intents.length == 1
115
+ intents[0].confidence = 1.0
116
+ return intents
117
+ end
118
+
119
+ # First determine the total number of entities in any of the intents
120
+ total = 0
121
+ intents.each { |i| total += i.entities.length }
122
+
123
+ if total == 0
124
+ # Edge case: all matching intents contain no entities.
125
+ # Assign equal confidence to all intents
126
+ result = intents.map do |i|
127
+ i.confidence = 1.0/intents.length
128
+ i # return this intent from the map
129
+ end
130
+ else
131
+ result = intents.map do |i|
132
+ i.confidence = i.entities.length.to_f/total
133
+ i # return this intent from the map
134
+ end
135
+ end
136
+
137
+ # Sort the array by descending confidence values
138
+ result.sort_by { |i| i.confidence }.reverse
139
+ end
140
+
141
+ # Internal: Converts a positive regex match and returns an Intent object.
142
+ # Note that the confidence is set to 0 as it will be determined later.
143
+ #
144
+ # rule - The Rule definining this intent.
145
+ # m - The positive regex match.
146
+ #
147
+ # Returns Intent.
148
+ def intent_from_match(rule, m)
149
+ if m.named_captures.empty?
150
+ # No capture groups found. Double-check the rule doesn't need any entities
151
+ if rule.entities.empty?
152
+ return Intent.new(rule.name, rule.skill, 0)
153
+ else
154
+ # Expected at least one entity. This can't be a valid match then
155
+ return nil
156
+ end
157
+ else
158
+ # Found some entities. Check they match up with the rule
159
+ intent = Intent.new(rule.name, rule.skill, 0)
160
+ rule.entities.each do |entity|
161
+ if m.named_captures.has_key?(entity.name)
162
+ e = Entity.new(entity.name, entity.type, m.named_captures[entity.name].strip)
163
+ intent.entities << e
164
+ else
165
+ # Found a named capture group that doesn't match an entity defined
166
+ # in the rule
167
+ return nil
168
+ end
169
+ end
170
+ if intent.entities.length != m.named_captures.length
171
+ # Found some entity matches but not all
172
+ return nil
173
+ else
174
+ return intent
175
+ end
176
+ end
177
+ end
178
+
179
+ # Internal: Tidies up the passed string to remove unnecessary characters
180
+ # and replace ambiguous phrases such as contractions.
181
+ #
182
+ # t - The string to purify.
183
+ #
184
+ # Examples
185
+ #
186
+ # str = "what're you doing?!"
187
+ # str = purify(str)
188
+ # # => "what are you doing"
189
+ def purify(t)
190
+ t = expand_contractions(t)
191
+ t = strip_trailing_punctuation(t)
192
+ end
193
+
194
+ # Internal: Removes trailing '?' and '!' from the passed string.
195
+ #
196
+ # t - The string from which to remove superfluous trailing punctuation.
197
+ def strip_trailing_punctuation(t)
198
+ t.sub(/[?!]+\z/, '')
199
+ end
200
+
201
+ # Private: Initialise the @contractions Hash. Only needs doing once.
202
+ # OPTIMISE: Perhaps move these values to an editable text file?
203
+ def init_contractions
204
+ {
205
+ "that's" => "that is",
206
+ "aren't" => "are not",
207
+ "can't" => "can not",
208
+ "could've" => "could have",
209
+ "couldn't" => "could not",
210
+ "didn't" => "did not",
211
+ "doesn't" => "does not",
212
+ "don't" => "do not",
213
+ "dunno" => "do not know",
214
+ "gonna" => "going to",
215
+ "gotta" => "got to",
216
+ "hadn't" => "had not",
217
+ "hasn't" => "has not",
218
+ "haven't" => "have not",
219
+ "he'd" => "he had",
220
+ "he'll" => "he will",
221
+ "he's" => "he is",
222
+ "how'd" => "how would",
223
+ "how'll" => "how will",
224
+ "how're" => "how are",
225
+ "how's" => "how is",
226
+ "i'd" => "i would",
227
+ "i'll" => "i will",
228
+ "i'm" => "i am",
229
+ "i've" => "i have",
230
+ "isn't" => "is not",
231
+ "it'd" => "it would",
232
+ "it'll" => "it will",
233
+ "it's" => "it is",
234
+ "mightn't" => "might not",
235
+ "might've" => "might have",
236
+ "mustn't" => "must not",
237
+ "must've" => "must have",
238
+ "ol'" => "old",
239
+ "oughtn't" => "ought not",
240
+ "shan't" => "shall not",
241
+ "she'd" => "she would",
242
+ "she'll" => "she will",
243
+ "she's" => "she is",
244
+ "should've" => "should have",
245
+ "shouldn't" => "should not",
246
+ "somebody's" => "somebody is",
247
+ "someone'll" => "someone will",
248
+ "someone's" => "someone is",
249
+ "something'll" => "something will",
250
+ "something's" => "something is",
251
+ "that'll" => "that will",
252
+ "that'd" => "that would",
253
+ "there'd" => "there had",
254
+ "there's" => "there is",
255
+ "they'd" => "they would",
256
+ "they'll" => "they will",
257
+ "they're" => "they are",
258
+ "they've" => "they have",
259
+ "wasn't" => "was not",
260
+ "we'd" => "we had",
261
+ "we'll" => "we will",
262
+ "we're" => "we are",
263
+ "we've" => "we have",
264
+ "weren't" => "were not",
265
+ "what'd" => "what did",
266
+ "what'll" => "what will",
267
+ "what're" => "what are",
268
+ "what's" => "what is",
269
+ "what've" => "what have",
270
+ "when's" => "when is",
271
+ "where'd" => "where did",
272
+ "where's" => "where is",
273
+ "where've" => "where have",
274
+ "who'd" => "who would",
275
+ "who'll" => "who will",
276
+ "who's" => "who is",
277
+ "why'd" => "why did",
278
+ "why're" => "why are",
279
+ "why's" => "why is",
280
+ "won't" => "will not",
281
+ "won't've" => "will not have",
282
+ "would've" => "would have",
283
+ "wouldn't" => "would not",
284
+ "you'd" => "you would",
285
+ "you'll" => "you will",
286
+ "you're" => "you are",
287
+ "you've" => "you have"
288
+ }
289
+ end
290
+
291
+ # Expand the contractions within this string.
292
+ #
293
+ # Examples
294
+ #
295
+ # t = "I'm hot"
296
+ # t.expand_contractions!
297
+ # # => "I am hot"
298
+ def expand_contractions(text)
299
+ result = ''
300
+ text.strip.split(' ').each do |word|
301
+ result = result + @contractions.fetch(word, word) + ' '
302
+ end
303
+ return result.strip
304
+ end
305
+ end
306
+ end
@@ -0,0 +1,93 @@
1
+ module Natter
2
+
3
+ # Public: The Rule class represents a rule which defines an in intent that
4
+ # occurs within an utterance.
5
+
6
+ class Rule
7
+
8
+ # The string name of this rule. Defferent rules can share the same name.
9
+ attr_accessor :name
10
+ # The regex pattern to search for within the an utterance.
11
+ attr_accessor :pattern
12
+ # An array of Entity objects this rule expects. May be empty.
13
+ attr_accessor :entities
14
+ # The name of the skill that this rule belongs to. May be '' if global.
15
+ attr_accessor :skill
16
+
17
+ # Public: Constructor.
18
+ #
19
+ # name - The name of this intent. Names do not need to be unique
20
+ # within a parser.
21
+ # pattern - The regular expression that matches the contents of an
22
+ # utterance. If this intent contains entities then the regex
23
+ # must capture the entities within the utterance as named
24
+ # capture groups. See examples below. Default: //
25
+ # skill - The name of the skill that this rule belongs to. If none is
26
+ # specified then we'll assume it's a global rule (default: '').
27
+ # entities - Optional array of Entity objects in the form:
28
+ # Each entity must have a correspondingly named capture group
29
+ # within the regex.
30
+ def initialize(name, pattern = //, skill = '', *entities)
31
+ @name = name
32
+ @pattern = pattern
33
+ @skill = skill
34
+ @entities = entities || []
35
+ end
36
+
37
+ # Public: A convenience method to permit prettier building of rules.
38
+ # Returns the Rule back to the calling method to allow chaining.
39
+ #
40
+ # pattern - The regex pattern to assign to this rule
41
+ #
42
+ # Examples
43
+ #
44
+ # definition = Rule.new('reminder').regex(/remind me to/i)
45
+ # definition = Rule.new('reminder')\
46
+ # .regex(/remind me/)\
47
+ # .needs_entity('task', EntityType::GENERIC)
48
+ def regex(pattern)
49
+ @pattern = pattern
50
+ return self
51
+ end
52
+
53
+ # Public: Returns this rule's identifier. An identifier is in the format:
54
+ # skill.name unless skill = '' in which case we return global.name
55
+ #
56
+ # Returns string
57
+ def identifier
58
+ "#{@skill == '' ? 'global' : @skill}.#{@name}"
59
+ end
60
+
61
+ # Public: A convenience method to permit prettier building of rules.
62
+ # Returns the Rule back to the calling method to allowing chaining.
63
+ #
64
+ # skill - The name of the skill that this this rule belongs to.
65
+ #
66
+ # Example
67
+ #
68
+ # def = Rule.new('play').belongs_to('sonos')
69
+ def belongs_to(skill)
70
+ @skill = skill
71
+ return self
72
+ end
73
+
74
+ # Public: A nicer syntax method for adding an Entity to this rule.
75
+ #
76
+ # name - The name of this entity. Must be unique within this rule definition
77
+ # and must match a named capture group within the definition's regex.
78
+ #
79
+ # type - The entity's type. Should be one of the predefined constants in
80
+ # Natter::EntityType
81
+ #
82
+ # Example
83
+ #
84
+ # definition.needs_entity('artist', EntityType::GENERIC)
85
+ def needs_entity(name, type)
86
+ @entities.each do |entity|
87
+ return if entity.name == name # this rule already contains this entity
88
+ end
89
+ @entities << Entity.new(name, type)
90
+ return self
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,3 @@
1
+ module Natter
2
+ VERSION = "0.1.0"
3
+ end
data/lib/natter.rb ADDED
@@ -0,0 +1,16 @@
1
+ # Natter is a rules-based natural language intent parser.
2
+ # Loads the files required by Natter.
3
+
4
+ # Third party dependencies
5
+ require 'chronic'
6
+
7
+ # # Modules/classes
8
+ require_relative 'natter/entity'
9
+ require_relative 'natter/intent'
10
+ require_relative 'natter/parser'
11
+ require_relative 'natter/rule'
12
+ require_relative 'natter/version'
13
+
14
+ module Natter
15
+
16
+ end
data/natter.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "natter/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "natter"
8
+ spec.version = Natter::VERSION
9
+ spec.authors = ["Garry Pettet"]
10
+ spec.email = ["contact@garrypettet.com"]
11
+
12
+ spec.summary = 'Rules-based natural language intent parser.'
13
+ spec.description = 'A rules-based natural language intent parser written in pure Ruby.'
14
+ spec.homepage = 'https://github.com/BarnabyAI/natter'
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_runtime_dependency "chronic", "~> 0.10"
25
+ spec.add_runtime_dependency "oj", "~> 3.3"
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.15"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: natter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Garry Pettet
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-10-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: chronic
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.10'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: oj
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.15'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.15'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ description: A rules-based natural language intent parser written in pure Ruby.
70
+ email:
71
+ - contact@garrypettet.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - Gemfile
77
+ - LICENSE.txt
78
+ - README.md
79
+ - Rakefile
80
+ - lib/natter.rb
81
+ - lib/natter/entity.rb
82
+ - lib/natter/intent.rb
83
+ - lib/natter/parser.rb
84
+ - lib/natter/rule.rb
85
+ - lib/natter/version.rb
86
+ - natter.gemspec
87
+ homepage: https://github.com/BarnabyAI/natter
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.6.13
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Rules-based natural language intent parser.
111
+ test_files: []