natural 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -10,8 +10,9 @@ gem 'activesupport'
10
10
  group :development do
11
11
  gem "rspec", "~> 2.8.0"
12
12
  gem "rdoc", "~> 3.12"
13
- gem "cucumber", ">= 0"
13
+ gem "cucumber"
14
14
  gem "bundler", "~> 1.0.0"
15
15
  gem "jeweler", "~> 1.8.3"
16
- gem "simplecov", ">= 0"
16
+ gem "simplecov"
17
+ gem "pry"
17
18
  end
data/Gemfile.lock CHANGED
@@ -5,6 +5,7 @@ GEM
5
5
  i18n (~> 0.6)
6
6
  multi_json (~> 1.0)
7
7
  builder (3.0.0)
8
+ coderay (1.0.5)
8
9
  cucumber (1.1.9)
9
10
  builder (>= 2.1.2)
10
11
  diff-lcs (>= 1.1.2)
@@ -24,7 +25,12 @@ GEM
24
25
  json (1.6.5)
25
26
  logger (1.2.8)
26
27
  map_by_method (0.8.3)
28
+ method_source (0.7.1)
27
29
  multi_json (1.1.0)
30
+ pry (0.9.8.3)
31
+ coderay (~> 1.0.5)
32
+ method_source (~> 0.7.1)
33
+ slop (>= 2.4.4, < 3)
28
34
  rake (0.9.2.2)
29
35
  rdoc (3.12)
30
36
  json (~> 1.4)
@@ -41,6 +47,7 @@ GEM
41
47
  multi_json (~> 1.0)
42
48
  simplecov-html (~> 0.5.3)
43
49
  simplecov-html (0.5.3)
50
+ slop (2.4.4)
44
51
  term-ansicolor (1.0.7)
45
52
 
46
53
  PLATFORMS
@@ -53,6 +60,7 @@ DEPENDENCIES
53
60
  jeweler (~> 1.8.3)
54
61
  logger
55
62
  map_by_method
63
+ pry
56
64
  rdoc (~> 3.12)
57
65
  rspec (~> 2.8.0)
58
66
  rubytree
data/README.markdown CHANGED
@@ -87,10 +87,6 @@ This is a bit easier to understand by looking at an example, take a gander at: l
87
87
  end
88
88
  end
89
89
 
90
- #### Plurals and Inflectors
91
-
92
- Everything is singularized and downcased before being matched. lib/infections.rb can be used to customize the singularization behavior.
93
-
94
90
  ### Compound Fragments
95
91
 
96
92
  class StartsWithLetter < Natural::Fragment
@@ -127,11 +123,15 @@ Everything is singularized and downcased before being matched. lib/infections.rb
127
123
  [n][tree] +---+ blu-rays expansion (0)
128
124
  [n][tree] +---> movies fragment (0)
129
125
 
126
+ ### Plurals and Inflectors
127
+
128
+ Everything is singularized and downcased before being matched. `lib/natural/inflections.rb` can be used to customize the singularization behavior.
129
+
130
130
  ### Scoring
131
131
 
132
132
  ## Generating the Answer
133
133
 
134
- ### Sets
134
+ ### Data
135
135
 
136
136
  ### Filters
137
137
 
@@ -139,15 +139,17 @@ Everything is singularized and downcased before being matched. lib/infections.rb
139
139
 
140
140
  ### Putting Them Together
141
141
 
142
- conform to the Natural way and use the built in answer method or navigate the tree and assemble sets, filters, and aggregators any way you want
142
+ use the built in answer method or DIY: navigate the tree and assemble data, filters, and aggregators any way you want
143
143
 
144
144
  ## Performance
145
145
 
146
146
  Natural has not (yet) been optimized for cpu or memory usage. Natural works best with short questions and a small vocabulary.
147
147
 
148
- ## Testing
148
+ ### Matching
149
+
150
+ You can match according to the highest scoring combination of words (slower, defalt), or you can match in the order of the passed in fragment_classes, ignoring any words that are already matched (faster).
149
151
 
150
- Yah. Need to write these. :p
152
+ Natural.new('how many days of the week start with the letter T', :matching => :first_match, :fragment_classes => [StartsWithLetter, DayNames, Count]).answer
151
153
 
