command_search 0.9.0 → 0.10.0
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 +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.
|