textquery 0.1.7 → 0.1.8

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.
data/.rspec ADDED
File without changes
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,44 @@
1
+ # TextQuery
2
+
3
+ Does it match? When regular expressions are not enough, textquery is the answer. For
4
+ example, regular expressions cannot evaluate recursive rules and often result in
5
+ overly verbose and complicated expressions.
6
+
7
+ Textquery is a simple PEG grammar with support for:
8
+
9
+ - AND (spaces are implicit AND's)
10
+ - OR
11
+ - NOT (- is an alias)
12
+ - 'quoted strings'
13
+ - fuzzy matching
14
+ - case (in)sensitive
15
+ - custom delimeters
16
+
17
+ ## Example
18
+
19
+ ```ruby
20
+ TextQuery.new("'to be' OR NOT 'to_be'").match?("to be") # => true
21
+
22
+ TextQuery.new("-test").match?("some string of text") # => true
23
+ TextQuery.new("NOT test").match?("some string of text") # => true
24
+
25
+ TextQuery.new("a AND b").match?("b a") # => true
26
+ TextQuery.new("a AND b").match?("a c") # => false
27
+
28
+ q = TextQuery.new("a AND (b AND NOT (c OR d))")
29
+ q.match?("d a b") # => false
30
+ q.match?("b") # => false
31
+ q.match?("a b cdefg") # => true
32
+
33
+ TextQuery.new("a~").match?("adf") # => true
34
+ TextQuery.new("~a").match?("dfa") # => true
35
+ TextQuery.new("~a~").match?("daf") # => true
36
+ TextQuery.new("2~a~1").match?("edaf") # => true
37
+ TextQuery.new("2~a~2").match?("edaf") # => false
38
+
39
+ TextQuery.new("a", :ignorecase => true).match?("A b cD") # => true
40
+ ```
41
+
42
+ ## License
43
+
44
+ The MIT License - Copyright (c) 2011 Ilya Grigorik
data/Rakefile CHANGED
@@ -1,19 +1,10 @@
1
- require 'rake'
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
2
3
 
3
- begin
4
- require 'jeweler'
5
- Jeweler::Tasks.new do |gemspec|
6
- gemspec.name = "textquery"
7
- gemspec.summary = "Evaluate any text against a collection of match rules"
8
- gemspec.description = gemspec.summary
9
- gemspec.email = "ilya@igvita.com"
10
- gemspec.homepage = "http://github.com/igrigorik/textquery"
11
- gemspec.authors = ["Ilya Grigorik"]
12
- gemspec.add_dependency("treetop")
13
- gemspec.rubyforge_project = "textquery"
14
- end
4
+ require 'rspec/core/rake_task'
15
5
 
16
- Jeweler::GemcutterTasks.new
17
- rescue LoadError
18
- puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
19
- end
6
+ desc "Run all RSpec tests"
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task :default => :spec
10
+ task :test => [:spec]
@@ -17,7 +17,7 @@ else
17
17
  RegExp::IGNORECACASE = Regexp::IGNORECASE
18
18
  end
19
19
 
20
- FUZZY = RegExp.new('(\d)*(~)?([^~]+)(~)?(\d)*$')
20
+ FUZZY = RegExp.new('(?:(\d)*(~))?([^~]+)(?:(~)?(\d)*)$')
21
21
 
22
22
  class WordMatch < Treetop::Runtime::SyntaxNode
23
23
 
@@ -52,6 +52,9 @@ class WordMatch < Treetop::Runtime::SyntaxNode
52
52
  end
53
53
  end
54
54
 
55
+ def accept(&block)
56
+ block.call(:value, text_value)
57
+ end
55
58
  end
56
59
 
57
60
  Treetop.load File.dirname(__FILE__) + "/textquery_grammar"
@@ -88,6 +91,16 @@ class TextQuery
88
91
  end
89
92
  alias :match? :eval
90
93
 
94
+ def accept(options = {}, &block)
95
+ update_options(options) if not options.empty?
96
+
97
+ if @query
98
+ @query.accept(&block)
99
+ else
100
+ raise TextQueryError, 'no query specified'
101
+ end
102
+ end
103
+
91
104
  def terminal_failures
92
105
  @parser.terminal_failures
93
106
  end
@@ -96,6 +109,6 @@ class TextQuery
96
109
 
97
110
  def update_options(options)
98
111
  @options = {:delim => ' '}.merge(options)
99
- @options[:delim] = "(#{[@options[:delim]].flatten.map { |opt| RegExp.escape(opt) }.join("|")})"
112
+ @options[:delim] = "(#{[@options[:delim]].flatten.map { |opt| opt.is_a?(Regexp) ? opt : RegExp.escape(opt) }.join("|")})"
100
113
  end
101
114
  end
@@ -9,12 +9,20 @@ grammar TextQueryGrammar
9
9
  def eval(text, opt)
10
10
  operator.eval(op1.eval(text, opt), op2.eval(text, opt))
11
11
  end
12
+
13
+ def accept(&block)
14
+ operator.accept(op1.accept(&block), op2.accept(&block), &block)
15
+ end
12
16
  }