152
154
  ## Contributing to Natural
153
155
 
data/Rakefile CHANGED
@@ -53,3 +53,8 @@ Rake::RDocTask.new do |rdoc|
53
53
  rdoc.rdoc_files.include('README*')
54
54
  rdoc.rdoc_files.include('lib/**/*.rb')
55
55
  end
56
+
57
+ desc "Open an irb session preloaded with this library"
58
+ task :c do
59
+ exec "irb -rubygems -I lib -r ./lib/natural.rb"
60
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.1
data/lib/natural.rb CHANGED
@@ -5,30 +5,38 @@ require 'natural/fragment'
5
5
  require 'natural/fragments/timeframes.rb'
6
6
  require 'natural/fragments/misc.rb'
7
7
  require 'natural/fragments/example.rb'
8
+ require 'pry'
8
9
 
9
10
  class Natural
10
11
  require 'map_by_method'
11
12
  require 'active_support/inflector'
12
13
  require 'active_support/core_ext'
13
-
14
14
  require 'logger'
15
- @@logger = Logger.new(STDOUT)
16
- @@logger.level = Logger::DEBUG
17
15
 
18
16
  GREEN = "\e[32m"
19
17
  RED = "\e[31m"
20
18
  YELLOW = "\e[33m"
21
19
  CLEAR = "\e[0m"
22
20
 
21
+ MATCHING_OPTIONS = [:most_points, :first_match]
22
+
23
23
  DEFAULT_SPELLINGS = {'week' => ['wek', 'weeek'], 'begin' => ['beginn', 'beegin']}
24
24
  DEFAULT_SYNONYMS = {'1' => ['start', 'begin', 'commence'], '2' => ['stop', 'end', 'finish', 'conclude']}
25
25
  DEFAULT_EXPANSIONS = {'food' => ['grocery', 'eat out', 'eating out', 'dining out', 'dine out', 'dine in'], 'music' => ['audio cd', 'audio tape'], 'movie' => ['blu-ray', 'dvd', 'video']}
26
+ DEFAULT_MATCHING = :most_points
26
27
 
27
28
  def initialize(text, options={})
28
- @text = text.squeeze(' ').strip
29
- @options = options
29
+ @text = text.squeeze(' ').strip
30
+ @options = options
31
+
32
+ if options[:logger]
33
+ @logger = options[:logger]
34
+ else
35
+ @logger = Logger.new(STDOUT)
36
+ @logger.level = Logger::DEBUG
37
+ end
30
38
 
31
- @parse = parse
39
+ @parse = parse
32
40
  end
33
41
 
34
42
  def text=(text)
@@ -45,6 +53,7 @@ class Natural
45
53
  return @parse if @parse
46
54
 
47
55
  start_at = Time.now
56
+
48
57
  # search for all possible matches using all the different fragment classes
49
58
  matches_by_class = {}
50
59
  fragment_classes = @options[:fragment_classes] || ObjectSpace.each_object(Class)
@@ -52,20 +61,31 @@ class Natural
52
61
  find_options = {
53
62
  :text => @text,
54
63
  :matches => matches_by_class,
64
+ :matching => @options[:matching] || DEFAULT_MATCHING,
55
65
  :spellings => @options[:spellings] || DEFAULT_SPELLINGS,
56
66
  :synonyms => @options[:synonyms] || DEFAULT_SYNONYMS,
57
67
  :expansions => @options[:expansions] || DEFAULT_EXPANSIONS
58
68
  }
59
- ObjectSpace.each_object(Class).select {|a| a < Natural::Alternative}.each do |klass|
60
- matches_by_class = klass.find(find_options)
61
- end
62
- # binding.pry
63
- fragment_classes.each do |klass|
64
- matches_by_class = klass.find(find_options)
69
+
70
+ if find_options[:matching] == :first_match
71
+ # once a match has been found, exclude those words from further consideration
72
+ # can help speed things up, but requires you order the candidate fragment_classes carefully
73
+ fragment_classes.each do |klass|
74
+ new_options = find_options.dup
75
+ new_options[:ignore] = matches_by_class.values.flatten.select{|a| a}.map_by_ids.flatten.uniq.sort
76
+ matches_by_class[klass] = klass.find(new_options)[klass] if klass.find(new_options)[klass]
77
+ end
78
+ else
79
+ ObjectSpace.each_object(Class).select {|a| a < Natural::Alternative}.each do |klass|
80
+ matches_by_class = klass.find(find_options)
81
+ end
82
+ fragment_classes.each do |klass|
83
+ matches_by_class = klass.find(find_options)
84
+ end
65
85
  end
