command_search 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ab34234037969a83820730a8a209e1fe0ca29ed9a2b34f1f1c3f0a72b570b5c
4
- data.tar.gz: f3c4af7b842ae0a59057a2d700bb9c39b739c96fe18248377deecdc38c70aede
3
+ metadata.gz: 93226578c66261e1923eeb6f56647b3eed274b3fe0dad3e8f016780c34e46831
4
+ data.tar.gz: 5d73e80d4b7f8fbca8febe5dc548332d48cd25f46b7611fae6c075268fdda5b0
5
5
  SHA512:
6
- metadata.gz: d1e7ffc036d37d54217a78184a2c18705ad85529cad90e75851c6512d10a0214d22d083da7cf504eed6257bc28576c885a56dd383bc4e37cb39919ac1e8e665d
7
- data.tar.gz: d4ce49e9747f21e06ced4884b4a72f03c6c335ab9d20e91f7e6ebd985a3a9cbca11dc0cacf1422d0ced8f4d322b9b48f95327099769030fa820eade6da4f5d7c
6
+ metadata.gz: a25f5ac8e3b4ba433df88abc38551162532ba7e04e47744e44a0ba7dd107a95471490d0506e98746f1344a259f94a8b2ff229f3468a5078ad4ef97158901b948
7
+ data.tar.gz: b14a8926f623e7b1ca05e51e5c0fbf1a754012a4e84284166e1fbc29b8894bf47b2fd58841d397575c1859dab46f722a21e0317afff482ec2da40302faddcf19
@@ -18,7 +18,7 @@ module CommandSearch
18
18
 
19
19
  aliased_query = Aliaser.alias(query, aliases)
20
20
  tokens = Lexer.lex(aliased_query)
21
- parsed = Parser.parse(tokens)
21
+ parsed = Parser.parse!(tokens)
22
22
  dealiased = CommandDealiaser.dealias(parsed, command_fields)
23
23
  cleaned = CommandDealiaser.decompose_unaliasable(dealiased, command_fields)
24
24
  opted = Optimizer.optimize(cleaned)
@@ -8,32 +8,34 @@ module CommandSearch
8
8
  Regexp.new(head_border + Regexp.escape(str) + tail_border, 'i')
9
9
  end
10
10
 