13
17
  /
14
18
  op1:value [\s]+ op2:expression {
15
19
  def eval(text, opt)
16
20
  op1.eval(text, opt) && op2.eval(text, opt)
17
21
  end
22
+
23
+ def accept(&block)
24
+ block.call(:and, op1.accept(&block), op2.accept(&block))
25
+ end
18
26
  }
19
27
  end
20
28
 
@@ -23,12 +31,20 @@ grammar TextQueryGrammar
23
31
  def eval(a,b)
24
32
  a && b
25
33
  end
34
+
35
+ def accept(a, b, &block)
36
+ block.call(:and, a, b)
37
+ end
26
38
  }
27
39
  /
28
40
  'OR' {
29
41
  def eval(a,b)
30
42
  a || b
31
43
  end
44
+
45
+ def accept(a, b, &block)
46
+ block.call(:or, a, b)
47
+ end
32
48
  }
33
49
  end
34
50
 
@@ -37,6 +53,10 @@ grammar TextQueryGrammar
37
53
  def eval(a)
38
54
  not a
39
55
  end
56
+
57
+ def accept(a, &block)
58
+ block.call(:not, a)
59
+ end
40
60
  }
41
61
  end
42
62
 
@@ -69,24 +89,40 @@ grammar TextQueryGrammar
69
89
  def eval(text, opt)
70
90
  expression.eval(text, opt)
71
91
  end
92
+
93
+ def accept(&block)
94
+ expression.accept(&block)
95
+ end
72
96
  }
73
97
  /
74
98
  operator:unary space value {
75
99
  def eval(text, opt)
76
100
  operator.eval(value.eval(text, opt))
77
101
  end
102
+
103
+ def accept(&block)
104
+ operator.accept(value.accept(&block), &block)
105
+ end
78
106
  }
79
107
  /
80
108
  double_quote space double_quote_words space double_quote {
81
109
  def eval(text, opt)
82
110
  double_quote_words.eval(text, opt)
83
111
  end
112
+
113
+ def accept(&block)
114
+ double_quote_words.accept(&block)
115
+ end
84
116
  }
85
117
  /
86
118
  single_quote space single_quote_words space single_quote {
87
119
  def eval(text, opt)
88
120
  single_quote_words.eval(text, opt)
89
121
  end
122
+
123
+ def accept(&block)
124
+ single_quote_words.accept(&block)
125
+ end
90
126
  }
91
127
  /
92
128
  word
@@ -1,10 +1,9 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require "rubygems"
4
- require "spec"
3
+ require "rspec"
5
4
  require "pp"
6
5
 
7
- require "lib/textquery"
6
+ require "textquery"
8
7
 
9
8
  # Resources:
10
9
  # - http://github.com/nathansobo/treetop
@@ -137,6 +136,13 @@ describe TextQuery do
137
136
  parse('"to be" OR NOT "to be"').eval("to be").should be_true
138
137
  end
139
138
 
139
+ it "should accept unbalanced quotes" do
140
+ parse("awesome").eval("M&M's are awesome").should be_true
141
+ parse("M&M's").eval("M&M's are awesome").should be_true
142
+ parse("M&M's AND awesome").eval("M&M's are awesome").should be_true
143
+ parse("M&M's AND fail").eval("M&M's are awesome").should be_false
144
+ end
145
+
140
146
  it "should accept mixed quotes inside the exact match queries" do
141
147
  parse("seattle's best").eval("seattle's best").should be_true
142
148
 
@@ -204,6 +210,11 @@ describe TextQuery do
204
210
  q.parse("~更は出~ OR ~尽く~").eval(JP).should be_true
205
211
  end
206
212
 
213
+ it "should work with queries starting with numbers" do
214
+ q = TextQuery.new('3827')
215
+ q.parse('abc 123 3827 9382').should be_true
216
+ end
217
+
207
218
  it "should be case insensitive" do
208
219
  TextQuery.new("a", :ignorecase => true).match?("A b cD").should be_true
209
220
  TextQuery.new("a AND CD", :ignorecase => true).match?("A b cD").should be_true