66
86
 
67
87
  matching_at = Time.now
68
- @@logger.debug "[n][perf] matching took #{(matching_at - start_at).seconds.round(1)} seconds"
88
+ @logger.debug "[n][perf] matching took #{(matching_at - start_at).seconds.round(1)} seconds"
69
89
 
70
90
  # find all valid combinations, choose the one with the highest score
71
91
  sequences = []
@@ -74,8 +94,8 @@ class Natural
74
94
  fragments = sequences.first || []
75
95
 
76
96
  scoring_at = Time.now
77
- @@logger.debug "[n][perf] scoring took #{(scoring_at - matching_at).seconds.round(1)} seconds"
78
- @@logger.debug "[n]"
97
+ @logger.debug "[n][perf] scoring took #{(scoring_at - matching_at).seconds.round(1)} seconds"
98
+ @logger.debug "[n]"
79
99
 
80
100
  # tag the leftover words as unused
81
101
  remaining_words = (0..@text.split(' ').size-1).to_a - (!fragments.blank? ? fragments.map_by_ids.flatten : [])
@@ -89,14 +109,14 @@ class Natural
89
109
  @parse = Fragment.new(:ids => (0..@text.split(' ').size-1).to_a, :text => @text)
90
110
  fragments.each {|a| @parse << a}
91
111
 
92
- sequences.each {|a| @@logger.debug "[n][scor] #{a.map_by_score.sum.to_s.rjust(2, '0')} #{a.sort{|b,c| b.ids.first <=> c.ids.first}.join(' | ')}"}
93
- @@logger.debug("[n]")
112
+ sequences.each {|a| @logger.debug "[n][scor] #{a.map_by_score.sum.to_s.rjust(2, '0')} #{a.sort{|b,c| b.ids.first <=> c.ids.first}.join(' | ')}"}
113
+ @logger.debug("[n]")
94
114
  @parse.pretty_to_s.each_line do |line|
95
- @@logger.debug("[n][tree] #{line.gsub("\n", '')}")
115
+ @logger.debug("[n][tree] #{line.gsub("\n", '')}")
96
116
  end
97
- @@logger.debug("[n]")
98
- @@logger.info("[n][orig] #{@text}" + (@options[:context] ? " (#{@options[:context]})" : ""))
99
- @@logger.info("[n][used] #{interpretation}" + (@options[:context] ? " (#{@options[:context]})" : ""))
117
+ @logger.debug("[n]")
118
+ @logger.info("[n][orig] #{@text}" + (@options[:context] ? " (#{@options[:context]})" : ""))
119
+ @logger.info("[n][used] #{interpretation}" + (@options[:context] ? " (#{@options[:context]})" : ""))
100
120
 
101
121
  @parse
102
122
  end
@@ -87,6 +87,7 @@ class Natural
87
87
 
88
88
  def self.find(options)
89
89
  text_to_search = options[:text]
90
+ words_to_ignore = options[:ignore] || []
90
91
  looking_for = options[:looking_for]
91
92
  old_matches = options[:matches] || {}
92
93
  if options[:matches] && (options[:merge_results].class == NilClass || options[:merge_results])
@@ -106,47 +107,49 @@ class Natural
106
107
  # look for the longest possible matches and work our way down to the short ones
107
108
  0.upto(words.size-1) do |first|
108
109
  (words.size-1).downto(first) do |last|
109
- match = nil
110
- selection = words[(first..last)].join(' ').strip.downcase
110
+ if ((first..last).to_a & words_to_ignore).blank?
111
+ match = nil
112
+ selection = words[(first..last)].join(' ').strip.downcase
111
113
 