11
- def opens_quote?(str)
12
- while str[/".*"/] || str[/'.*'/]
13
- mark = str[/["']/]
14
- str.sub(/#{mark}.*#{mark}/, '')
15
- end
16
- str[/"/] || str[/\B'/]
11
+ def quotes?(head, tail)
12
+ return true if head.count("'").odd? && tail.count("'").odd?
13
+ return true if head.count('"').odd? && tail.count('"').odd?
14
+ false
17
15
  end
18
16
 
19
17
  def alias_item(query, alias_key, alias_value)
20
18
  if alias_key.is_a?(Regexp)
21
19
  pattern = alias_key
20
+ elsif alias_key.is_a?(String)
21
+ pattern = build_regex(alias_key)
22
22
  else
23
- pattern = build_regex(alias_key.to_s)
23
+ return query
24
24
  end
25
25
  current_match = query[pattern]
26
26
  return query unless current_match
27
27
  offset = Regexp.last_match.offset(0)
28
28
  head = query[0...offset.first]
29
29
  tail = alias_item(query[offset.last..-1], alias_key, alias_value)
30
- if opens_quote?(head)
30
+ if quotes?(head, tail)
31
31
  replacement = current_match
32
32
  else
33
33
  if alias_value.is_a?(String)
34
34
  replacement = alias_value
35
35
  elsif alias_value.is_a?(Proc)
36
36
  replacement = alias_value.call(current_match).to_s
37
+ else
38
+ return query
37
39
  end
38
40
  end
39
41
  head + replacement + tail
@@ -23,9 +23,9 @@ module CommandSearch
23
23
  end
24
24
 
25
25
  def dealias(ast, aliases)
26
- ast.flat_map do |x|
26
+ ast.map! do |x|
27
27
  next x unless x[:nest_type]
28
- x[:value] = dealias(x[:value], aliases)
28
+ dealias(x[:value], aliases)
29
29
  next x unless [:colon, :compare].include?(x[:nest_type])
30
30
  x[:value] = dealias_values(x[:value], aliases)
31
31
  x
@@ -33,9 +33,9 @@ module CommandSearch
33
33
  end
34
34
 
35
35
  def decompose_unaliasable(ast, aliases)
36
- ast.flat_map do |x|
36
+ ast.map! do |x|
37
37
  next x unless x[:nest_type]
38
- x[:value] = decompose_unaliasable(x[:value], aliases)
38
+ decompose_unaliasable(x[:value], aliases)
39
39
  next x unless [:colon, :compare].include?(x[:nest_type])
40
40
  unnest_unaliased(x, aliases)
41
41
  end
@@ -8,29 +8,29 @@ module CommandSearch
8
8
  while i < input.length
9
9
  match = nil
10
10
  case input[i..-1]
11
- when /^\s+/
11
+ when /\A\s+/
12
12
  type = :space
13
- when /^"(.*?)"/
13
+ when /\A"(.*?)"/
14
14
  match = Regexp.last_match[1]
15
15
  type = :quoted_str
16
- when /^'(.*?)'/
16
+ when /\A'(.*?)'/
17
17
  match = Regexp.last_match[1]
18
18
  type = :quoted_str
19
- when /^\-?\d+(\.\d+)?(?=$|[\s"':|<>()])/
19
+ when /\A\-?\d+(\.\d+)?(?=$|[\s"':|<>()])/
20
20
  type = :number
21
- when /^-/
21
+ when /\A-/
22
22
  type = :minus
23
- when /^[^\s:"|<>()]+/
23
+ when /\A[^\s:"|<>()]+/
24
24
  type = :str
25
- when /^\|+/
25
+ when /\A\|+/
26
26
  type = :pipe
27
- when /^[()]/
27
+ when /\A[()]/
28
28
  type = :paren
29
- when /^:/
29
+ when /\A:/
30
30
  type = :colon
31
- when /^[<>]=?/
31
+ when /\A[<>]=?/
32
32
  type = :compare
33
- when /^./
33
+ when /\A./
34
34
  type = :str
35
35
  end
36
36
  match = match || Regexp.last_match[0]
@@ -10,7 +10,6 @@ module CommandSearch
10
10
  raw_cmd_type = [command_types[cmd]].flatten
11
11
  allow_existence_boolean = raw_cmd_type.include?(:allow_existence_boolean)
12
12
  cmd_type = (raw_cmd_type - [:allow_existence_boolean]).first
13
- return unless cmd_type
14
13
  if cmd_type == Boolean
15
14
  if cmd_search[/true/i]
16
15
  item[cmd]
@@ -25,8 +24,6 @@ module CommandSearch
25
24
  end
26
25
  elsif !item.key?(cmd)
27
26
  return false
28
- elsif val[1][:type] == :str
29
- item[cmd][/#{Regexp.escape(cmd_search)}/i]
30
27
  elsif val[1][:type] == :quoted_str
31
28
  regex = /\b#{Regexp.escape(cmd_search)}\b/
32
29
  if cmd_search[/(^\W)|(\W$)/]
@@ -5,7 +5,7 @@ module CommandSearch
5
5
  module_function
6
6
 
7
7
  def build_search(ast_node, fields)
8
- str = ast_node[:value]
8
+ str = ast_node[:value] || ''
9
9
  fields = [fields] unless fields.is_a?(Array)
10
10
  if ast_node[:type] == :quoted_str
11
11
  regex = /\b#{Regexp.escape(str)}\b/
@@ -15,9 +15,7 @@ module CommandSearch
15
15
  regex = Regexp.new(head_border + Regexp.escape(str) + tail_border)
16
16
  end
17
17
  else
18
- # TODO: This OR check is only needed due to mutable objects between subclasses of command_search,
19
- # and is only needed for outside use or benchmarking.
20
- regex = /#{Regexp.escape(str || '')}/i
18
+ regex = /#{Regexp.escape(str)}/i
21
19
  end
22
20
  if ast_node[:negate]
23
21
  forms = fields.map { |f| { f => { '$not' => regex } } }
@@ -33,17 +31,16 @@ module CommandSearch
33
31
  end
34
32
 
35
33
  def is_bool_str?(str)
36
- return true if str[/^true$|^false$/i]
34
+ return true if str[/\Atrue\Z|\Afalse\Z/i]
37
35
  false
38
36
  end
39
37
 
40
38
  def make_boolean(str)
41
- return true if str[/^true$/i]
39
+ return true if str[/\Atrue\Z/i]
42
40
  false
43
41
  end
44
42
 
45
43
  def build_command(ast_node, command_types)
46
- # aliasing will is done before ast gets to mongoer.rb
47
44
  (field_node, search_node) = ast_node[:value]
48
45
  key = field_node[:value]
49
46
  raw_type = command_types[key.to_sym]
@@ -60,8 +57,7 @@ module CommandSearch
60
57
  type = raw_type
61
58
  end
62
59
 
63
- if defined?(Boolean) && type == Boolean
64
- # val = make_boolean(raw_val)
60
+ if type == Boolean
65
61
  bool = make_boolean(raw_val)
66
62
  bool = !bool if field_node[:negate]
67
63
  val = [
@@ -100,7 +96,7 @@ module CommandSearch
100
96
  elsif [Numeric, Integer].include?(type)
101
97
  if raw_val == raw_val.to_i.to_s
102
98
  val = raw_val.to_i
103
- elsif raw_val.to_f != 0 || raw_val[/^[\.0]*0$/]
99
+ elsif raw_val.to_f != 0 || raw_val[/\A[\.0]*0\Z/]
104
100
  val = raw_val.to_f
105
101
  else
106
102
  val = raw_val
@@ -205,40 +201,41 @@ module CommandSearch
205
201
  { key => { mongo_op => val } }
206
202
  end
207
203
 
208
- def build_searches(ast, fields, command_types)
209
- ast.flat_map do |x|
204
+ def build_searches!(ast, fields, command_types)
205
+ ast.map! do |x|
210
206
  type = x[:nest_type]
211
207
  if type == :colon
212
208
  build_command(x, command_types)
213
209
  elsif type == :compare
214
210
  build_compare(x, command_types)
215
211
  elsif [:paren, :pipe, :minus].include?(type)
216
- x[:value] = build_searches(x[:value], fields, command_types)
212
+ build_searches!(x[:value], fields, command_types)
217
213
  x
218
214
  else
219
215
  build_search(x, fields)
220
216
  end
221
217
  end
218
+ ast.flatten!
222
219
  end
223
220
 
224
- def build_tree(ast)
225
- ast.flat_map do |x|
221
+ def build_tree!(ast)
222
+ mongo_types = { paren: '$and', pipe: '$or', minus: '$not' }
223
+ ast.each do |x|
226
224
  next x unless x[:nest_type]
227
- mongo_types = { paren: '$and', pipe: '$or', minus: '$not' }
225
+ build_tree!(x[:value])
228
226
  key = mongo_types[x[:nest_type]]
229
- { key => build_tree(x[:value]) }
227
+ x[key] = x[:value]
228
+ x.delete(:nest_type)
229
+ x.delete(:nest_op)
230
+ x.delete(:value)
231
+ x.delete(:type)
230
232
  end
231
233
  end
232
234
 
233
- def collapse_ors(ast)
234
- ast.flat_map do |x|
235
- ['$and', '$or', '$not'].map do |key|
236
- next unless x[key]
237
- x[key] = collapse_ors(x[key])
238
- end
239
- next x unless x['$or']
240
- val = x['$or'].flat_map { |kid| kid['$or'] || kid }
241
- { '$or' => val }
235
+ def collapse_ors!(ast)
236
+ ast.each do |x|
237
+ next unless x['$or']
238
+ x['$or'].map! { |kid| kid['$or'] || kid }.flatten!
242
239
  end
243
240
  end
244
241
 
@@ -257,12 +254,11 @@ module CommandSearch
257
254
  end
258
255
 
259
256
  def build_query(ast, fields, command_types = {})
260
- # Numbers are searched as strings unless part of a compare/command
261
257
  out = ast
262
258
  out = decompose_nots(out)
263
- out = build_searches(out, fields, command_types)
264
- out = build_tree(out)
265
- out = collapse_ors(out)
259
+ build_searches!(out, fields, command_types)
260
+ build_tree!(out)
261
+ collapse_ors!(out)
266
262
  out = {} if out == []
267
263
  out = out.first if out.count == 1
268
264
  out = { '$and' => out } if out.count > 1
@@ -2,18 +2,16 @@ module CommandSearch
2
2
  module Optimizer
3
3
  module_function
4
4
 
5
- def ands_and_ors(ast)
6
- ast.uniq.map do |node|
7
- next node unless node[:nest_type]
8
- next node if node[:nest_type] == :compare
9
- node[:value] = ands_and_ors(node[:value])
5
+ def ands_and_ors!(ast)
6
+ ast.map! do |node|
7
+ next node unless node[:nest_type] == :paren || node[:nest_type] == :pipe
8
+ ands_and_ors!(node[:value])
9
+ next node[:value].first if node[:value].length < 2
10
10
  node[:value] = node[:value].flat_map do |kid|
11
11
  next kid[:value] if kid[:nest_type] == :pipe
12
12
  kid
13
13
  end
14
- if node[:nest_type] == :pipe && node[:value].length == 1
15
- next node[:value].first
16
- end
14
+ node[:value].uniq!
17
15
  node
18
16
  end
19
17
  end
@@ -23,7 +21,6 @@ module CommandSearch
23
21
  next node unless node[:nest_type]
24
22
  node[:value] = negate_negate(node[:value])
25
23
  next [] if node[:value] == []
26
- next node if node[:value].count > 1
27
24
  type = node[:nest_type]
28
25
  child_type = node[:value].first[:nest_type]
29
26
  next node unless type == :minus && child_type == :minus
@@ -34,51 +31,30 @@ module CommandSearch
34
31
  def denest_parens(ast, parent_type = :root)
35
32
  ast.flat_map do |node|
36
33
  next node unless node[:nest_type]
37
-
38
34
  node[:value] = denest_parens(node[:value], node[:nest_type])
39
-
40
- valid_self = node[:nest_type] == :paren
41
- valid_parent = parent_type != :pipe
42
- valid_child = node[:value].count < 2
43
-
44
- next node[:value] if valid_self && valid_parent
45
- next node[:value] if valid_self && valid_child
35
+ # valid_self && (valid_parent || valid_child)
36
+ if node[:nest_type] == :paren && (parent_type != :pipe || node[:value].count < 2)
37
+ next node[:value]
38
+ end
46
39
  node
47
40
  end
48
41
  end
49
42
 
50
- def remove_empty_strings(ast)
51
- out = ast.flat_map do |node|
52
- next if node[:type] == :quoted_str && node[:value] == ''
53
- next node unless node[:nest_type]
54
- node[:value] = remove_empty_strings(node[:value])
55
- node
43
+ def remove_empty_strings!(ast)
44
+ ast.reject! do |node|
45
+ remove_empty_strings!(node[:value]) if node[:nest_type]
46
+ node[:type] == :quoted_str && node[:value] == ''
56
47
  end
57
- out.compact
58
48
  end
59
49
 
60
- def optimization_pass(ast)
61
- # '(a b)|(c d)' is the only current
62
- # situation where parens are needed.
63
- # 'a|(b|(c|d))' can be flattened by
64
- # repeated application of "ands_and_or"
65
- # and "denest_parens".
50
+ def optimize(ast)
66
51
  out = ast
67
52
  out = denest_parens(out)
53
+ remove_empty_strings!(out)
68
54
  out = negate_negate(out)
69
- out = ands_and_ors(out)
70
- out = remove_empty_strings(out)
55
+ ands_and_ors!(out)
56
+ out.uniq!
71
57
  out
72
58
  end
73
-
74
- def optimize(ast)
75
- out_a = optimization_pass(ast)
76
- out_b = optimization_pass(out_a)
77
- until out_a == out_b
78
- out_a = out_b
79
- out_b = optimization_pass(out_b)
80
- end
81
- out_b
82
- end
83
59
  end
84
60
  end
@@ -3,7 +3,6 @@ module CommandSearch
3
3
  module_function
4
4
 
5
5
  def parens_rindex(input)
6
- val_list = input.map { |x| x[:value] }
7
6
  open_i = input.rindex { |x| x[:value] == '(' && x[:type] == :paren}
8
7
  return unless open_i
9
8
  close_offset = input.drop(open_i).index { |x| x[:value] == ')' && x[:type] == :paren}
@@ -11,48 +10,41 @@ module CommandSearch
11
10
  [open_i, close_offset + open_i]
12
11
  end
13
12
 
14
- def group_parens(input)
15
- out = input
16
- while parens_rindex(out)
17
- (a, b) = parens_rindex(out)
18
- val = out[(a + 1)..(b - 1)]
19
- out[a..b] = { type: :nest, nest_type: :paren, value: val }
13
+ def group_parens!(input)
14
+ while parens_rindex(input)
15
+ (a, b) = parens_rindex(input)
16
+ val = input[(a + 1)..(b - 1)]
17
+ input[a..b] = { type: :nest, nest_type: :paren, value: val }
20
18
  end
21
- out
22
19
  end
23
20
 
24
21
  def cluster!(type, input, cluster_type = :binary)
25
22
  binary = (cluster_type == :binary)
26
- out = input
27
- out = out[:value] while out.is_a?(Hash)
28
- out.compact!
23
+ input.compact!
29
24
  # rindex (vs index) important for nested prefixes
30
- while (i = out.rindex { |x| x[:type] == type })
31
- val = [out[i + 1]]
32
- val.unshift(out[i - 1]) if binary && i > 0
25
+ while (i = input.rindex { |x| x[:type] == type })
26
+ val = [input[i + 1]]
27
+ val.unshift(input[i - 1]) if binary && i > 0
33
28
  front_offset = 0
34
29
  front_offset = 1 if binary && i > 0
35
- out[(i - front_offset)..(i + 1)] = {
30
+ input[(i - front_offset)..(i + 1)] = {
36
31
  type: :nest,
37
32
  nest_type: type,
38
- nest_op: out[i][:value],
33
+ nest_op: input[i][:value],
39
34
  value: val
40
35
  }
41
36
  end
42
- out.map! do |x|
43
- next x unless x[:type] == :nest
44
- x[:value] = cluster!(type, x[:value], cluster_type)
45
- x
37
+ input.each do |x|
38
+ cluster!(type, x[:value], cluster_type) if x[:type] == :nest
46
39
  end
47
40
  end
48
41
 
49
42
  def unchain!(types, input)
50
43
  i = 0
51
44
  while i < input.length - 2
52
- front = input[i][:type]
53
- mid = input[i + 1][:type]
54
- back = input[i + 2][:type]
55
- if types.include?(front) && !types.include?(mid) && types.include?(back)
45
+ left = input[i][:type]
46
+ right = input[i + 2][:type]
47
+ if types.include?(left) && types.include?(right)
56
48
  input.insert(i + 1, input[i + 1])
57
49
  end
58
50
  i += 1
@@ -60,7 +52,6 @@ module CommandSearch
60
52
  end
61
53
 
62
54
  def merge_strs(input, (x, y))
63
- return input if input.empty?
64
55
  if input[y] && input[y][:type] == :str
65
56
  values = input.map { |x| x[:value] }
66
57
  { type: :str, value: values.join() }
@@ -71,8 +62,6 @@ module CommandSearch
71
62
  end
72
63
 
73
64
  def clean_ununusable!(input)
74
- return unless input.any?
75
-
76
65
  i = 0
77
66
  while i < input.length
78
67
  next i += 1 unless input[i][:type] == :minus
@@ -97,31 +86,19 @@ module CommandSearch
97
86
  end
98
87
 
99
88
  def clean_ununused!(input)
100
- input.map! do |x|
101
- next if x[:type] == :paren && x[:value].is_a?(String)
102
- next if x[:nest_type] == :colon && x[:value].empty?
103
- if x[:nest_type] == :compare && x[:value].length < 2
104
- x = clean_ununused!(x[:value]).first
105
- end
106
- next x unless x && x[:type] == :nest
107
- x[:value] = clean_ununused!(x[:value])
108
- x
109
- end
110
- input.compact!
111
- input
89
+ input.reject! { |x| x[:type] == :paren && x[:value].is_a?(String) }
112
90
  end
113
91
 
114
- def parse(input)
115
- out = input
116
- clean_ununusable!(out)
117
- unchain!([:colon, :compare], out)
118
- out = group_parens(out)
119
- cluster!(:colon, out)
120
- cluster!(:compare, out)
121
- cluster!(:minus, out, :prefix)
122
- cluster!(:pipe, out)
123
- clean_ununused!(out)
124
- out
92
+ def parse!(input)
93
+ clean_ununusable!(input)
94
+ unchain!([:colon, :compare], input)
95
+ group_parens!(input)
96
+ cluster!(:colon, input)
97
+ cluster!(:compare, input)
98
+ cluster!(:minus, input, :prefix)
99
+ cluster!(:pipe, input)
100
+ clean_ununused!(input)
101
+ input
125
102
  end
126
103
  end
127
104
  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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - zumbalogy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-08 00:00:00.000000000 Z
11
+ date: 2018-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chronic