structured_search 0.0.1
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +23 -0
- data/lib/structured_search.rb +25 -0
- data/lib/structured_search/base_node.rb +23 -0
- data/lib/structured_search/errors.rb +22 -0
- data/lib/structured_search/evaluator.rb +89 -0
- data/lib/structured_search/lexer.rb +89 -0
- data/lib/structured_search/parser.rb +134 -0
- data/lib/structured_search/patterns.rb +45 -0
- data/lib/structured_search/token.rb +27 -0
- data/lib/structured_search/tree/asterisk.rb +16 -0
- data/lib/structured_search/tree/from.rb +23 -0
- data/lib/structured_search/tree/select.rb +33 -0
- data/lib/structured_search/tree/statement.rb +13 -0
- data/lib/structured_search/tree/string.rb +15 -0
- data/lib/structured_search/tree/where.rb +14 -0
- data/lib/structured_search/version.rb +4 -0
- data/lib/tasks/structured_search_tasks.rake +4 -0
- metadata +175 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: d44d9c424f6c8016aa9f87f520eb13fa78597259
|
|
4
|
+
data.tar.gz: efcd6f229905161abdccde0eb5740c94baefdbcf
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ca7d8fde1e85b7cd55d862b98f90142cb27d2914b464ccd1d5692f3fa2f43df31e6372c7f69048581c964a3f1f851df90a88762c2261465e4b089d593c5ec852
|
|
7
|
+
data.tar.gz: 7640c8320709de35fe5e4c92b017e68280214f6b02cc22f522863c4dbdec49178c2755dd36b6d287a477d0c1171731b00ee070277a8eaa8768bd08b7f525678a
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2014 YOURNAME
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'bundler/setup'
|
|
3
|
+
rescue LoadError
|
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
require 'rdoc/task'
|
|
8
|
+
|
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
11
|
+
rdoc.title = 'StructuredSearch'
|
|
12
|
+
rdoc.options << '--line-numbers'
|
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# route :default to :spec for travis ci
|
|
18
|
+
require 'rspec/core/rake_task'
|
|
19
|
+
task :default => :spec
|
|
20
|
+
RSpec::Core::RakeTask.new
|
|
21
|
+
|
|
22
|
+
Bundler::GemHelper.install_tasks
|
|
23
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'structured_search/lexer'
|
|
2
|
+
require 'structured_search/parser'
|
|
3
|
+
require 'structured_search/evaluator'
|
|
4
|
+
require 'structured_search/errors'
|
|
5
|
+
require 'structured_search/patterns'
|
|
6
|
+
require 'structured_search/token'
|
|
7
|
+
require 'structured_search/base_node'
|
|
8
|
+
|
|
9
|
+
module StructuredSearch
|
|
10
|
+
class << self
|
|
11
|
+
|
|
12
|
+
# Creates an evaluator instance, with a given input and provider hash
|
|
13
|
+
# and returns the evaluator result.
|
|
14
|
+
# Params:
|
|
15
|
+
# +input+:: Input string to parse and evaluate.
|
|
16
|
+
# +providers+:: The search provider keys and classes.
|
|
17
|
+
def evaluate(input, providers)
|
|
18
|
+
parser = StructuredSearch::Parser.new(input, providers)
|
|
19
|
+
parser.parse_to_end
|
|
20
|
+
@evaluator = StructuredSearch::Evaluator.new(parser.statements)
|
|
21
|
+
@evaluator.eval if @evaluator
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module StructuredSearch
|
|
2
|
+
module Tree
|
|
3
|
+
|
|
4
|
+
# The base of any node in the AST, it stores the line,
|
|
5
|
+
# column and type of token.
|
|
6
|
+
class BaseNode
|
|
7
|
+
|
|
8
|
+
#+line+:: The line this AST node was found
|
|
9
|
+
#+column+:: The column this AST node was found
|
|
10
|
+
#+type+:: Holds the type of node for fast lookup
|
|
11
|
+
attr_accessor :line, :column, :type
|
|
12
|
+
|
|
13
|
+
# sends each token value to it's respective attribute
|
|
14
|
+
def initialize(topts = {})
|
|
15
|
+
topts.each { |key, val| send "#{key}=", val }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# require all parse tree files
|
|
20
|
+
Dir[File.expand_path("../tree/*", __FILE__)].each { |f| require f }
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module StructuredSearch
|
|
2
|
+
|
|
3
|
+
# Raised when an error occurs during lexical analysis,
|
|
4
|
+
# such as an incorrect character in the input
|
|
5
|
+
class LexicalError < StandardError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Raised when an error occurs during parsing, such as
|
|
9
|
+
# an invalid token in the stream (FROM before a SELECT etc.)
|
|
10
|
+
class SyntaxError < StandardError
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Raised when a provider is referenced, but no provider
|
|
14
|
+
# class is given to the evaluator
|
|
15
|
+
class UnregisteredProviderError < StandardError
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Raised when a provider does not have a method required
|
|
19
|
+
# by StructuredSearch (e.g. doesn't respond to search() etc.)
|
|
20
|
+
class ProviderNotAvailableError < StandardError
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require 'structured_search/errors'
|
|
2
|
+
|
|
3
|
+
module StructuredSearch
|
|
4
|
+
|
|
5
|
+
# Evaluates one or more statements that make up a query.
|
|
6
|
+
# Each statement is made up of a series of AST nodes produced
|
|
7
|
+
# by the parser, from input generated by the lexer.
|
|
8
|
+
class Evaluator
|
|
9
|
+
|
|
10
|
+
# statements to be evaluated
|
|
11
|
+
attr_accessor :statements
|
|
12
|
+
|
|
13
|
+
#:nodoc:
|
|
14
|
+
def initialize(statements = [])
|
|
15
|
+
@statements = statements
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Evaluate every statement given to the initializer.
|
|
19
|
+
# Returns:
|
|
20
|
+
# +result+:: An array containing the result of each statement
|
|
21
|
+
def eval
|
|
22
|
+
results = []
|
|
23
|
+
|
|
24
|
+
@statements.each do |s|
|
|
25
|
+
st_res = eval_statement(s)
|
|
26
|
+
results << st_res if st_res
|
|
27
|
+
end
|
|
28
|
+
results
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Evaluates a single statement.
|
|
32
|
+
# Returns:
|
|
33
|
+
# +result+:: An array containing the result of the statement
|
|
34
|
+
def eval_statement(statement)
|
|
35
|
+
search_terms = results = []
|
|
36
|
+
|
|
37
|
+
statement.each do |node|
|
|
38
|
+
|
|
39
|
+
case node.type
|
|
40
|
+
when :SELECT
|
|
41
|
+
search_terms = node.search_terms
|
|
42
|
+
when :FROM
|
|
43
|
+
results = provider_lookup(node, search_terms)
|
|
44
|
+
when :WHERE
|
|
45
|
+
node.constraints.each do
|
|
46
|
+
# results = send :method, results (or something like that)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
results
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# Performs a search operation, looking up each search term
|
|
57
|
+
# on each provider (by way of Provider#search), and storing it in a Hash.
|
|
58
|
+
# Parameters:
|
|
59
|
+
# +node+:: The FROM node that contains search provider sources
|
|
60
|
+
# +search_terms+:: The search terms from a SELECT node, as an array
|
|
61
|
+
# Returns:
|
|
62
|
+
# +provider_results+::A Hash that contains the provider as a key, and the
|
|
63
|
+
# search term as a value, which can be used to lookup the result via
|
|
64
|
+
# 'result[:provider][:search_term]'.
|
|
65
|
+
def provider_lookup(node, search_terms)
|
|
66
|
+
provider_results = {}
|
|
67
|
+
search_results = {}
|
|
68
|
+
|
|
69
|
+
node.sources.each do |provider, prov_class|
|
|
70
|
+
if prov_class.respond_to? :search
|
|
71
|
+
search_terms.each do |search_term|
|
|
72
|
+
search_result = prov_class.send :search, search_term
|
|
73
|
+
search_results[search_term] = search_result if search_result
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
provider_results[provider] = search_results
|
|
77
|
+
|
|
78
|
+
else
|
|
79
|
+
raise ProviderNotAvailableError, "Method 'search' not available for provider #{provider}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
provider_results
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require 'structured_search/token'
|
|
2
|
+
require 'structured_search/errors'
|
|
3
|
+
|
|
4
|
+
module StructuredSearch
|
|
5
|
+
|
|
6
|
+
# Converts the input into a token stream, that can be worked
|
|
7
|
+
# by the syntax parser.
|
|
8
|
+
class Lexer
|
|
9
|
+
|
|
10
|
+
# +input+:: Input string to parse
|
|
11
|
+
# +column+:: Current column position
|
|
12
|
+
# +line+:: Current position in the line
|
|
13
|
+
# +lexer_offset+:: Current character position in the input string
|
|
14
|
+
attr_accessor :input, :column, :line, :lexer_offset
|
|
15
|
+
|
|
16
|
+
# Returns the current state of the lexer, by way of input,
|
|
17
|
+
# current line and column.
|
|
18
|
+
def state
|
|
19
|
+
{ input: input, column: column, line: line }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Sets the state of the lexer
|
|
23
|
+
def state=(state)
|
|
24
|
+
@input = state[:input]
|
|
25
|
+
@column = state[:column]
|
|
26
|
+
@line = state[:line]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Creates a new instance of the Lexer.
|
|
30
|
+
# Params:
|
|
31
|
+
# +input+:: The SQL input that will be parsed.
|
|
32
|
+
def initialize(input)
|
|
33
|
+
@input = input
|
|
34
|
+
@column = 1
|
|
35
|
+
@line = 1
|
|
36
|
+
@lexer_offset = 0
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Scans the input, matching each token that appears and
|
|
40
|
+
# returns the token. Supports both read and peek operations
|
|
41
|
+
# determined by the state of the peek flag.
|
|
42
|
+
# Params:
|
|
43
|
+
# +is_peek+:: Whether the lexer will consume the token, or
|
|
44
|
+
# remain in it's current position (false by default)
|
|
45
|
+
# Returns:
|
|
46
|
+
# +token+:: A StructuredSeach::Token is returned to the caller.
|
|
47
|
+
def scan(is_peek = false)
|
|
48
|
+
PATTERNS.each do |pattern|
|
|
49
|
+
match = pattern[1].match(@input, @lexer_offset)
|
|
50
|
+
if match
|
|
51
|
+
token_data = { token: pattern[0],
|
|
52
|
+
lexeme: pattern[2] ? pattern[2].call(match) : '',
|
|
53
|
+
line: @line, column: @column }
|
|
54
|
+
token = Token.new(token_data)
|
|
55
|
+
|
|
56
|
+
# increment line and col position if a read op:
|
|
57
|
+
if !is_peek
|
|
58
|
+
tok_length = match[0].size
|
|
59
|
+
newline_count = match[0].count("\n")
|
|
60
|
+
@lexer_offset += tok_length
|
|
61
|
+
@line += newline_count
|
|
62
|
+
@column = 1 if newline_count
|
|
63
|
+
@column += tok_length - (match[0].rindex("\n") || 0)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# clear any whitespace
|
|
67
|
+
if pattern[0] == :WHITESPACE
|
|
68
|
+
@lexer_offset += match[0].size
|
|
69
|
+
return scan(is_peek)
|
|
70
|
+
else
|
|
71
|
+
return token
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# have we underrun the input due to lex error?:
|
|
78
|
+
if @lexer_offset < @input.size
|
|
79
|
+
raise LexicalError, "Unexpected character \"#{@input[@lexer_offset+1]}\" at (Line #{@line}, Column #{@column})"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
module StructuredSearch
|
|
2
|
+
|
|
3
|
+
# Parses a token stream, returning an array of StructuredSearch::Statement
|
|
4
|
+
class Parser
|
|
5
|
+
|
|
6
|
+
# stores all parse tree nodes
|
|
7
|
+
attr_reader :statements, :providers
|
|
8
|
+
|
|
9
|
+
# Creates a new instance of the Parser, taking a Lexer and a hash of
|
|
10
|
+
# providers (An identifier and class that contains the search method)
|
|
11
|
+
# Params:
|
|
12
|
+
# +lexer+:: A StructuredSearch::Lexer object
|
|
13
|
+
# +providers+:: A Hash of provider names and their classes
|
|
14
|
+
def initialize(input, providers)
|
|
15
|
+
@lexer = StructuredSearch::Lexer.new(input)
|
|
16
|
+
@providers = Hash.new
|
|
17
|
+
providers.each { |k,v| @providers[k.downcase] = v }
|
|
18
|
+
|
|
19
|
+
@nodes, @statements = [], []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Reads the next token in the stream
|
|
23
|
+
def read_token
|
|
24
|
+
@lexer.scan
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Peeks at the next token in stream
|
|
28
|
+
def peek_token
|
|
29
|
+
@lexer.scan(true)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Parses the token stream into statements until there
|
|
33
|
+
# are no more tokens left
|
|
34
|
+
def parse_to_end
|
|
35
|
+
while peek_token
|
|
36
|
+
new_node = parse
|
|
37
|
+
@nodes << new_node if new_node
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# flush token stream to a statement if no semicolon
|
|
41
|
+
new_statement if @nodes.length > 0
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Parses the next token in the token stream into an AST node
|
|
45
|
+
# Returns an AST node
|
|
46
|
+
def parse
|
|
47
|
+
@current_token = read_token
|
|
48
|
+
|
|
49
|
+
case @current_token.token
|
|
50
|
+
when :SEMICOLON
|
|
51
|
+
new_statement
|
|
52
|
+
else
|
|
53
|
+
send "new_#{@current_token.token.downcase}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Basic options given to BaseNode when creating a new instance
|
|
58
|
+
# of a node, including the line, column and type
|
|
59
|
+
def basic_options
|
|
60
|
+
{ line: @current_token.line, column: @current_token.column, type: @current_token.token }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# Creates a new Tree::Select
|
|
66
|
+
def new_select
|
|
67
|
+
quant_list = [ :ALL, :DISTINCT ]
|
|
68
|
+
select_list = [ :ASTERISK, :STRING ]
|
|
69
|
+
|
|
70
|
+
select_tok = Tree::Select.new(basic_options)
|
|
71
|
+
|
|
72
|
+
# handle an optional set quantifier (ALL or DISTINCT)
|
|
73
|
+
if quant_list.include? peek_token.token
|
|
74
|
+
select_tok.set_quantifier = read_token.token
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# handle a select list (ASTERISK or search terms)
|
|
78
|
+
if select_list.include? peek_token.token
|
|
79
|
+
|
|
80
|
+
# read in all search terms from the query:
|
|
81
|
+
while peek_token.token == :STRING
|
|
82
|
+
select_tok.add_search_term(read_token.lexeme)
|
|
83
|
+
read_token if peek_token.token == :COMMA
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
else
|
|
87
|
+
raise SyntaxError "No valid select list given (#{error_location})"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
select_tok
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Creates a new Tree::From
|
|
94
|
+
def new_from
|
|
95
|
+
source_tokens = [ :STRING ]
|
|
96
|
+
from_tok = Tree::From.new(basic_options)
|
|
97
|
+
|
|
98
|
+
# read in all the derived columns - search sources:
|
|
99
|
+
while peek_token and source_tokens.include? peek_token.token
|
|
100
|
+
src_token = read_token
|
|
101
|
+
# check if the provider is registered in the given list:
|
|
102
|
+
if provider_exists? src_token.lexeme.downcase
|
|
103
|
+
from_tok.sources[src_token.lexeme] = @providers[src_token.lexeme.downcase.to_sym]
|
|
104
|
+
else raise UnregisteredProviderError, "#{src_token.lexeme} is not a registered provider"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
if from_tok.sources.count == 0
|
|
109
|
+
raise SyntaxError, "No search sources given (#{error_location})"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
from_tok
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Creates a new Tree::Statement
|
|
116
|
+
def new_statement
|
|
117
|
+
@statements.push Tree::Statement.new(@nodes)
|
|
118
|
+
@nodes = [] # reset node array
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Checks whether a search provider class exists
|
|
123
|
+
def provider_exists?(source)
|
|
124
|
+
@providers.has_key? source.to_sym
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Returns a string that will inform the user where an error
|
|
128
|
+
# has occurred.
|
|
129
|
+
def error_location
|
|
130
|
+
"Line #{basic_options[:line]}, Column #{basic_options[:column]}"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module StructuredSearch
|
|
2
|
+
|
|
3
|
+
class Lexer
|
|
4
|
+
|
|
5
|
+
# SQL reserved words list
|
|
6
|
+
RESERVED = %w{
|
|
7
|
+
SELECT ALL DISTINCT FROM WHERE ASC DESC
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
# Pattern hash of token keys and regex values
|
|
11
|
+
PATTERNS = [
|
|
12
|
+
|
|
13
|
+
[:WHITESPACE, /[\r\v\f\t ]+/],
|
|
14
|
+
[:TERMINATOR, /[\r\n]/],
|
|
15
|
+
|
|
16
|
+
# intern reserved words and their patterns
|
|
17
|
+
*RESERVED.map { |rw| [rw.intern, /#{rw}(?=[^A-z0-9_])/] },
|
|
18
|
+
|
|
19
|
+
# match single / double quoted strings
|
|
20
|
+
[:STRING, /(['"])(\\n|\\.|((?!\1)(?!\\)|.)*?((?!\1)(?!\\).)?)\1/, -> match { match[2] } ],
|
|
21
|
+
[:L_PAREN, /\(/],
|
|
22
|
+
[:R_PAREN, /\)/],
|
|
23
|
+
[:L_BRACKET, /\[/],
|
|
24
|
+
[:R_BRACKET, /\]/],
|
|
25
|
+
[:L_BRACE, /\{/],
|
|
26
|
+
[:R_BRACE, /\}/],
|
|
27
|
+
[:PERCENT, /%/ ],
|
|
28
|
+
[:AMPERSAND, /&/ ],
|
|
29
|
+
[:ASTERISK, /\*/],
|
|
30
|
+
[:PLUS, /\+/],
|
|
31
|
+
[:MINUS, /-/],
|
|
32
|
+
[:COMMA, /,/ ],
|
|
33
|
+
[:PERIOD, /\./],
|
|
34
|
+
[:COLON, /:/ ],
|
|
35
|
+
[:SEMICOLON, /;/ ],
|
|
36
|
+
[:LEQ, /<=/],
|
|
37
|
+
[:EQUALS, /=/ ],
|
|
38
|
+
[:GEQ, />=/],
|
|
39
|
+
[:QUESTION, /\?/],
|
|
40
|
+
[:CIRCUMFLEX, /\^/],
|
|
41
|
+
[:UNDERSCORE, /_/ ],
|
|
42
|
+
[:PIPE, /\|/]
|
|
43
|
+
].map { |pattern| [pattern[0], /\G#{pattern[1]}/m, pattern[2]] }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module StructuredSearch
|
|
2
|
+
|
|
3
|
+
# A token generated by the lexer
|
|
4
|
+
class Token
|
|
5
|
+
|
|
6
|
+
#+token+:: The token's type
|
|
7
|
+
#+lexeme+:: The matched characters that make up the token
|
|
8
|
+
#+column+:: Column this token was located
|
|
9
|
+
#+line+:: Line this token was located
|
|
10
|
+
attr_accessor :token, :lexeme, :column, :line
|
|
11
|
+
|
|
12
|
+
#:nodoc:
|
|
13
|
+
def initialize(tok = {})
|
|
14
|
+
#send val to key setter
|
|
15
|
+
tok.each do |k, v|
|
|
16
|
+
send "#{k}=", v
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns a string representation of the token
|
|
21
|
+
def to_s
|
|
22
|
+
"#{@token}: '#{@lexeme}' (Line #{@line}, Column #{@column})"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module StructuredSearch
|
|
2
|
+
module Tree
|
|
3
|
+
|
|
4
|
+
# FROM reserved word node
|
|
5
|
+
class From < BaseNode
|
|
6
|
+
|
|
7
|
+
# in sql spec, this is where we store all the
|
|
8
|
+
# derived columns, such as Google, Omniref etc
|
|
9
|
+
#
|
|
10
|
+
# This is a hash containing the symbol reference
|
|
11
|
+
# and a class reference to what generates the query
|
|
12
|
+
attr_accessor :sources
|
|
13
|
+
|
|
14
|
+
#:nodoc:
|
|
15
|
+
def initialize(*argv)
|
|
16
|
+
@sources = {}
|
|
17
|
+
super *argv
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module StructuredSearch
|
|
2
|
+
module Tree
|
|
3
|
+
|
|
4
|
+
##
|
|
5
|
+
# SELECT reserved word node
|
|
6
|
+
class Select < BaseNode
|
|
7
|
+
|
|
8
|
+
# +set_quantifier+:: Whether this search uses ALL or DISTINCT
|
|
9
|
+
# +search_terms+:: The search terms we're looking for
|
|
10
|
+
attr_accessor :set_quantifier, :search_terms
|
|
11
|
+
|
|
12
|
+
# Sets the set quantifier (either ALL or DISTINCT) for this statement
|
|
13
|
+
def set_quantifier=(quantifier)
|
|
14
|
+
if [:ALL, :DISTINCT].include? quantifier
|
|
15
|
+
@set_quantifier = quantifier
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Adds a search term to the list of terms
|
|
20
|
+
def add_search_term(term)
|
|
21
|
+
@search_terms.push term
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
#:nodoc:
|
|
25
|
+
def initialize(*argv)
|
|
26
|
+
@search_terms = []
|
|
27
|
+
@set_quantifier = :ALL
|
|
28
|
+
super *argv
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: structured_search
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Matt Ryder
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-11-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '4.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '4.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: coveralls
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: sqlite3
|
|
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: rspec-rails
|
|
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: guard-rspec
|
|
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
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: guard-spork
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: byebug
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: google_custom_search_api
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
description: Use SQL to query search engines and APIs.
|
|
126
|
+
email:
|
|
127
|
+
- matt@mattryder.co.uk
|
|
128
|
+
executables: []
|
|
129
|
+
extensions: []
|
|
130
|
+
extra_rdoc_files: []
|
|
131
|
+
files:
|
|
132
|
+
- MIT-LICENSE
|
|
133
|
+
- README.rdoc
|
|
134
|
+
- Rakefile
|
|
135
|
+
- lib/structured_search.rb
|
|
136
|
+
- lib/structured_search/base_node.rb
|
|
137
|
+
- lib/structured_search/errors.rb
|
|
138
|
+
- lib/structured_search/evaluator.rb
|
|
139
|
+
- lib/structured_search/lexer.rb
|
|
140
|
+
- lib/structured_search/parser.rb
|
|
141
|
+
- lib/structured_search/patterns.rb
|
|
142
|
+
- lib/structured_search/token.rb
|
|
143
|
+
- lib/structured_search/tree/asterisk.rb
|
|
144
|
+
- lib/structured_search/tree/from.rb
|
|
145
|
+
- lib/structured_search/tree/select.rb
|
|
146
|
+
- lib/structured_search/tree/statement.rb
|
|
147
|
+
- lib/structured_search/tree/string.rb
|
|
148
|
+
- lib/structured_search/tree/where.rb
|
|
149
|
+
- lib/structured_search/version.rb
|
|
150
|
+
- lib/tasks/structured_search_tasks.rake
|
|
151
|
+
homepage: http://www.mattryder.co.uk
|
|
152
|
+
licenses:
|
|
153
|
+
- MIT
|
|
154
|
+
metadata: {}
|
|
155
|
+
post_install_message:
|
|
156
|
+
rdoc_options: []
|
|
157
|
+
require_paths:
|
|
158
|
+
- lib
|
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
160
|
+
requirements:
|
|
161
|
+
- - ">="
|
|
162
|
+
- !ruby/object:Gem::Version
|
|
163
|
+
version: '0'
|
|
164
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
165
|
+
requirements:
|
|
166
|
+
- - ">="
|
|
167
|
+
- !ruby/object:Gem::Version
|
|
168
|
+
version: '0'
|
|
169
|
+
requirements: []
|
|
170
|
+
rubyforge_project:
|
|
171
|
+
rubygems_version: 2.4.7
|
|
172
|
+
signing_key:
|
|
173
|
+
specification_version: 4
|
|
174
|
+
summary: Use SQL to query search engines and APIs.
|
|
175
|
+
test_files: []
|