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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4560d75eabd25ff56808df06ab6b72d97813fbd75909739d15bfa2e13055557
4
- data.tar.gz: 8c48236dd477f98450076128b7737121fabb16398c123e3c567f049bd68cadbf
3
+ metadata.gz: fa471a870d21295459efa58aa18e3777752841dc0cfec3033a6423996ed2d98c
4
+ data.tar.gz: 37a0b72e6cb764c90fd14eb44b0a1c096d4e6c6e2756a4715db3e02471aa6889
5
5
  SHA512:
6
- metadata.gz: b0abdf98f95abba34b3a27d8f805f5183b1089e7291dad6b7f3a67e6cdb8a2413dad6813cf06e7118b733b44132428d79c89545739184022f3fc6d6222e60f8d
7
- data.tar.gz: 836f1b245c14396850259c7e326e4ed1451bfcda6df07a594162ed4bbd1725abdf4a4a46c90c75c23eeb3b3a0ed242e7caa02cded714437acc1cc9ce65a63781
6
+ metadata.gz: fe02049b5e6eec1b62ffa6d030a747312b661f4db05befb66f5e3585bf0b5b679f65c10c7ba3348a19de97ecdb0d4f9f19b2e5466b75bd4604658b66021c6fe0
7
+ data.tar.gz: a5e0fa90644d5c7dd3346e1b9c7e43ef04c6e652f9f293ac59b468a2667bb388be8614854680612b62cda2812179fd800ee45a9e4229c7d4774760facd2b0b79
@@ -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
- command_fields = Normalizer.normalize!(ast, command_fields)
24
+ Normalizer.normalize!(ast, fields)
25
25
 
26
26
  if source.respond_to?(:mongo_client) && source.queryable
27
- fields = [:__CommandSearch_dummy_key__] if fields.empty?
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, fields, command_fields) }
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
- head_border = '(?<=^|[^:\w])'
7
- tail_border = '(?=$|\W)'
8
- Regexp.new(head_border + Regexp.escape(str) + tail_border, 'i')
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, alias_key, alias_value)
20
- return query unless alias_value.is_a?(String) || alias_value.is_a?(Proc)
21
- if alias_key.is_a?(Regexp)
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!(pattern) do |match|
23
+ query.gsub!(key) do |match|
29
24
  next match if quotes?(query, Regexp.last_match.offset(0))
30
- if alias_value.is_a?(String)
31
- alias_value
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
- cmd_search = val[1][:value]
8
- item_val = item[cmd.to_sym] || item[cmd]
9
- val_type = val[1][:type]
10
- val_type = Boolean if val_type == :existence && cmd_search == true
11
- if val_type == Boolean
12
- !!item_val == cmd_search
13
- elsif val_type == :existence
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 val_type == Time
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
- cmd_search.first <= item_time && item_time < cmd_search.last
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.to_s[/#{Regexp.escape(cmd_search)}/i]
25
+ item_val == search
26
26
  end
27
27
  end
28
28
 
29
- def compare_check(item, node, cmd_types)
30
- cmd = node[:value].first
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
- search = node[:value].last
35
- val = search[:value]
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 if item_val
38
- elsif search[:type] == :str && cmd_types[val.to_sym]
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
- args = [item_val, val]
42
- return unless args.all?
39
+ return unless val
43
40
  fn = node[:nest_op].to_sym.to_proc
44
- fn.call(*args.map(&:to_f))
41
+ fn.call(item_val.to_f, val.to_f)
45
42
  end
46
43
 
47
- def check(item, ast, fields, cmd_types)
44
+ def check(item, ast)
48
45
  ast.all? do |node|
49
46
  val = node[:value]
50
- case node[:nest_type]
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, cmd_types)
60
- when :minus
61
- !val.all? { |v| check(item, [v], fields, cmd_types) }
62
- when :pipe
63
- val.any? { |v| check(item, [v], fields, cmd_types) }
64
- when :paren
65
- val.all? { |v| check(item, [v], fields, cmd_types) }
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, cmd_fields)
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 val.class == String && cmd_fields[val.to_sym]
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, fields, cmd_fields)
59
- mongo_types = { paren: '$and', pipe: '$or', minus: '$nor' }
44
+ def build_searches!(ast)
45
+ mongo_types = { and: '$and', or: '$or', not: '$nor' }
60
46
  ast.map! do |node|