@@ -236,5 +247,28 @@ describe TextQuery do
236
247
  it 'should not match just the delimiter' do
237
248
  TextQuery.new("a*b", :delim => ["*", "<"]).match?("over<under").should be_false
238
249
  end
250
+
251
+ it 'should accept a Regexp as a delimiter' do
252
+ TextQuery.new("a", :delim => [%r{\b}]).match?("a.b").should be_true
253
+ TextQuery.new("a b", :delim => [%r{\b}]).match?("a.b").should be_true
254
+ TextQuery.new("a b", :delim => [%r{\b}]).match?("a.c").should be_false
255
+ end
256
+
257
+ it 'should OR multiple Regexp delimiters and match on all words' do
258
+ TextQuery.new("cd", :delim => [%r{\d}, %r{\.\.}]).match?("ab2cd..ef").should be_true
259
+ TextQuery.new("ef", :delim => [%r{\d}, %r{\.\.}]).match?("ab2cd..ef").should be_true
260
+ TextQuery.new("ab2cd", :delim => [%r{\d}, %r{\.\.}]).match?("ab2cd..ef").should be_true
261
+ end
262
+
263
+ it 'should accept mixed Strings and Regexps as delimiters' do
264
+ TextQuery.new("a", :delim => [%r{a{2,3}}]).match?("aab").should be_false
265
+ TextQuery.new("a", :delim => [%r{a{2,3}}, 'b']).match?("aab").should be_false
266
+ TextQuery.new("b", :delim => [%r{a{2,3}}, 'a']).match?("aab").should be_true
267
+ end
268
+
269
+ it 'should allow query to be traversed' do
270
+ TextQuery.new("a b").accept { |*a| a }.should == [ :and, [ :value, 'a' ], [ :value, 'b' ] ]
271
+ TextQuery.new("a OR b").accept { |*a| a }.should == [ :or, [ :value, 'a' ], [ :value, 'b' ] ]
272
+ end
239
273
  end
240
274
  end
@@ -1,56 +1,24 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
1
  # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
5
3
 
6
4
  Gem::Specification.new do |s|
