search_lingo 1.0.0.beta2

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: 3dfc725892e1b65db47327257cd8c082536dd74b
4
+ data.tar.gz: 78e4297906559efcb5cedc54b003cf2abfaf4ac1
5
+ SHA512:
6
+ metadata.gz: 5359eff62098245796f55f5abe4d3e663e0e56677b7925ba1267bf0e00dda71cfefa84d87c416a57a6b7f21699ca7b2b9914d96b74f37b02fa0597a35c32b931
7
+ data.tar.gz: aaa8f3414ebf996194ce415e0270fa2321290900ac98092ea34d2dcb43608e3f6df4e62146fea960814063f753ca7b05fd9fb8a3637399dd1459fe786c7700aa
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in search_lingo.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 John Parker
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,177 @@
1
+ # SearchLingo
2
+
3
+ SearchLingo is a framework for defining simple, user-friendly query languages
4
+ and translating them into their underlying queries.
5
+
6
+ It was originally designed after I found myself implementing the same basic
7
+ query parsing over and over again across different projects. I wanted a way to
8
+ simplify the process without having to worry about application-specific aspects
9
+ of searching.
10
+
11
+ The way the searches themselves are performed lies outside the scope of this
12
+ project. Although originally designed to work with basic searching with
13
+ ActiveRecord models, it should be usable with other data stores provided they
14
+ let you chain queries together onto a single object.
15
+
16
+ ## Installation
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ ```ruby
21
+ gem 'search_lingo'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install search_lingo
31
+
32
+ ## Usage
33
+
34
+ Create a class which inherits from SearchLingo::AbstractSearch. Provide an
35
+ implementation of <code>#default_parse</code> in that class. Register parsers
36
+ for specific types of search tokens using the <code>parser</code> class method.
37
+
38
+ Instantiate your search class by passing in the query string and the scope on
39
+ which to perform the search. Use the <code>#results</code> method to compile
40
+ the search and return the results.
41
+
42
+ Take a look at the examples/ directory for more concrete examples.
43
+
44
+ ## How It Works
45
+
46
+ A search is instantiated with a query string and a search scope (commonly an
47
+ ActiveRecord model). The search breaks the query string down into a series of
48
+ tokens, and each token is processed by a declared series of parsers. If a
49
+ parser succeeds, the process immediately terminates and advances to the next
50
+ token. If none of the declared parsers succeeds, and the token is compound --
51
+ that is, the token is composed of an operator and a term (e.g., "foo: bar"),
52
+ the token is simplified and then processed by the declared parsers again. If
53
+ the second pass also fails, then the (now simplified) token falls through to
54
+ the <code>#default_parse</code> method defined by the search class. (It is
55
+ important that this method be implemented in such a way that it always
56
+ succeeds.)
57
+
58
+ ## Search Classes
59
+
60
+ Search classes should inherit from SearchLingo::AbstractSearch and they should
61
+ override the <code>#default_parse</code> instance method. It is important that
62
+ this method be defined in such a way that it always succeeds, as the results
63
+ will be sent to the query object via <code>#public_send</code>. In addtion, the
64
+ class method <code>parser</code> can be used to declare additional parsers that
65
+ should be used by the search class. (See the section "Parsing" for more
66
+ information on what makes a suitable parser.)
67
+
68
+ ## Parsers
69
+
70
+ Any object that can respond to the <code>#call</code> method can be used as a
71
+ parser. If the parser succeeds, it should return an Array of arguments that can
72
+ be sent to the query object using <code>#public_send</code>, e.g.,
73
+ <code>[:where, { id: 42 }]</code>. If the parser fails, it should return a
74
+ falsey value (typically nil).
75
+
76
+ For very simple parsers which need not be reusable, you can pass the
77
+ parsing logic to the <code>parser</code> method as a block:
78
+
79
+ class MySearch < SearchLingo::AbstractSearch
80
+ parser do |token|
81
+ token.match /\Aid:[[:space:]]*([[:digit:]]+)\z/ do |m|
82
+ [:where, { id: m[1] }]
83
+ end
84
+ end
85
+ end
86
+
87
+ Parsers can also be implemented as lambdas:
88
+
89
+ module Parsers
90
+ ID_PARSER = lambda do |token|
91
+ token.match h/\Aid:[[:space:]]*([[:digit:]]+)\z/ do |m|
92
+ [:where, { id: m[1] }]
93
+ end
94
+ end
95
+ end
96
+
97
+ class MySearch < SearchLingo::AbstractSearch
98
+ parser Parsers::ID_PARSER
99
+ end
100
+
101
+ class MyOtherSearch < SearchLingo::AbstractSearch
102
+ parser Parsers::ID_PARSER
103
+ end
104
+
105
+ Finally, for the most complicated cases, you could implement parsers as
106
+ classes:
107
+
108
+ module Parsers
109
+ class IdParser
110
+ def initialize(table, operator = nil)
111
+ @table = table
112
+ @prefix = /#{operator}:\s*/ if operator
113
+ end
114
+
115
+ def call(token)
116
+ token.match /\A#{@prefix}([[:digit:]]+)\z/ do |m|
117
+ [:where, { @table => { id: m[1] } }]
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ class EventSearch < SearchLingo::AbstractSearch
124
+ parser Parsers::IdParser.new :events # => match "42"
125
+ parser Parsers::IdParser.new :categories, 'category' # => match "category: 42"
126
+ end
127
+
128
+ class CategorySearch < SearchLingo::AbstractSearch
129
+ parser Parsers::IdParser.new :categories
130
+ end
131
+
132
+ ## Tokenization
133
+
134
+ Queries are comprised of one or more tokens separated by spaces. A simple token
135
+ is a term which can be a single word (or date, number, etc.) or multiple terms
136
+ within a pair of double quotes. A compound token is a simple token preceded by
137
+ an operator followed by zero or more spaces.
138
+
139
+ QUERY := TOKEN*
140
+ TOKEN := COMPOUND_TOKEN | TERM
141
+ COMPOUND_TOKEN := OPERATOR TERM
142
+ OPERATOR := [[:graph:]]+:
143
+ TERM := "[^"]*" | [[:graph:]]+
144
+
145
+ Terms can be things like:
146
+
147
+ * foo
148
+ * "foo bar"
149
+ * 6/14/15
150
+ * 1000.00
151
+
152
+ Operators can be things like:
153
+
154
+ * foo:
155
+ * bar_baz:
156
+
157
+ (If you want to perform a query with a term that could potentially be parsed as
158
+ an operator, you would place the term in quotes, i.e., "foo:".)
159
+
160
+ ## Development
161
+
162
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
163
+ `bin/console` for an interactive prompt that will allow you to experiment.
164
+
165
+ To install this gem onto your local machine, run `bundle exec rake install`. To
166
+ release a new version, update the version number in `version.rb`, and then run
167
+ `bundle exec rake release` to create a git tag for the version, push git
168
+ commits and tags, and push the `.gem` file to
169
+ [rubygems.org](https://rubygems.org).
170
+
171
+ ## Contributing
172
+
173
+ 1. Fork it ( https://github.com/jparker/search_lingo/fork )
174
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
175
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
176
+ 4. Push to the branch (`git push origin my-new-feature`)
177
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.pattern = 'test/**/test_*.rb'
7
+ end
8
+
9
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "search_lingo"
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,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,83 @@
1
+ class Job < ActiveRecord::Base
2
+ # Assume this model has attributes: :id, :date, :name
3
+ end
4
+
5
+ class Receipt < ActiveRecord::Base
6
+ # Assume this model has attributes: :id, :check_no, :check_date, :post_date, :amount
7
+ end
8
+
9
+ module Parsers
10
+ class IdParser
11
+ def initialize(table)
12
+ @table = table
13
+ end
14
+
15
+ def call(token)
16
+ token.match /\Aid:\s*([[:digit:]]+)\z/ do |m|
17
+ [:where, { @table => { id: m[1] } }]
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ class JobSearch < AbstractSearch
24
+ parser Parsers::IdParser.new Job.table_name
25
+
26
+ parser SearchLingo::Parsers::DateParser.new Job.table_name,
27
+ :date
28
+ parser SearchLingo::Parsers::DateRangeParser.new Job.table_name,
29
+ :date
30
+ parser SearchLingo::Parsers::LTEDateParser.new Job.table_name,
31
+ :date, connection: Job.connection
32
+ parser SearchLingo::Parsers::GTEDateParser.new Job.table_name,
33
+ :date, connection: Job.connection
34
+
35
+ def default_parse(token)
36
+ [:where, 'jobs.name LIKE ?', "%#{token}%"]
37
+ end
38
+ end
39
+
40
+ class ReceiptSearch < AbstractSearch
41
+ parser Parsers::IdParser.new Receipt.table_name
42
+
43
+ parser SearchLingo::Parsers::DateParser.new Receipt.table_name,
44
+ :check_date
45
+ parser SearchLingo::Parsers::DateRangeParser.new Receipt.table_name,
46
+ :check_date
47
+ parser SearchLingo::Parsers::LTEDateParser.new Receipt.table_name,
48
+ :check_date, connection: Receipt.connection
49
+ parser SearchLingo::Parsers::GTEDateParser.new Receipt.table_name,
50
+ :check_date, connection: Receipt.connection
51
+
52
+ parser SearchLingo::Parsers::DateParser.new Receipt.table_name,
53
+ :post_date, 'posted'
54
+ parser SearchLingo::Parsers::DateRangeParser.new Receipt.table_name,
55
+ :post_date, 'posted'
56
+ parser SearchLingo::Parsers::LTEDateParser.new Receipt.table_name,
57
+ :post_date, 'posted', connection: Receipt.connection
58
+ parser SearchLingo::Parsers::GTEDateParser.new Receipt.table_name,
59
+ :post_date, 'posted', connection: Receipt.connection
60
+
61
+ parser do |token|
62
+ token.match /\Aamount: (\d+(?:\.\d+)?)\z/ do |m|
63
+ [:where, { receipts: { amount: m[1] } }]
64
+ end
65
+ end
66
+
67
+ def default_parse(token)
68
+ [:where, 'receipts.check_no LIKE ?', token]
69
+ end
70
+ end
71
+
72
+ search = JobSearch.new('6/4/15-6/5/15 id: 42 "foo bar"')
73
+ search.results # => Job
74
+ # .where('jobs' => { date: Date.new(2015, 6, 4)..Date.new(2015, 6, 5) })
75
+ # .where('jobs' => { id: '42' })
76
+ # .where('jobs.name LIKE ?', '%foo bar%')
77
+
78
+ search = ReceiptSearch.new('-6/4/15 posted: 6/5/15- amount: 1000 123')
79
+ search.results # => Receipt
80
+ # .where('"receipts"."check_date" <= ?', Date.new(2015, 6, 4))
81
+ # .where('"receipts"."post_date" >= ?', Date.new(2015, 6, 5))
82
+ # .where(receipts: { amount: '1000' })
83
+ # .where('receipts.check_no LIKE ?', 123)
@@ -0,0 +1,31 @@
1
+ class Task < ActiveRecord::Base
2
+ # Assume this model has attributes: :id, :due_date, and :name
3
+ end
4
+
5
+ class TaskSearch < SearchLingo::AbstractSearch
6
+ parser SearchLingo::Parsers::DateParser.new :tasks,
7
+ :due_date
8
+ parser SearchLingo::Parsers::DateRangeParser.new :tasks,
9
+ :due_date
10
+ parser SearchLingo::Parsers::LTEDateParser.new :tasks,
11
+ :due_date, connection: ActiveRecord::Base.connection
12
+ parser SearchLingo::Parsers::GTEDateParser.new :tasks,
13
+ :due_date, connection: ActiveRecord::Base.connection
14
+
15
+ parser do |token|
16
+ token.match /\Aid:\s*([[:digit:]]+)\z/ do |m|
17
+ [:where, { tasks: { id: m[1] } }]
18
+ end
19
+ end
20
+
21
+ def default_parse(token)
22
+ [:where, 'tasks.name LIKE ?', "%#{token}%"]
23
+ end
24
+ end
25
+
26
+ search = TaskSearch.new('6/4/15 id: 42 foo "bar baz"', Task)
27
+ search.results # => Task
28
+ # .where(tasks: { due_date: Date.new(2015, 6, 4) })
29
+ # .where(tasks: { id: '42' })
30
+ # .where('tasks.name LIKE ?', '%foo%')
31
+ # .where('tasks.name LIKE ?', '%bar baz%')
@@ -0,0 +1,63 @@
1
+ require 'search_lingo/tokenizer'
2
+
3
+ module SearchLingo
4
+ class AbstractSearch
5
+ def initialize(query, scope, tokenizer: Tokenizer)
6
+ @query = query || ''
7
+ @scope = scope
8
+ @tokenizer = tokenizer.new @query
9
+ end
10
+
11
+ attr_reader :query, :scope, :tokenizer
12
+
13
+ def self.parsers
14
+ @parsers ||= []
15
+ end
16
+
17
+ def self.parser(callable = nil, &block)
18
+ unless callable || block_given?
19
+ raise ArgumentError, '.parse must be called with callable or block'
20
+ end
21
+ if callable && block_given?
22
+ warn "WARNING: parse called with callable and block (#{caller.first}"
23
+ end
24
+
25
+ parsers << (callable || block)
26
+ end
27
+
28
+ def parsers
29
+ self.class.parsers
30
+ end
31
+
32
+ def results
33
+ @results ||= conditions.inject(scope) do |query, condition|
34
+ query.public_send(*condition)
35
+ end
36
+ end
37
+
38
+ def conditions
39
+ tokenizer.inject([]) do |conditions, token|
40
+ conditions << catch(:match) do
41
+ parse token
42
+ if token.compound?
43
+ token = tokenizer.simplify
44
+ parse token
45
+ end
46
+ default_parse token
47
+ end
48
+ end
49
+ end
50
+
51
+ def parse(token)
52
+ parsers.each do |parser|
53
+ result = parser.call token
54
+ throw :match, result if result
55
+ end
56
+ end
57
+
58
+ def default_parse(token)
59
+ raise NotImplementedError,
60
+ "#default_parse must be implemented by #{self.class}"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,34 @@
1
+ require 'search_lingo/parsers/mdy'
2
+
3
+ module SearchLingo
4
+ module Parsers
5
+ class DateParser
6
+ include MDY
7
+
8
+ def initialize(table, column, operator = nil, **options)
9
+ @table = table
10
+ @column = column
11
+ @prefix = %r{#{operator}:\s*} if operator
12
+
13
+ post_initialize **options
14
+ end
15
+
16
+ attr_reader :table, :column, :prefix
17
+
18
+ def call(token)
19
+ token.match /\A#{prefix}(?<date>#{US_DATE})\z/ do |m|
20
+ date = parse m[:date]
21
+ [:where, { table => { column => date } }] if date
22
+ end
23
+ end
24
+
25
+ def post_initialize(**)
26
+ end
27
+
28
+ def inspect
29
+ '#<%s:0x%x @table=%s @column=%s @prefix=%s>' %
30
+ [self.class, object_id << 1, table.inspect, column.inspect, prefix.inspect]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,15 @@
1
+ require 'search_lingo/parsers/date_parser'
2
+
3
+ module SearchLingo
4
+ module Parsers
5
+ class DateRangeParser < DateParser
6
+ def call(token)
7
+ token.match /\A#{prefix}(?<min>#{US_DATE})-(?<max>#{US_DATE})\z/ do |m|
8
+ min = parse m[:min]
9
+ max = parse m[:max], relative_to: min.next_year if min
10
+ [:where, { table => { column => min..max } }] if min && max
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ require 'search_lingo/parsers/date_parser'
2
+ require 'forwardable'
3
+
4
+ module SearchLingo
5
+ module Parsers
6
+ class GTEDateParser < DateParser
7
+ extend Forwardable
8
+
9
+ def call(token)
10
+ token.match /\A#{prefix}(?<date>#{US_DATE})-\z/ do |m|
11
+ date = parse m[:date]
12
+ if date
13
+ [:where, "#{quote_table_name table}.#{quote_column_name column} >= ?", date]
14
+ end
15
+ end
16
+ end
17
+
18
+ def post_initialize(connection:, **)
19
+ @connection = connection
20
+ end
21
+
22
+ def_delegators :@connection, :quote_column_name, :quote_table_name
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ require 'search_lingo/parsers/date_parser'
2
+ require 'forwardable'
3
+
4
+ module SearchLingo
5
+ module Parsers
6
+ class LTEDateParser < DateParser
7
+ extend Forwardable
8
+
9
+ def call(token)
10
+ token.match /\A#{prefix}-(?<date>#{US_DATE})\z/ do |m|
11
+ date = parse m[:date]
12
+ if date
13
+ [:where, "#{quote_table_name table}.#{quote_column_name column} <= ?", date]
14
+ end
15
+ end
16
+ end
17
+
18
+ def post_initialize(connection:, **)
19
+ @connection = connection
20
+ end
21
+
22
+ def_delegators :@connection, :quote_column_name, :quote_table_name
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ require 'date'
2
+
3
+ module SearchLingo
4
+ module Parsers
5
+ module MDY
6
+ US_DATE = %r{(?<m>\d{1,2})/(?<d>\d{1,2})(?:/(?<y>\d{2}\d{2}?))?}
7
+
8
+ def parse(term, relative_to: Date.today)
9
+ term.match /\A#{US_DATE}\z/ do |m|
10
+ return Date.parse "#{m[:y]}/#{m[:m]}/#{m[:d]}" if m[:y]
11
+
12
+ day = Integer(m[:d])
13
+ month = Integer(m[:m])
14
+ year = begin
15
+ if month < relative_to.month || month == relative_to.month && day <= relative_to.day
16
+ relative_to.year
17
+ else
18
+ relative_to.year - 1
19
+ end
20
+ end
21
+
22
+ Date.new year, month, day
23
+ end
24
+ rescue ArgumentError
25
+ end
26
+
27
+ module_function :parse
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ require 'delegate'
2
+
3
+ module SearchLingo
4
+ class Token < DelegateClass(String)
5
+ FORMAT = %r{\A(?:(\S+):\s*)?"?(.+?)"?\z}
6
+
7
+ def operator
8
+ self[FORMAT, 1]
9
+ end
10
+
11
+ def term
12
+ self[FORMAT, 2]
13
+ end
14
+
15
+ def compound?
16
+ !!operator
17
+ end
18
+
19
+ def inspect
20
+ '#<%s %s operator=%s term=%s>' % [self.class, super, operator.inspect, term.inspect]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,44 @@
1
+ require 'forwardable'
2
+ require 'strscan'
3
+ require 'search_lingo/token'
4
+
5
+ module SearchLingo
6
+ class Tokenizer
7
+ include Enumerable
8
+ extend Forwardable
9
+
10
+ SIMPLE = %r{"[^"]*"|[[:graph:]]+}
11
+ COMPOUND = %r{(?:[[:graph:]]+:[[:space:]]*)?#{SIMPLE}}
12
+ DELIMITER = %r{[[:space:]]*}
13
+
14
+ def initialize(query)
15
+ @scanner = StringScanner.new query.strip
16
+ end
17
+
18
+ def enum
19
+ Enumerator.new do |yielder|
20
+ until scanner.eos?
21
+ token = scanner.scan COMPOUND
22
+ if token
23
+ yielder << Token.new(token)
24
+ end
25
+ scanner.skip DELIMITER
26
+ end
27
+ end
28
+ end
29
+
30
+ def_delegator :scanner, :reset
31
+ def_delegators :enum, :each, :next
32
+
33
+ def simplify
34
+ scanner.unscan
35
+ Token.new(scanner.scan(SIMPLE)).tap do
36
+ scanner.skip DELIMITER
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :scanner
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module SearchLingo
2
+ VERSION = '1.0.0.beta2'
3
+ end
@@ -0,0 +1,7 @@
1
+ require 'search_lingo/version'
2
+ require 'search_lingo/abstract_search'
3
+
4
+ require 'search_lingo/parsers/date_parser'
5
+ require 'search_lingo/parsers/date_range_parser'
6
+ require 'search_lingo/parsers/gte_date_parser'
7
+ require 'search_lingo/parsers/lte_date_parser'
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'search_lingo/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "search_lingo"
8
+ spec.version = SearchLingo::VERSION
9
+ spec.authors = ["John Parker"]
10
+ spec.email = ["jparker@urgetopunt.com"]
11
+
12
+ spec.summary = %q{Framework for building simple query languages}
13
+ spec.description = %q{Framework for building simple query language for converting user queries into database queries.}
14
+ spec.homepage = "https://github.com/jparker/search_lingo"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.9"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency 'minitest'
25
+ spec.add_development_dependency 'minitest-focus'
26
+ spec.add_development_dependency 'pry'
27
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: search_lingo
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.beta2
5
+ platform: ruby
6
+ authors:
7
+ - John Parker
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-06-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-focus
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Framework for building simple query language for converting user queries
84
+ into database queries.
85
+ email:
86
+ - jparker@urgetopunt.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".travis.yml"
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - bin/console
98
+ - bin/setup
99
+ - examples/complex.rb
100
+ - examples/simple.rb
101
+ - lib/search_lingo.rb
102
+ - lib/search_lingo/abstract_search.rb
103
+ - lib/search_lingo/parsers/date_parser.rb
104
+ - lib/search_lingo/parsers/date_range_parser.rb
105
+ - lib/search_lingo/parsers/gte_date_parser.rb
106
+ - lib/search_lingo/parsers/lte_date_parser.rb
107
+ - lib/search_lingo/parsers/mdy.rb
108
+ - lib/search_lingo/token.rb
109
+ - lib/search_lingo/tokenizer.rb
110
+ - lib/search_lingo/version.rb
111
+ - search_lingo.gemspec
112
+ homepage: https://github.com/jparker/search_lingo
113
+ licenses:
114
+ - MIT
115
+ metadata: {}
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">"
128
+ - !ruby/object:Gem::Version
129
+ version: 1.3.1
130
+ requirements: []
131
+ rubyforge_project:
132
+ rubygems_version: 2.4.7
133
+ signing_key:
134
+ specification_version: 4
135
+ summary: Framework for building simple query languages
136
+ test_files: []