natter 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.
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: []