7
- s.name = %q{textquery}
8
- s.version = "0.1.7"
5
+ s.name = "textquery"
6
+ s.version = "0.1.8"
9
7
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Ilya Grigorik"]
12
- s.date = %q{2010-09-04}
13
- s.description = %q{Evaluate any text against a collection of match rules}
14
- s.email = %q{ilya@igvita.com}
15
- s.extra_rdoc_files = [
16
- "README.rdoc"
17
- ]
18
- s.files = [
19
- ".gitignore",
20
- "README.rdoc",
21
- "Rakefile",
22
- "VERSION",
23
- "benchmark/benchmark.rb",
24
- "benchmark/sample.txt",
25
- "examples/web.rb",
26
- "lib/textquery.rb",
27
- "lib/textquery/textquery.rb",
28
- "lib/textquery/textquery_grammar.treetop",
29
- "spec/textquery_spec.rb",
30
- "textquery.gemspec"
31
- ]
32
- s.homepage = %q{http://github.com/igrigorik/textquery}
33
- s.rdoc_options = ["--charset=UTF-8"]
34
- s.require_paths = ["lib"]
35
- s.rubyforge_project = %q{textquery}
36
- s.rubygems_version = %q{1.3.7}
37
- s.summary = %q{Evaluate any text against a collection of match rules}
38
- s.test_files = [
39
- "spec/textquery_spec.rb",
40
- "examples/web.rb"
41
- ]
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ilya Grigorik"]
10
+ s.email = ["ilya@igvita.com"]
11
+ s.homepage = "http://github.com/igrigorik/textquery"
12
+ s.summary = "Evaluate any text against a collection of match rules"
13
+ s.description = s.summary
14
+ s.rubyforge_project = "textquery"
42
15
 
43
- if s.respond_to? :specification_version then
44
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
45
- s.specification_version = 3
16
+ s.add_dependency "treetop"
17
+ s.add_development_dependency "rspec"
18
+ s.add_development_dependency "rake"
46
19
 
47
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
48
- s.add_runtime_dependency(%q<treetop>, [">= 0"])
49
- else
50
- s.add_dependency(%q<treetop>, [">= 0"])
51
- end
52
- else
53
- s.add_dependency(%q<treetop>, [">= 0"])
54
- end
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
55
24
  end
56
-
metadata CHANGED
@@ -1,48 +1,61 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: textquery
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 1
8
- - 7
9
- version: 0.1.7
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.8
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Ilya Grigorik
13
9
  autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
-
17
- date: 2010-09-04 00:00:00 -04:00
18
- default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2011-06-28 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: treetop
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &2152520940 !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- segments:
29
- - 0
30
- version: "0"
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
31
22
  type: :runtime
32
- version_requirements: *id001
23
+ prerelease: false
24
+ version_requirements: *2152520940
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &2152520500 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2152520500
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ requirement: &2152520080 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *2152520080
33
47
  description: Evaluate any text against a collection of match rules
34
- email: ilya@igvita.com
48
+ email:
49
+ - ilya@igvita.com
35
50
  executables: []
36
-
37
51
  extensions: []
38
-
39
- extra_rdoc_files:
40
- - README.rdoc
41
- files:
52
+ extra_rdoc_files: []
53
+ files:
42
54
  - .gitignore
43
- - README.rdoc
55
+ - .rspec
56
+ - Gemfile
57
+ - README.md
44
58
  - Rakefile
45
- - VERSION
46
59
  - benchmark/benchmark.rb
47
60
  - benchmark/sample.txt
48
61
  - examples/web.rb
@@ -51,38 +64,29 @@ files:
51
64
  - lib/textquery/textquery_grammar.treetop
52
65
  - spec/textquery_spec.rb
53
66
  - textquery.gemspec
54
- has_rdoc: true
55
67
  homepage: http://github.com/igrigorik/textquery
56
68
  licenses: []
57
-
58
69
  post_install_message:
59
- rdoc_options:
60
- - --charset=UTF-8
61
- require_paths:
70
+ rdoc_options: []
71
+ require_paths:
62
72
  - lib
63
- required_ruby_version: !ruby/object:Gem::Requirement
73
+ required_ruby_version: !ruby/object:Gem::Requirement
64
74
  none: false
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- segments:
69
- - 0
70
- version: "0"
71
- required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
80
  none: false
73
- requirements:
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- segments:
77
- - 0
78
- version: "0"
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
79
85
  requirements: []
80
-
81
86
  rubyforge_project: textquery
82
- rubygems_version: 1.3.7
87
+ rubygems_version: 1.8.5
83
88
  signing_key:
84
89
  specification_version: 3
85
90
  summary: Evaluate any text against a collection of match rules
86
- test_files:
91
+ test_files:
87
92
  - spec/textquery_spec.rb
88
- - examples/web.rb
@@ -1,62 +0,0 @@
1
- = TextQuery
2
-
3
- Does it match? When regular expressions are not enough, textquery is the answer. For
4
- example, regular expressions cannot evaluate recursive rules and often result in
5
- overly verbose and complicated expressions.
6
-
7
- Textquery is a simple PEG grammar with support for:
8
- - AND (spaces are implicit AND's)
9
- - OR
10
- - NOT (- is an alias)
11
- - 'quoted strings'
12
- - fuzzy matching
13
- - case (in)sensitive
14
- - custom delimeters
15
-
16
- == Example
17
-
18
- TextQuery.new("'to be' OR NOT 'to_be'").match?("to be") # => true
19
-
20
- TextQuery.new("-test").match?("some string of text") # => true
21
- TextQuery.new("NOT test").match?("some string of text") # => true
22
-
23
- TextQuery.new("a AND b").match?("b a") # => true
24
- TextQuery.new("a AND b").match?("a c") # => false
25
-
26
- q = TextQuery.new("a AND (b AND NOT (c OR d))")
27
- q.match?("d a b") # => false
28
- q.match?("b") # => false
29
- q.match?("a b cdefg") # => true
30
-
31
- TextQuery.new("a~").match?("adf") # => true
32
- TextQuery.new("~a").match?("dfa") # => true
33
- TextQuery.new("~a~").match?("daf") # => true
34
- TextQuery.new("2~a~1").match?("edaf") # => true
35
- TextQuery.new("2~a~2").match?("edaf") # => false
36
-
37
- TextQuery.new("a", :ignorecase => true).match?("A b cD") # => true
38
-
39
- == License
40
-
41
- (The MIT License)
42
-
43
- Copyright (c) 2009 Ilya Grigorik
44
-
45
- Permission is hereby granted, free of charge, to any person obtaining
46
- a copy of this software and associated documentation files (the
47
- 'Software'), to deal in the Software without restriction, including
48
- without limitation the rights to use, copy, modify, merge, publish,
49
- distribute, sublicense, and/or sell copies of the Software, and to
50
- permit persons to whom the Software is furnished to do so, subject to
51
- the following conditions:
52
-
53
- The above copyright notice and this permission notice shall be
54
- included in all copies or substantial portions of the Software.
55
-
56
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
57
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
58
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
59
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
60
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
61
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
62
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.1.7