112
- if looking_for.include?(selection.singularize.downcase)
113
- match = match_class.new(:ids => (first..last).to_a, :text => selection)
114
- end
115
-
116
- # didn't find a simple match, try swapping some or all words for alternatives and try again
117
- if !match && !(match_class < Natural::Alternative)
118
- fragments = old_matches.select {|k,v| k < Natural::Alternative && !v.blank?}.values.flatten.select {|a| a.ids.first >= first && a.ids.last <= last}
119
-
120
- # assemble a list of all the possible, non-overlapping swaps
121
- combinations = (1..fragments.size).inject([]) do |memo, i|
122
- fragments.combination(i).each do |combo|
123
- if !combo.combination(2).any? {|a| (a[0].ids.first..a[0].ids.last).overlaps?(a[1].ids.first..a[1].ids.last)}
124
- memo << combo
125
- end
126
- end
127
- memo
114
+ if looking_for.include?(selection.singularize.downcase)
115
+ match = match_class.new(:ids => (first..last).to_a, :text => selection)
128
116
  end
129
117
 
130
- combinations.each do |combo|
131
- alternative_words = words.clone
132
- alternative_fragments = []
133
-
134
- combo.each do |fragment|
135
- alternative_words.slice!(fragment.ids.first..fragment.ids.last)
136
- alternative_words.insert(fragment.ids.first, fragment.to_s)
137
- alternative_fragments << fragment
118
+ # didn't find a simple match, try swapping some or all words for alternatives and try again
119
+ if !match && !(match_class < Natural::Alternative)
120
+ fragments = old_matches.select {|k,v| k < Natural::Alternative && !v.blank?}.values.flatten.select {|a| a.ids.first >= first && a.ids.last <= last}
121
+
122
+ # assemble a list of all the possible, non-overlapping swaps
123
+ combinations = (1..fragments.size).inject([]) do |memo, i|
124
+ fragments.combination(i).each do |combo|
125
+ if !combo.combination(2).any? {|a| (a[0].ids.first..a[0].ids.last).overlaps?(a[1].ids.first..a[1].ids.last)}
126
+ memo << combo
127
+ end
128
+ end
129
+ memo
138
130
  end
139
- alternative_selection = alternative_words[(first..last)].join(' ').strip.downcase
140
131
 
141
- if looking_for.include?(alternative_selection.singularize.downcase)
142
- match = match_class.new(:ids => (first..last).to_a, :text => alternative_selection)
143
- leftovers = ((first..last).to_a - combo.map {|a| a.ids}.flatten).to_ranges
144
- leftovers.each do |range|
145
- alternative_fragments << Fragment.new(:ids => range.to_a, :text => words[range].join(' '))
132
+ combinations.each do |combo|
133
+ alternative_words = words.clone
134
+ alternative_fragments = []
135
+
136
+ combo.each do |fragment|
137
+ alternative_words.slice!(fragment.ids.first..fragment.ids.last)
138
+ alternative_words.insert(fragment.ids.first, fragment.to_s)
139
+ alternative_fragments << fragment
140
+ end
141
+ alternative_selection = alternative_words[(first..last)].join(' ').strip.downcase
142
+
143
+ if looking_for.include?(alternative_selection.singularize.downcase)
144
+ match = match_class.new(:ids => (first..last).to_a, :text => alternative_selection)
145
+ leftovers = ((first..last).to_a - combo.map {|a| a.ids}.flatten).to_ranges
146
+ leftovers.each do |range|
147
+ alternative_fragments << Fragment.new(:ids => range.to_a, :text => words[range].join(' '))
148
+ end
149
+ alternative_fragments.sort_by {|a| a.ids.first}.each {|a| match << a}
146
150
  end
147
- alternative_fragments.sort_by {|a| a.ids.first}.each {|a| match << a}
148
- end
149
151
 
152
+ end
150
153
  end
151
154
  end
152
155
 
@@ -188,9 +191,11 @@ class Natural
188
191
  looking_for[1..-1].each do |term|
189
192
  if term.class == Class && term <= Fragment
190
193
  new_matches = term.find(:text => text_to_search, :matches => old_matches, :spellings => options[:spellings], :synonyms => options[:synonyms], :expansions => options[:expansions]) if !old_matches[term]
191
- new_matches[term].each do |match|
192
- if match.ids.first == fragments.select {|a| a}.last.ids.last + 1
193
- fragments << match
194
+ if new_matches[term]
195
+ new_matches[term].each do |match|
196
+ if match.ids.first == fragments.select {|a| a}.last.ids.last + 1
197
+ fragments << match
198
+ end
194
199
  end
