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 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.