61
- type = node[:nest_type]
47
+ type = node[:type]
62
48
  if type == :colon
63
49
  build_command(node)
64
50
  elsif type == :compare
65
- build_compare(node, cmd_fields)
51
+ build_compare(node)
66
52
  elsif key = mongo_types[type]
67
- build_searches!(node[:value], fields, cmd_fields)
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, fields, cmd_fields)
82
- build_searches!(ast, fields, cmd_fields)
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 }
@@ -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 = :quoted_str
15
+ type = :quote
16
16
  when /\A'(.*?)'/
17
17
  match = Regexp.last_match[1]
18
- type = :quoted_str
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 = match || Regexp.last_match[0]
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(type, node)
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 type.is_a?(Array) && type.include?(:allow_existence_boolean)
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[:nest_type] == :compare
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 == :quoted_str
52
- return node[:value] = '' if raw == ''
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 flip_operator!(node, cmd_fields)
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 if cmd_fields[val[0][:value].to_sym]
62
- return unless cmd_fields[val[1][:value].to_sym]
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, cmd_fields)
69
- key = cmd_fields[key.to_sym] while cmd_fields[key.to_sym].is_a?(Symbol)
70
- key.to_s
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 dealias!(ast, cmd_fields)
111
+ def normalize!(ast, fields)
74
112
  ast.map! do |node|
75
- nest = node[:nest_type]
76
- unless nest
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
- unless nest == :colon || nest == :compare
82
- dealias!(node[:value], cmd_fields)
83
- next node
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
- flip_operator!(node, cmd_fields) if nest == :compare
86
- (key_node, search_node) = node[:value]
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
- str_values = "#{new_key}#{node[:nest_op]}#{search_node[:value]}"
98
- node = { type: :str, value: str_values }
99
- cast_regex(node)
100
- node
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
- v = Numeric if v == Integer
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 = :paren)
5
+ def denest!(ast, parent_type = :and)
6
6
  ast.map! do |node|
7
- next [] if node[:type] == :quoted_str && node[:value] == '' && [:paren, :pipe, :minus].include?(parent_type)
8
- type = node[:nest_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 == :minus
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[:nest_type] == :minus
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 == :paren && parent_type == :minus
22
- next node if type == :paren
23
- denest!(node[:value], type) # type == :pipe, parent_type == :paren
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
- elsif opening = opening_idxs.pop()
13
- val = input[(opening + 1)..(i - 1)]
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 += 1
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 cluster!(type, input, cluster_type = :binary)
22
- binary = (cluster_type == :binary)
23
- i = input.length - 1
24
- while i >= 0
25
- if input[i][:type] == type
26
- val = [input[i + 1]]
27
- val.compact!
28
- val.unshift(input[i - 1]) if binary && i > 0
29
- front_offset = 0
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
- cluster!(type, input[i][:value], cluster_type) if input[i][:type] == :nest
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!(types, input)
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 = 0
99
+ i = 1
68
100
  while i < input.length
69
101
  next i += 1 unless input[i][:type] == :minus
70
- next i += 1 unless i > 0 && [:compare, :colon].include?(input[i - 1][:type])
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, :quoted_str].include?(input[i - 1][:type]) &&
80
- [:str, :number, :quoted_str].include?(input[i + 1][:type])
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], input)
120
+ unchain!(input, [:colon, :compare])
121
+ cluster_cmds!(input)
96
122
  group_parens!(input)
97
- cluster!(:colon, input)
98
- cluster!(:compare, input)
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.9.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-10-15 00:00:00.000000000 Z
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.4
60
+ rubygems_version: 3.0.6
61
61
  signing_key:
62
62
  specification_version: 4
63
63
  summary: Let users query collections with ease.