command_search 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/command_search.rb +6 -7
- data/lib/command_search/aliaser.rb +10 -19
- data/lib/command_search/backends/memory.rb +31 -39
- data/lib/command_search/backends/mongoer.rb +9 -25
- data/lib/command_search/lexer.rb +4 -6
- data/lib/command_search/normalizer.rb +71 -54
- data/lib/command_search/optimizer.rb +9 -10
- data/lib/command_search/parser.rb +64 -41
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa471a870d21295459efa58aa18e3777752841dc0cfec3033a6423996ed2d98c
|
4
|
+
data.tar.gz: 37a0b72e6cb764c90fd14eb44b0a1c096d4e6c6e2756a4715db3e02471aa6889
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe02049b5e6eec1b62ffa6d030a747312b661f4db05befb66f5e3585bf0b5b679f65c10c7ba3348a19de97ecdb0d4f9f19b2e5466b75bd4604658b66021c6fe0
|
7
|
+
data.tar.gz: a5e0fa90644d5c7dd3346e1b9c7e43ef04c6e652f9f293ac59b468a2667bb388be8614854680612b62cda2812179fd800ee45a9e4229c7d4774760facd2b0b79
|
data/lib/command_search.rb
CHANGED
@@ -12,23 +12,22 @@ class Boolean; end
|
|
12
12
|
module CommandSearch
|
13
13
|
module_function
|
14
14
|
|
15
|
-
def search(source, query, options
|
15
|
+
def search(source, query, options)
|
16
16
|
aliases = options[:aliases] || {}
|
17
|
-
fields = options[:fields] ||
|
18
|
-
command_fields = options[:command_fields] || {}
|
17
|
+
fields = options[:fields] || {}
|
19
18
|
|
20
19
|
aliased_query = Aliaser.alias(query, aliases)
|
21
20
|
ast = Lexer.lex(aliased_query)
|
21
|
+
|
22
22
|
Parser.parse!(ast)
|
23
23
|
Optimizer.optimize!(ast)
|
24
|
-
|
24
|
+
Normalizer.normalize!(ast, fields)
|
25
25
|
|
26
26
|
if source.respond_to?(:mongo_client) && source.queryable
|
27
|
-
|
28
|
-
mongo_query = Mongoer.build_query(ast, fields, command_fields)
|
27
|
+
mongo_query = Mongoer.build_query(ast)
|
29
28
|
return source.where(mongo_query)
|
30
29
|
end
|
31
30
|
|
32
|
-
source.select { |x| Memory.check(x, ast
|
31
|
+
source.select { |x| Memory.check(x, ast) }
|
33
32
|
end
|
34
33
|
end
|
@@ -3,9 +3,9 @@ module CommandSearch
|
|
3
3
|
module_function
|
4
4
|
|
5
5
|
def build_regex(str)
|
6
|
-
|
7
|
-
|
8
|
-
Regexp.new(
|
6
|
+
head = '(?<=^|[^:\w])'
|
7
|
+
tail = '(?=$|\W)'
|
8
|
+
Regexp.new(head + Regexp.escape(str) + tail, 'i')
|
9
9
|
end
|
10
10
|
|
11
11
|
def quotes?(str, offset)
|
@@ -16,30 +16,21 @@ module CommandSearch
|
|
16
16
|
false
|
17
17
|
end
|
18
18
|
|
19
|
-
def alias_item(query,
|
20
|
-
|
21
|
-
|
22
|
-
pattern = alias_key
|
23
|
-
elsif alias_key.is_a?(String)
|
24
|
-
pattern = build_regex(alias_key)
|
25
|
-
else
|
26
|
-
return query
|
19
|
+
def alias_item!(query, key, val)
|
20
|
+
if key.is_a?(String) || key.is_a?(Symbol)
|
21
|
+
key = build_regex(key)
|
27
22
|
end
|
28
|
-
query.gsub!(
|
23
|
+
query.gsub!(key) do |match|
|
29
24
|
next match if quotes?(query, Regexp.last_match.offset(0))
|
30
|
-
if
|
31
|
-
|
32
|
-
elsif alias_value.is_a?(Proc)
|
33
|
-
alias_value.call(match).to_s
|
34
|
-
end
|
25
|
+
next val.call(match) if val.is_a?(Proc)
|
26
|
+
val
|
35
27
|
end
|
36
|
-
query
|
37
28
|
end
|
38
29
|
|
39
30
|
def alias(query, aliases)
|
40
31
|
return query unless aliases.any?
|
41
32
|
out = query.dup
|
42
|
-
aliases.each { |(k, v)| alias_item(out, k, v) }
|
33
|
+
aliases.each { |(k, v)| alias_item!(out, k, v) }
|
43
34
|
out
|
44
35
|
end
|
45
36
|
end
|
@@ -4,65 +4,57 @@ module CommandSearch
|
|
4
4
|
|
5
5
|
def command_check(item, val)
|
6
6
|
cmd = val[0][:value]
|
7
|
-
|
8
|
-
item_val = item[cmd.to_sym] || item[cmd]
|
9
|
-
|
10
|
-
|
11
|
-
if
|
12
|
-
!!item_val ==
|
13
|
-
elsif
|
7
|
+
search = val[1][:value]
|
8
|
+
item_val = item[cmd.to_sym] || item[cmd.to_s]
|
9
|
+
type = val[1][:type]
|
10
|
+
type = Boolean if type == :existence && search == true
|
11
|
+
if type == Boolean
|
12
|
+
!!item_val == search
|
13
|
+
elsif type == :existence
|
14
14
|
item_val == nil
|
15
15
|
elsif !item_val
|
16
16
|
return false
|
17
|
-
elsif
|
17
|
+
elsif search.is_a?(Regexp)
|
18
|
+
item_val.to_s[search]
|
19
|
+
# for versions ruby 2.4.0 (2016-12-25) and up, match? is much faster
|
20
|
+
# item_val.to_s.match?(search)
|
21
|
+
elsif type == Time
|
18
22
|
item_time = item_val.to_time
|
19
|
-
|
20
|
-
elsif cmd_search.is_a?(Regexp)
|
21
|
-
item_val[cmd_search]
|
22
|
-
elsif cmd_search == ''
|
23
|
-
item_val == cmd_search
|
23
|
+
search.first <= item_time && item_time < search.last
|
24
24
|
else
|
25
|
-
item_val
|
25
|
+
item_val == search
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
def compare_check(item, node
|
30
|
-
|
31
|
-
cmd_val = cmd[:value]
|
32
|
-
cmd_type = cmd_types[cmd[:value].to_sym]
|
29
|
+
def compare_check(item, node)
|
30
|
+
cmd_val = node[:value].first[:value]
|
33
31
|
item_val = item[cmd_val.to_sym] || item[cmd_val.to_s]
|
34
|
-
|
35
|
-
val =
|
32
|
+
return unless item_val
|
33
|
+
val = node[:value].last[:value]
|
36
34
|
if val.is_a?(Time)
|
37
|
-
item_val = item_val.to_time
|
38
|
-
elsif
|
35
|
+
item_val = item_val.to_time
|
36
|
+
elsif node[:compare_across_fields]
|
39
37
|
val = item[val.to_sym] || item[val.to_s]
|
40
38
|
end
|
41
|
-
|
42
|
-
return unless args.all?
|
39
|
+
return unless val
|
43
40
|
fn = node[:nest_op].to_sym.to_proc
|
44
|
-
fn.call(
|
41
|
+
fn.call(item_val.to_f, val.to_f)
|
45
42
|
end
|
46
43
|
|
47
|
-
def check(item, ast
|
44
|
+
def check(item, ast)
|
48
45
|
ast.all? do |node|
|
49
46
|
val = node[:value]
|
50
|
-
case node[:
|
51
|
-
when nil
|
52
|
-
fields.any? do |x|
|
53
|
-
item_val = item[x.to_sym] || item[x.to_s]
|
54
|
-
item_val.to_s[val] if item_val
|
55
|
-
end
|
47
|
+
case node[:type]
|
56
48
|
when :colon
|
57
49
|
command_check(item, val)
|
58
50
|
when :compare
|
59
|
-
compare_check(item, node
|
60
|
-
when :
|
61
|
-
!val.all? { |v| check(item, [v]
|
62
|
-
when :
|
63
|
-
val.any? { |v| check(item, [v]
|
64
|
-
when :
|
65
|
-
val.all? { |v| check(item, [v]
|
51
|
+
compare_check(item, node)
|
52
|
+
when :not
|
53
|
+
!val.all? { |v| check(item, [v]) }
|
54
|
+
when :or
|
55
|
+
val.any? { |v| check(item, [v]) }
|
56
|
+
when :and
|
57
|
+
val.all? { |v| check(item, [v]) }
|
66
58
|
end
|
67
59
|
end
|
68
60
|
end
|
@@ -2,20 +2,6 @@ module CommandSearch
|
|
2
2
|
module Mongoer
|
3
3
|
module_function
|
4
4
|
|
5
|
-
def build_search(node, fields, cmd_fields)
|
6
|
-
val = node[:value]
|
7
|
-
forms = fields.map do |field|
|
8
|
-
type = cmd_fields[field.to_sym]
|
9
|
-
if type == Numeric
|
10
|
-
{ field => node[:number_value] }
|
11
|
-
else
|
12
|
-
{ field => val }
|
13
|
-
end
|
14
|
-
end
|
15
|
-
return forms if forms.count < 2
|
16
|
-
{ '$or' => forms }
|
17
|
-
end
|
18
|
-
|
19
5
|
def build_command(node)
|
20
6
|
(field_node, search_node) = node[:value]
|
21
7
|
key = field_node[:value]
|
@@ -41,12 +27,12 @@ module CommandSearch
|
|
41
27
|
{ key => val }
|
42
28
|
end
|
43
29
|
|
44
|
-
def build_compare(node
|
30
|
+
def build_compare(node)
|
45
31
|
op_map = { '<' => '$lt', '>' => '$gt', '<=' => '$lte', '>=' => '$gte' }
|
46
32
|
op = op_map[node[:nest_op]]
|
47
33
|
key = node[:value][0][:value]
|
48
34
|
val = node[:value][1][:value]
|
49
|
-
if
|
35
|
+
if node[:compare_across_fields]
|
50
36
|
val = '$' + val
|
51
37
|
key = '$' + key
|
52
38
|
val = [key, val]
|
@@ -55,31 +41,29 @@ module CommandSearch
|
|
55
41
|
{ key => { op => val } }
|
56
42
|
end
|
57
43
|
|
58
|
-
def build_searches!(ast
|
59
|
-
mongo_types = {
|
44
|
+
def build_searches!(ast)
|
45
|
+
mongo_types = { and: '$and', or: '$or', not: '$nor' }
|
60
46
|
ast.map! do |node|
|
61
|
-
type = node[:
|
47
|
+
type = node[:type]
|
62
48
|
if type == :colon
|
63
49
|
build_command(node)
|
64
50
|
elsif type == :compare
|
65
|
-
build_compare(node
|
51
|
+
build_compare(node)
|
66
52
|
elsif key = mongo_types[type]
|
67
|
-
build_searches!(node[:value]
|
53
|
+
build_searches!(node[:value])
|
68
54
|
val = node[:value]
|
69
55
|
if key == '$nor' && val.count > 1
|
70
56
|
next { key => [{ '$and' => val }] }
|
71
57
|
end
|
72
58
|
val.map! { |x| x['$or'] || x }.flatten! unless key == '$and'
|
73
59
|
{ key => val }
|
74
|
-
else
|
75
|
-
build_search(node, fields, cmd_fields)
|
76
60
|
end
|
77
61
|
end
|
78
62
|
ast.flatten!
|
79
63
|
end
|
80
64
|
|
81
|
-
def build_query(ast
|
82
|
-
build_searches!(ast
|
65
|
+
def build_query(ast)
|
66
|
+
build_searches!(ast)
|
83
67
|
return {} if ast == []
|
84
68
|
return ast.first if ast.count == 1
|
85
69
|
{ '$and' => ast }
|
data/lib/command_search/lexer.rb
CHANGED
@@ -12,15 +12,15 @@ module CommandSearch
|
|
12
12
|
next i += Regexp.last_match[0].length
|
13
13
|
when /\A"(.*?)"/
|
14
14
|
match = Regexp.last_match[1]
|
15
|
-
type = :
|
15
|
+
type = :quote
|
16
16
|
when /\A'(.*?)'/
|
17
17
|
match = Regexp.last_match[1]
|
18
|
-
type = :
|
18
|
+
type = :quote
|
19
19
|
when /\A\-?\d+(\.\d+)?(?=$|[\s"':|<>()])/
|
20
20
|
type = :number
|
21
21
|
when /\A-/
|
22
22
|
type = :minus
|
23
|
-
when /\A[^\s
|
23
|
+
when /\A[^\s:|<>()]+/
|
24
24
|
type = :str
|
25
25
|
when /\A\|+/
|
26
26
|
type = :pipe
|
@@ -30,10 +30,8 @@ module CommandSearch
|
|
30
30
|
type = :colon
|
31
31
|
when /\A[<>]=?/
|
32
32
|
type = :compare
|
33
|
-
when /\A./
|
34
|
-
type = :str
|
35
33
|
end
|
36
|
-
match
|
34
|
+
match ||= Regexp.last_match[0]
|
37
35
|
out.push(type: type, value: match)
|
38
36
|
i += Regexp.last_match[0].length
|
39
37
|
end
|
@@ -4,19 +4,21 @@ module CommandSearch
|
|
4
4
|
module Normalizer
|
5
5
|
module_function
|
6
6
|
|
7
|
-
def cast_bool(
|
7
|
+
def cast_bool!(field, node)
|
8
|
+
type = field.is_a?(Hash) ? field[:type] : field
|
8
9
|
if type == Boolean
|
10
|
+
return if field.is_a?(Hash) && field[:general_search] && !node[:value][/\Atrue\Z|\Afalse\Z/i]
|
9
11
|
node[:type] = Boolean
|
10
12
|
node[:value] = !!node[:value][0][/t/i]
|
11
13
|
return
|
12
14
|
end
|
13
|
-
return unless
|
15
|
+
return unless field.is_a?(Hash) && field[:allow_existence_boolean]
|
14
16
|
return unless node[:type] == :str && node[:value][/\Atrue\Z|\Afalse\Z/i]
|
15
17
|
node[:type] = :existence
|
16
18
|
node[:value] = !!node[:value][0][/t/i]
|
17
19
|
end
|
18
20
|
|
19
|
-
def cast_time(node)
|
21
|
+
def cast_time!(node)
|
20
22
|
search_node = node[:value][1]
|
21
23
|
search_node[:type] = Time
|
22
24
|
str = search_node[:value]
|
@@ -33,7 +35,7 @@ module CommandSearch
|
|
33
35
|
return
|
34
36
|
end
|
35
37
|
end
|
36
|
-
return unless node[:
|
38
|
+
return unless node[:type] == :compare
|
37
39
|
op = node[:nest_op]
|
38
40
|
if op == '<' || op == '>='
|
39
41
|
search_node[:value] = search_node[:value].first
|
@@ -43,79 +45,94 @@ module CommandSearch
|
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
46
|
-
def cast_regex(node)
|
48
|
+
def cast_regex!(node)
|
47
49
|
type = node[:type]
|
48
|
-
return unless type == :str || type == :quoted_str || type == :number
|
49
50
|
raw = node[:value]
|
51
|
+
return unless raw.is_a?(String)
|
52
|
+
return if node[:value] == ''
|
50
53
|
str = Regexp.escape(raw)
|
51
|
-
return node[:value] = /#{str}/i unless type == :
|
52
|
-
return node[:value] =
|
53
|
-
return node[:value] = /\b#{str}\b/ unless raw[/(^\W)|(\W$)/]
|
54
|
+
return node[:value] = /#{str}/i unless type == :quote
|
55
|
+
return node[:value] = /\b#{str}\b/ unless raw[/(\A\W)|(\W\Z)/]
|
54
56
|
border_a = '(^|\s|[^:+\w])'
|
55
57
|
border_b = '($|\s|[^:+\w])'
|
56
58
|
node[:value] = Regexp.new(border_a + str + border_b)
|
57
59
|
end
|
58
60
|
|
59
|
-
def
|
61
|
+
def cast_numeric!(node)
|
62
|
+
return unless node[:type] == :number
|
63
|
+
node[:value] = node[:value].to_f
|
64
|
+
end
|
65
|
+
|
66
|
+
def clean_comparison!(node, fields)
|
60
67
|
val = node[:value]
|
61
|
-
return
|
62
|
-
|
68
|
+
return unless fields[val[1][:value].to_sym]
|
69
|
+
if fields[val[0][:value].to_sym]
|
70
|
+
node[:compare_across_fields] = true
|
71
|
+
return
|
72
|
+
end
|
63
73
|
flip_ops = { '<' => '>', '>' => '<', '<=' => '>=', '>=' => '<=' }
|
64
74
|
node[:nest_op] = flip_ops[node[:nest_op]]
|
65
75
|
node[:value].reverse!
|
66
76
|
end
|
67
77
|
|
68
|
-
def dealias_key(key,
|
69
|
-
key =
|
70
|
-
key
|
78
|
+
def dealias_key(key, fields)
|
79
|
+
key = fields[key.to_sym] while fields[key.to_sym].is_a?(Symbol)
|
80
|
+
key
|
81
|
+
end
|
82
|
+
|
83
|
+
def split_general_fields(node, fields)
|
84
|
+
general_fields = fields.select { |k, v| v.is_a?(Hash) && v[:general_search] }.keys
|
85
|
+
general_fields = ['__CommandSearch_dummy_key__'] if general_fields.empty?
|
86
|
+
new_val = general_fields.map! do |field|
|
87
|
+
{
|
88
|
+
type: :colon,
|
89
|
+
value: [
|
90
|
+
{ value: field.to_s },
|
91
|
+
{ value: node[:value], type: node[:type] }
|
92
|
+
]
|
93
|
+
}
|
94
|
+
end
|
95
|
+
return new_val.first if new_val.count < 2
|
96
|
+
{ type: :or, value: new_val }
|
97
|
+
end
|
98
|
+
|
99
|
+
def type_cast!(node, fields)
|
100
|
+
(key_node, search_node) = node[:value]
|
101
|
+
key = key_node[:value]
|
102
|
+
field = fields[key.to_sym] || fields[key.to_s]
|
103
|
+
return unless field
|
104
|
+
type = field.is_a?(Class) ? field : field[:type]
|
105
|
+
cast_bool!(field, search_node)
|
106
|
+
return cast_time!(node) if [Time, Date, DateTime].include?(type)
|
107
|
+
return cast_numeric!(search_node) if [Integer, Numeric].include?(type)
|
108
|
+
cast_regex!(search_node)
|
71
109
|
end
|
72
110
|
|
73
|
-
def
|
111
|
+
def normalize!(ast, fields)
|
74
112
|
ast.map! do |node|
|
75
|
-
|
76
|
-
|
77
|
-
node[:number_value] = node[:value] if node[:type] == :number
|
78
|
-
cast_regex(node)
|
113
|
+
if node[:type] == :and || node[:type] == :or || node[:type] == :not
|
114
|
+
normalize!(node[:value], fields)
|
79
115
|
next node
|
80
116
|
end
|
81
|
-
|
82
|
-
|
83
|
-
|
117
|
+
if node[:type] == :colon || node[:type] == :compare
|
118
|
+
clean_comparison!(node, fields) if node[:type] == :compare
|
119
|
+
key = dealias_key(node[:value][0][:value], fields)
|
120
|
+
node[:value][0][:value] = key.to_s
|
121
|
+
unless fields[key.to_sym] || fields[key.to_s]
|
122
|
+
str_values = "#{key}#{node[:nest_op]}#{node[:value][1][:value]}"
|
123
|
+
node = { type: :str, value: str_values }
|
124
|
+
end
|
84
125
|
end
|
85
|
-
|
86
|
-
|
87
|
-
new_key = dealias_key(key_node[:value], cmd_fields)
|
88
|
-
type = cmd_fields[new_key.to_sym]
|
89
|
-
node[:value][0][:value] = new_key
|
90
|
-
if type
|
91
|
-
cast_bool(type, search_node)
|
92
|
-
type = (type - [:allow_existence_boolean]).first if type.is_a?(Array)
|
93
|
-
cast_time(node) if [Time, Date, DateTime].include?(type)
|
94
|
-
cast_regex(search_node) if type == String
|
95
|
-
next node
|
126
|
+
if node[:type] == :str || node[:type] == :quote || node[:type] == :number
|
127
|
+
node = split_general_fields(node, fields)
|
96
128
|
end
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def normalize!(ast, cmd_fields)
|
105
|
-
dealias!(ast, cmd_fields)
|
106
|
-
clean = {}
|
107
|
-
cmd_fields.each do |k, v|
|
108
|
-
next if v.is_a?(Symbol)
|
109
|
-
if v.is_a?(Array)
|
110
|
-
clean[k] = (v - [:allow_existence_boolean]).first
|
111
|
-
next
|
129
|
+
if node[:type] == :or
|
130
|
+
node[:value].each { |x| type_cast!(x, fields) }
|
131
|
+
else
|
132
|
+
type_cast!(node, fields)
|
112
133
|
end
|
113
|
-
|
114
|
-
v = Time if v == Date
|
115
|
-
v = Time if v == DateTime
|
116
|
-
next clean[k] = v
|
134
|
+
node
|
117
135
|
end
|
118
|
-
clean
|
119
136
|
end
|
120
137
|
end
|
121
138
|
end
|
@@ -2,26 +2,25 @@ module CommandSearch
|
|
2
2
|
module Optimizer
|
3
3
|
module_function
|
4
4
|
|
5
|
-
def denest!(ast, parent_type = :
|
5
|
+
def denest!(ast, parent_type = :and)
|
6
6
|
ast.map! do |node|
|
7
|
-
|
8
|
-
type
|
9
|
-
next node unless type
|
10
|
-
next node unless type == :paren || type == :pipe || type == :minus
|
7
|
+
type = node[:type]
|
8
|
+
next node unless type == :and || type == :or || type == :not
|
11
9
|
denest!(node[:value], type)
|
12
10
|
next [] if node[:value] == []
|
13
|
-
if type == :
|
11
|
+
if type == :not
|
14
12
|
only_child = node[:value].count == 1
|
15
13
|
child = node[:value].first
|
16
|
-
next child[:value] if only_child && child[:
|
14
|
+
next child[:value] if only_child && child[:type] == :not
|
17
15
|
next node
|
18
16
|
end
|
19
17
|
next node[:value] if node[:value].count == 1
|
20
18
|
next node[:value] if type == parent_type
|
21
|
-
next node[:value] if type == :
|
22
|
-
next node if type == :
|
23
|
-
denest!(node[:value], type) # type == :
|
19
|
+
next node[:value] if type == :and && parent_type == :not
|
20
|
+
next node if type == :and
|
21
|
+
denest!(node[:value], type) # type == :or, parent_type == :and
|
24
22
|
node[:value].uniq!
|
23
|
+
next node[:value] if node[:value].count == 1
|
25
24
|
node
|
26
25
|
end
|
27
26
|
ast.flatten!
|
@@ -9,39 +9,71 @@ module CommandSearch
|
|
9
9
|
next i += 1 unless input[i][:type] == :paren
|
10
10
|
if input[i][:value] == '('
|
11
11
|
opening_idxs.push(i)
|
12
|
-
|
13
|
-
|
14
|
-
input[opening..i] = { type: :nest, nest_type: :paren, value: val }
|
15
|
-
i -= (val.length + 1)
|
12
|
+
input.delete_at(i)
|
13
|
+
next
|
16
14
|
end
|
17
|
-
i
|
15
|
+
input.delete_at(i)
|
16
|
+
opening = opening_idxs.pop()
|
17
|
+
next unless opening
|
18
|
+
val = input.slice(opening, i - opening)
|
19
|
+
if val.count > 1
|
20
|
+
input[opening..(i - 1)] = { type: :and, value: val }
|
21
|
+
i -= val.length
|
22
|
+
next
|
23
|
+
elsif val.count == 1
|
24
|
+
input[opening] = val.first
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def cluster_cmds!(input)
|
30
|
+
i = 1
|
31
|
+
while i < input.length - 1
|
32
|
+
type = input[i][:type]
|
33
|
+
next i += 1 unless type == :colon || type == :compare
|
34
|
+
input[(i - 1)..(i + 1)] = {
|
35
|
+
type: type,
|
36
|
+
nest_op: input[i][:value],
|
37
|
+
value: [input[i - 1], input[i + 1]]
|
38
|
+
}
|
18
39
|
end
|
19
40
|
end
|
20
41
|
|
21
|
-
def
|
22
|
-
|
23
|
-
i
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
front_offset = 1 if binary && i > 0
|
31
|
-
input[(i - front_offset)..(i + 1)] = {
|
32
|
-
type: :nest,
|
33
|
-
nest_type: type,
|
34
|
-
nest_op: input[i][:value],
|
35
|
-
value: val
|
36
|
-
}
|
37
|
-
i -= 1 if binary
|
42
|
+
def cluster_or!(input)
|
43
|
+
i = 0
|
44
|
+
while i < input.length
|
45
|
+
type = input[i][:type]
|
46
|
+
cluster_or!(input[i][:value]) if type == :and || type == :not
|
47
|
+
next i += 1 unless type == :pipe
|
48
|
+
if i == 0 || i == input.length - 1
|
49
|
+
input.delete_at(i)
|
50
|
+
next
|
38
51
|
end
|
39
|
-
|
52
|
+
val = [input[i - 1], input[i + 1]]
|
53
|
+
cluster_or!(val)
|
54
|
+
input[(i - 1)..(i + 1)] = { type: :or, value: val }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def cluster_not!(input)
|
59
|
+
i = input.length
|
60
|
+
while i > 0
|
40
61
|
i -= 1
|
62
|
+
type = input[i][:type]
|
63
|
+
cluster_not!(input[i][:value]) if type == :and
|
64
|
+
next unless type == :minus
|
65
|
+
if i == input.length - 1
|
66
|
+
input.delete_at(i)
|
67
|
+
next
|
68
|
+
end
|
69
|
+
input[i..(i + 1)] = {
|
70
|
+
type: :not,
|
71
|
+
value: [input[i + 1]]
|
72
|
+
}
|
41
73
|
end
|
42
74
|
end
|
43
75
|
|
44
|
-
def unchain!(
|
76
|
+
def unchain!(input, types)
|
45
77
|
i = 0
|
46
78
|
while i < input.length - 2
|
47
79
|
left = input[i][:type]
|
@@ -64,41 +96,32 @@ module CommandSearch
|
|
64
96
|
end
|
65
97
|
|
66
98
|
def clean_ununusable!(input)
|
67
|
-
i =
|
99
|
+
i = 1
|
68
100
|
while i < input.length
|
69
101
|
next i += 1 unless input[i][:type] == :minus
|
70
|
-
next i += 1 unless
|
102
|
+
next i += 1 unless [:compare, :colon].include?(input[i - 1][:type])
|
71
103
|
input[i..i + 1] = merge_strs(input[i..i + 1], [0, 1])
|
72
104
|
end
|
73
|
-
|
74
105
|
i = 0
|
75
106
|
while i < input.length
|
76
107
|
next i += 1 if ![:compare, :colon].include?(input[i][:type])
|
77
108
|
next i += 1 if i > 0 &&
|
78
109
|
(i < input.count - 1) &&
|
79
|
-
[:str, :number, :
|
80
|
-
[:str, :number, :
|
110
|
+
[:str, :number, :quote].include?(input[i - 1][:type]) &&
|
111
|
+
[:str, :number, :quote].include?(input[i + 1][:type])
|
81
112
|
|
82
113
|
input[i..i + 1] = merge_strs(input[i..i + 1], [0, 1])
|
83
114
|
input[i - 1..i] = merge_strs(input[i - 1..i], [1, 0]) if i > 0
|
84
115
|
end
|
85
|
-
|
86
|
-
input[-1][:type] = :str if input[-1] && input[-1][:type] == :minus
|
87
|
-
end
|
88
|
-
|
89
|
-
def clean_ununused!(input)
|
90
|
-
input.reject! { |x| x[:type] == :paren && x[:value].is_a?(String) }
|
91
116
|
end
|
92
117
|
|
93
118
|
def parse!(input)
|
94
119
|
clean_ununusable!(input)
|
95
|
-
unchain!([:colon, :compare]
|
120
|
+
unchain!(input, [:colon, :compare])
|
121
|
+
cluster_cmds!(input)
|
96
122
|
group_parens!(input)
|
97
|
-
|
98
|
-
|
99
|
-
cluster!(:minus, input, :prefix)
|
100
|
-
cluster!(:pipe, input)
|
101
|
-
clean_ununused!(input)
|
123
|
+
cluster_not!(input)
|
124
|
+
cluster_or!(input)
|
102
125
|
input
|
103
126
|
end
|
104
127
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: command_search
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- zumbalogy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: chronic
|
@@ -57,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
57
57
|
- !ruby/object:Gem::Version
|
58
58
|
version: '0'
|
59
59
|
requirements: []
|
60
|
-
rubygems_version: 3.0.
|
60
|
+
rubygems_version: 3.0.6
|
61
61
|
signing_key:
|
62
62
|
specification_version: 4
|
63
63
|
summary: Let users query collections with ease.
|