195
200
  end
196
201
  elsif [Array, Hash, String].include?(term.class)
data/natural.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "natural"
8
- s.version = "0.1.0"
8
+ s.version = "0.1.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Scott Bonds"]
12
- s.date = "2012-03-05"
12
+ s.date = "2012-08-24"
13
13
  s.description = "Natural provides a framework for answering 'naturally' worded questions like 'how many books did I buy last month' or 'list my Facebook friends'."
14
14
  s.email = "scott@ggr.com"
15
15
  s.extra_rdoc_files = [
@@ -43,7 +43,7 @@ Gem::Specification.new do |s|
43
43
  s.homepage = "http://github.com/bonds/natural"
44
44
  s.licenses = ["MIT"]
45
45
  s.require_paths = ["lib"]
46
- s.rubygems_version = "1.8.11"
46
+ s.rubygems_version = "1.8.23"
47
47
  s.summary = "natural language parser"
48
48
 
49
49
  if s.respond_to? :specification_version then
@@ -60,6 +60,7 @@ Gem::Specification.new do |s|
60
60
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
61
61
  s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
62
62
  s.add_development_dependency(%q<simplecov>, [">= 0"])
63
+ s.add_development_dependency(%q<pry>, [">= 0"])
63
64
  else
64
65
  s.add_dependency(%q<rubytree>, [">= 0"])
65
66
  s.add_dependency(%q<logger>, [">= 0"])
@@ -71,6 +72,7 @@ Gem::Specification.new do |s|
71
72
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
72
73
  s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
73
74
  s.add_dependency(%q<simplecov>, [">= 0"])
75
+ s.add_dependency(%q<pry>, [">= 0"])
74
76
  end
75
77
  else
76
78
  s.add_dependency(%q<rubytree>, [">= 0"])
@@ -83,6 +85,7 @@ Gem::Specification.new do |s|
83
85
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
84
86
  s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
85
87
  s.add_dependency(%q<simplecov>, [">= 0"])
88
+ s.add_dependency(%q<pry>, [">= 0"])
86
89
  end
87
90
  end
88
91
 
data/spec/natural_spec.rb CHANGED
@@ -1,7 +1,19 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- # describe "Natural" do
4
- # it "fails" do
5
- # fail "hey buddy, you should probably rename this file and start specing for real"
6
- # end
7
- # end
3
+ describe "Natural" do
4
+ it "recognizes synonyms on top of misspellings" do
5
+ logger = double("logger")
6
+ logger.should_receive(:info).with("[n][orig] how many days of the wek beginn with the letter T")
7
+ logger.should_receive(:info).with("[n][used] how many days of the week start with the letter t")
8
+ logger.should_receive(:debug).any_number_of_times
9
+ Natural.new('how many days of the wek beginn with the letter T', :logger => logger).answer.should eq(2)
10
+ end
11
+
12
+ it "expands movies into blu-rays" do
13
+ logger = double("logger")
14
+ logger.should_receive(:info).any_number_of_times
15
+ logger.should_receive(:debug).with("[n][scor] 01 blu-rays").at_least(1).times
16
+ logger.should_receive(:debug).any_number_of_times
17
+ Natural.new('movies', :logger => logger)
18
+ end
19
+ end
data/spec/spec_helper.rb CHANGED
@@ -5,6 +5,7 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
5
  $LOAD_PATH.unshift(File.dirname(__FILE__))
6
6
  require 'rspec'
7
7
  require 'natural'
8
+ require 'pry'
8
9
 
9
10
  # Requires supporting files with custom matchers and macros, etc,
10
11
  # in ./support/ and its subdirectories.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: natural
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-05 00:00:00.000000000 Z
12
+ date: 2012-08-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rubytree
16
- requirement: &70176893369980 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70176893369980
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: logger
27
- requirement: &70176893369320 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: '0'
33
38
  type: :runtime
34
39
  prerelease: false
35
- version_requirements: *70176893369320
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: map_by_method
38
- requirement: &70176893368200 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ! '>='
@@ -43,10 +53,15 @@ dependencies:
43
53
  version: '0'
44
54
  type: :runtime
45
55
  prerelease: false
46
- version_requirements: *70176893368200
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: activesupport
49
- requirement: &70176893367300 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ! '>='
@@ -54,10 +69,15 @@ dependencies:
54
69
  version: '0'
55
70
  type: :runtime
56
71
  prerelease: false
57
- version_requirements: *70176893367300
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
58
78
  - !ruby/object:Gem::Dependency
59
79
  name: rspec
60
- requirement: &70176893366660 !ruby/object:Gem::Requirement
80
+ requirement: !ruby/object:Gem::Requirement
61
81
  none: false
62
82
  requirements:
63
83
  - - ~>
@@ -65,10 +85,15 @@ dependencies:
65
85
  version: 2.8.0
66
86
  type: :development
67
87
  prerelease: false
68
- version_requirements: *70176893366660
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 2.8.0
69
94
  - !ruby/object:Gem::Dependency
70
95
  name: rdoc
71
- requirement: &70176893365820 !ruby/object:Gem::Requirement
96
+ requirement: !ruby/object:Gem::Requirement
72
97
  none: false
73
98
  requirements:
74
99
  - - ~>
@@ -76,10 +101,15 @@ dependencies:
76
101
  version: '3.12'
77
102
  type: :development
78
103
  prerelease: false
79
- version_requirements: *70176893365820
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '3.12'
80
110
  - !ruby/object:Gem::Dependency
81
111
  name: cucumber
82
- requirement: &70176893063820 !ruby/object:Gem::Requirement
112
+ requirement: !ruby/object:Gem::Requirement
83
113
  none: false
84
114
  requirements:
85
115
  - - ! '>='
@@ -87,10 +117,15 @@ dependencies:
87
117
  version: '0'
88
118
  type: :development
89
119
  prerelease: false
90
- version_requirements: *70176893063820
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
91
126
  - !ruby/object:Gem::Dependency
92
127
  name: bundler
93
- requirement: &70176893063100 !ruby/object:Gem::Requirement
128
+ requirement: !ruby/object:Gem::Requirement
94
129
  none: false
95
130
  requirements:
96
131
  - - ~>
@@ -98,10 +133,15 @@ dependencies:
98
133
  version: 1.0.0
99
134
  type: :development
100
135
  prerelease: false
101
- version_requirements: *70176893063100
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: 1.0.0
102
142
  - !ruby/object:Gem::Dependency
103
143
  name: jeweler
104
- requirement: &70176893062300 !ruby/object:Gem::Requirement
144
+ requirement: !ruby/object:Gem::Requirement
105
145
  none: false
106
146
  requirements:
107
147
  - - ~>
@@ -109,10 +149,15 @@ dependencies:
109
149
  version: 1.8.3
110
150
  type: :development
111
151
  prerelease: false
112
- version_requirements: *70176893062300
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ~>
156
+ - !ruby/object:Gem::Version
157
+ version: 1.8.3
113
158
  - !ruby/object:Gem::Dependency
114
159
  name: simplecov
115
- requirement: &70176893061480 !ruby/object:Gem::Requirement
160
+ requirement: !ruby/object:Gem::Requirement
116
161
  none: false
117
162
  requirements:
118
163
  - - ! '>='
@@ -120,7 +165,28 @@ dependencies:
120
165
  version: '0'
121
166
  type: :development
122
167
  prerelease: false
123
- version_requirements: *70176893061480
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ - !ruby/object:Gem::Dependency
175
+ name: pry
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ! '>='
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ type: :development
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
124
190
  description: Natural provides a framework for answering 'naturally' worded questions
125
191
  like 'how many books did I buy last month' or 'list my Facebook friends'.
126
192
  email: scott@ggr.com
@@ -167,7 +233,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
167
233
  version: '0'
168
234
  segments:
169
235
  - 0
170
- hash: -4600966347874648454
236
+ hash: -2109308253021748277
171
237
  required_rubygems_version: !ruby/object:Gem::Requirement
172
238
  none: false
173
239
  requirements:
@@ -176,7 +242,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
176
242
  version: '0'
177
243
  requirements: []
178
244
  rubyforge_project:
179
- rubygems_version: 1.8.11
245
+ rubygems_version: 1.8.23
180
246
  signing_key:
181
247
  specification_version: 3
182
248
  summary: natural language parser