query-ruby 0.1.0 → 1.1.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: 0ac8665b3965512fc0ec7deb35364d8dcfeeaa4d7314bd79b98a8e03429063f0
4
- data.tar.gz: de6848d68312bc5c612953e12968b775a8a3b7971051b1cd80ec274765539a5c
3
+ metadata.gz: 637f0d62e9d40ea882160cc00f8e10fd09cb2ad730387c29d2170a07150ff894
4
+ data.tar.gz: f4617293cd65815243117e7d939a7b98102da0c2c4d77c7dd692e2b1618c1187
5
5
  SHA512:
6
- metadata.gz: c5db80249c3f4b02d054453acea39ae197b35768d84dba7698b0c3a9dcfd6aafc77cc7e483893309b74e55c346c4af7fb507c67cf7902f41478f275429d07d57
7
- data.tar.gz: e6d66eed9e04b42164f7a0542c1450996b511c8bcb9bf0a05e46ab5336202056f81370710a8552d8073ee79e5aaaaf02ce168d727da581f085b4613e8d812963
6
+ metadata.gz: 5c46fea9d0bad92f106400380c9b095746141b5eb312970b26a15cbb2b39010b4e5a4d6738ed6e3f3e2dfd4a4cb94ab1e2451ccba2da9270818ca1140a170c3c
7
+ data.tar.gz: 62519e0608c41d5b3348822380ca50ef3b501c5561aa0067a1f325029a210a79fa4e1eb9432ef47fc553712a06c414e6a035233e1f1f5697b9a2d45ce38ff3de
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- query-ruby (0.1.0)
4
+ query-ruby (1.1.0)
5
5
  activesupport
6
6
  bigdecimal
7
7
  dorian-arguments
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 1.1.0
data/bin/query CHANGED
@@ -35,14 +35,14 @@ parsed =
35
35
  abort Query::Version.to_s if parsed.options.version
36
36
  abort parsed.help if parsed.options.help
37
37
 
38
- inputs = [parsed.options.input.to_s]
38
+ inputs = [parsed.options.input.to_s].compact_blank
39
39
  inputs += parsed.arguments
40
40
  inputs += parsed.files.map { |file| File.read(file) }
41
41
 
42
42
  input = inputs.join(" ")
43
43
 
44
44
  if parsed.options.parse
45
- pp Query::Parser.parse(input).to_raw
45
+ pp Query.parse(input)
46
46
  elsif parsed.options.decompile
47
47
  puts Query.decompile(Query.evaluate(input))
48
48
  elsif parsed.options.combine
@@ -11,13 +11,10 @@ class Query
11
11
  end
12
12
 
13
13
  def combine
14
- sources.map do |source|
15
- Query.evaluate(source)
16
- end.reduce do |acc, element|
17
- combine_parsed(acc, element)
18
- end.then do |parsed|
19
- Query.decompile(parsed)
20
- end
14
+ sources
15
+ .map { |source| Query.evaluate(source) }
16
+ .reduce { |acc, element| combine_parsed(acc, element) }
17
+ .then { |parsed| Query.decompile(parsed) }
21
18
  end
22
19
 
23
20
  private
@@ -25,9 +22,10 @@ class Query
25
22
  attr_accessor :sources
26
23
 
27
24
  def combine_parsed(left, right)
28
- (left + right).reverse.uniq do |node|
29
- node.is_a?(Hash) ? [node[:key], node[:operator]] : node
30
- end.reverse
25
+ (left + right)
26
+ .reverse
27
+ .uniq { |node| node.is_a?(Hash) ? [node[:key], node[:operator]] : node }
28
+ .reverse
31
29
  end
32
30
  end
33
31
  end
@@ -11,17 +11,33 @@ class Query
11
11
  end
12
12
 
13
13
  def decompile
14
- parsed.map { |node| format_value(node) }.join(" ")
14
+ if parsed.is_a?(Hash) && parsed.key?(:left)
15
+ format_logic_operator(parsed)
16
+ elsif parsed.is_a?(Hash) && parsed.key?(:right)
17
+ format_not_operator(parsed)
18
+ else
19
+ format_value(parsed)
20
+ end
21
+ end
22
+
23
+ def format_logic_operator(parsed)
24
+ left = self.class.decompile(parsed[:left])
25
+ right = self.class.decompile(parsed[:right])
26
+
27
+ "(#{left} #{parsed[:operator]} #{right})"
15
28
  end
16
29
 
17
- def format_string(string)
30
+ def format_not_operator(parsed)
31
+ right = self.class.decompile(parsed[:right])
32
+
33
+ "(#{parsed[:operator]} #{right})"
18
34
  end
19
35
 
20
36
  def format_value(value)
21
37
  if value.is_a?(Hash)
22
38
  "#{value[:key]}#{value[:operator]}#{format_value(value[:value])}"
23
39
  elsif value.is_a?(String)
24
- Query.evaluate(value).many? ? value.inspect : value
40
+ Query.evaluate(value).is_a?(Hash) ? value.inspect : value
25
41
  elsif value.is_a?(BigDecimal)
26
42
  value.to_s("F")
27
43
  elsif value.is_a?(Range)
@@ -7,15 +7,12 @@ class Query
7
7
 
8
8
  def initialize(parsed)
9
9
  self.whole = parsed.delete(:whole)
10
- self.exponent = Node::Value.new(parsed.delete(:exponent)) if parsed.key?(:exponent)
10
+ self.exponent =
11
+ Node::Value.new(parsed.delete(:exponent)) if parsed.key?(:exponent)
11
12
  end
12
13
 
13
14
  def evaluate(**args)
14
- if exponent
15
- whole.to_i * 10 ** exponent.evaluate(**args)
16
- else
17
- whole.to_i
18
- end
15
+ exponent ? whole.to_i * 10**exponent.evaluate(**args) : whole.to_i
19
16
  end
20
17
  end
21
18
  end
@@ -31,7 +31,7 @@ class Query
31
31
  "N" => false,
32
32
  "off" => false,
33
33
  "Off" => false,
34
- "OFF" => false,
34
+ "OFF" => false
35
35
  }
36
36
 
37
37
  def initialize(parsed)
@@ -15,7 +15,7 @@ class Query
15
15
 
16
16
  def evaluate(**args)
17
17
  if exponent
18
- BigDecimal(decimal) * 10 ** exponent.evaluate(**args)
18
+ BigDecimal(decimal) * 10**exponent.evaluate(**args)
19
19
  else
20
20
  BigDecimal(decimal)
21
21
  end
@@ -3,9 +3,11 @@
3
3
  class Query
4
4
  class Node
5
5
  class Operator < Node
6
- attr_accessor :operator
6
+ attr_accessor :prefix, :suffix
7
7
 
8
- OPERATORS = {
8
+ PREFIXES = { "+" => "", "-" => "!", "!" => "!", "" => "", nil => "" }
9
+
10
+ SUFFIXES = {
9
11
  "^" => "^",
10
12
  "$" => "$",
11
13
  ">=" => ">=",
@@ -20,15 +22,16 @@ class Query
20
22
  ":" => ":",
21
23
  "=" => "=",
22
24
  "==" => "=",
23
- "===" => "=",
25
+ "===" => "="
24
26
  }
25
27
 
26
28
  def initialize(parsed)
27
- self.operator = parsed
29
+ self.prefix = parsed.delete(:prefix)
30
+ self.suffix = parsed.delete(:suffix)
28
31
  end
29
32
 
30
33
  def evaluate(**args)
31
- OPERATORS.fetch(operator)
34
+ "#{PREFIXES.fetch(prefix)}#{SUFFIXES.fetch(suffix)}"
32
35
  end
33
36
  end
34
37
  end
@@ -3,14 +3,72 @@
3
3
  class Query
4
4
  class Node
5
5
  class Query < Node
6
- attr_accessor :parts
6
+ attr_accessor :left, :operator, :right, :query, :negative
7
+
8
+ LOGIC_OPERATORS = {
9
+ "or" => "or",
10
+ "Or" => "or",
11
+ "OR" => "or",
12
+ "||" => "or",
13
+ "|" => "or",
14
+ "and" => "and",
15
+ "And" => "and",
16
+ "AND" => "and",
17
+ "&&" => "and",
18
+ "&" => "and",
19
+ "" => "and",
20
+ nil => "and"
21
+ }
22
+
23
+ NEGATIVE_OPERATORS = {
24
+ "not" => "not",
25
+ "Not" => "not",
26
+ "NOT" => "not",
27
+ "!" => "not"
28
+ }
7
29
 
8
30
  def initialize(parsed)
9
- self.parts = (parsed.presence || []).map { |part| Part.new(part) }
31
+ self.negative = (parsed.presence || {}).delete(:not)
32
+
33
+ if parsed.is_a?(Hash)
34
+ if parsed.key?(:key_value)
35
+ self.query = KeyValue.new(parsed.delete(:key_value))
36
+ elsif parsed.key?(:string)
37
+ self.query = String.new(parsed.delete(:string))
38
+ else
39
+ self.left = Query.new(parsed.delete(:left))
40
+ self.operator = parsed.delete(:operator)
41
+ self.right = Query.new(parsed.delete(:right))
42
+ end
43
+ else
44
+ self.query = String.new(parsed)
45
+ end
10
46
  end
11
47
 
12
48
  def evaluate(**args)
13
- parts.map { |part| part.evaluate(**args) }
49
+ if negative && query
50
+ {
51
+ operator: NEGATIVE_OPERATORS.fetch(negative),
52
+ right: query.evaluate(**args)
53
+ }
54
+ elsif negative
55
+ {
56
+ operator: NEGATIVE_OPERATORS.fetch(negative),
57
+ right: {
58
+ left: left.evaluate(**args),
59
+ operator: LOGIC_OPERATORS.fetch(operator),
60
+ right: right.evaluate(**args)
61
+ }
62
+ }
63
+ elsif query
64
+ query.evaluate(**args)
65
+ else
66
+ {
67
+ left: left.evaluate(**args),
68
+ operator: LOGIC_OPERATORS.fetch(operator),
69
+ right: right.evaluate(**args)
70
+ }
71
+ end
14
72
  end
15
73
  end
16
74
  end
@@ -3,20 +3,16 @@
3
3
  class Query
4
4
  class Parser
5
5
  class Boolean < Language
6
- def special
7
- str("...") | str("..") | Whitespace | Operator
8
- end
9
-
10
6
  def root
11
7
  (
12
8
  (
13
9
  str("true") | str("t") | str("True") | str("TRUE") | str("T") |
14
10
  str("yes") | str("y") | str("Yes") | str("YES") | str("Y") |
15
11
  str("on") | str("On") | str("ON")
16
- str("false") | str("f") | str("False") | str("FALSE") |
17
- str("F") | str("no") | str("n") | str("No") | str("NO") |
18
- str("N") | str("off") | str("Off") | str("OFF")
19
- ) << special.present
12
+ str("false") | str("f") | str("False") | str("FALSE") | str("F") |
13
+ str("no") | str("n") | str("No") | str("NO") | str("N") |
14
+ str("off") | str("Off") | str("OFF")
15
+ ) << Special.present
20
16
  ).aka(:boolean)
21
17
  end
22
18
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Query
4
+ class Parser
5
+ class Group < Language
6
+ def root
7
+ str("(").ignore << Whitespace.maybe << Part.maybe <<
8
+ (Whitespace.maybe << str(")").ignore).maybe
9
+ end
10
+ end
11
+ end
12
+ end
@@ -4,7 +4,7 @@ class Query
4
4
  class Parser
5
5
  class Key < Language
6
6
  def root
7
- str("_").absent << (Whitespace.absent << Operator.absent << any).repeat(1)
7
+ (Special.absent << Operator.absent << any).repeat(1).aka(:key)
8
8
  end
9
9
  end
10
10
  end
@@ -4,9 +4,7 @@ class Query
4
4
  class Parser
5
5
  class KeyValue < Language
6
6
  def root
7
- (Key.aka(:key) << Operator.aka(:operator) << Value.aka(:value)).aka(
8
- :key_value
9
- )
7
+ (Key << Operator << Value).aka(:key_value)
10
8
  end
11
9
  end
12
10
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Query
4
+ class Parser
5
+ class LogicOperator < Language
6
+ def root
7
+ (
8
+ Whitespace << (
9
+ str("and") | str("And") | str("AND") | str("or") | str("Or") |
10
+ str("OR")
11
+ ) << Whitespace
12
+ ) |
13
+ (
14
+ Whitespace.maybe << (
15
+ str("&&") | str("&") | str("||") | str("|") | str("")
16
+ ) << Whitespace.maybe
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Query
4
+ class Parser
5
+ class NotOperator < Language
6
+ def root
7
+ ((str("not") | str("Not") | str("NOT")) << Whitespace) | str("!")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -3,10 +3,6 @@
3
3
  class Query
4
4
  class Parser
5
5
  class Number < Language
6
- def special
7
- str("...") | str("..") | Whitespace | Operator
8
- end
9
-
10
6
  def zero
11
7
  str("0")
12
8
  end
@@ -150,7 +146,7 @@ class Query
150
146
  decimal.aka(:decimal) | base_16_number.aka(:base_16) |
151
147
  base_8_number.aka(:base_8) | base_2_number.aka(:base_2) |
152
148
  base_10_number.aka(:base_10)
153
- ) << special.present
149
+ ) << Special.present
154
150
  ).aka(:number)
155
151
  end
156
152
  end
@@ -3,39 +3,36 @@
3
3
  class Query
4
4
  class Parser
5
5
  class Operator < Language
6
- OPERATORS = [
7
- "^",
8
- "$",
9
- ">=",
10
- "=>",
11
- "<=",
12
- "=<",
13
- "<",
14
- ">",
15
- "!",
16
- ]
6
+ PREFIXES = %w[- + !]
17
7
 
18
- MODIFIERS = [
19
- ":::",
20
- "::",
21
- ":",
22
- "===",
23
- "==",
24
- "=",
25
- "",
26
- ]
8
+ OPERATORS = %w[^ $ >= => <= =< < > !]
9
+
10
+ MODIFIERS = [":::", "::", ":", "===", "==", "=", ""]
27
11
 
28
12
  def root
29
- OPERATORS.flat_map do |operator|
30
- MODIFIERS.map do |modifier|
31
- str("#{operator}#{modifier}") |
32
- str("#{modifier}#{operator}") |
33
- str(operator)
34
- end.reduce(&:|).then { operator }
35
- end.reduce(&:|) |
36
- MODIFIERS.compact_blank.map do |modifier|
37
- str(modifier)
38
- end.reduce(&:|)
13
+ (
14
+ PREFIXES
15
+ .map { |prefix| str(prefix) }
16
+ .reduce(&:|)
17
+ .aka(:prefix)
18
+ .maybe << (
19
+ OPERATORS
20
+ .flat_map do |operator|
21
+ MODIFIERS
22
+ .map do |modifier|
23
+ str("#{operator}#{modifier}") |
24
+ str("#{modifier}#{operator}") | str(operator)
25
+ end
26
+ .reduce(&:|)
27
+ .then { operator }
28
+ end
29
+ .reduce(&:|) |
30
+ MODIFIERS
31
+ .compact_blank
32
+ .map { |modifier| str(modifier) }
33
+ .reduce(&:|)
34
+ ).aka(:suffix)
35
+ ).aka(:operator)
39
36
  end
40
37
  end
41
38
  end
@@ -4,7 +4,10 @@ class Query
4
4
  class Parser
5
5
  class Part < Language
6
6
  def root
7
- KeyValue | Text
7
+ (
8
+ Statement.aka(:left) << LogicOperator.aka(:operator) <<
9
+ Part.aka(:right)
10
+ ) | Statement
8
11
  end
9
12
  end
10
13
  end
@@ -3,12 +3,8 @@
3
3
  class Query
4
4
  class Parser
5
5
  class Query < Language
6
- def whitespace?
7
- Whitespace.maybe
8
- end
9
-
10
6
  def root
11
- whitespace? << (Part << Whitespace).repeat | any.repeat | whitespace?
7
+ Whitespace.maybe << Part.maybe << Whitespace.maybe
12
8
  end
13
9
  end
14
10
  end
@@ -11,20 +11,17 @@ class Query
11
11
  Whitespace.maybe
12
12
  end
13
13
 
14
- def special
15
- str("...") | str("..") | Whitespace | Operator
16
- end
17
-
18
14
  def root
19
15
  (
20
16
  (
21
17
  (
22
18
  Number.aka(:left) << operator.aka(:operator) << Number.aka(:right)
23
19
  ) |
24
- (
25
- String.aka(:left) << operator.aka(:operator) << String.aka(:right)
26
- )
27
- ) << special.present
20
+ (
21
+ String.aka(:left) << operator.aka(:operator) <<
22
+ String.aka(:right)
23
+ )
24
+ ) << Special.present
28
25
  ).aka(:range)
29
26
  end
30
27
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Query
4
+ class Parser
5
+ class Special < Language
6
+ def root
7
+ str("&") | str("|") | str("(") | str(")") | str("...") | str("..") |
8
+ Whitespace
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Query
4
+ class Parser
5
+ class Statement < Language
6
+ def root
7
+ (
8
+ Whitespace.maybe << NotOperator.aka(:not) << Whitespace.maybe
9
+ ).maybe << (Group | KeyValue | String)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -15,10 +15,6 @@ class Query
15
15
  str("\\")
16
16
  end
17
17
 
18
- def special
19
- str("...") | str("..") | Whitespace | Operator
20
- end
21
-
22
18
  def double_quoted_string
23
19
  double_quote.ignore << (
24
20
  (backslash.ignore << double_quote) | (double_quote.absent << any)
@@ -35,8 +31,8 @@ class Query
35
31
  (
36
32
  (
37
33
  double_quoted_string | single_quoted_string |
38
- (special.absent << any).repeat(1)
39
- ) << special.present
34
+ (Special.absent << any).repeat(1)
35
+ ) << Special.present
40
36
  ).aka(:string)
41
37
  end
42
38
  end
@@ -4,7 +4,7 @@ class Query
4
4
  class Parser
5
5
  class Value < Language
6
6
  def root
7
- Range | KeyValue | Boolean | Number | String
7
+ (Range | KeyValue | Boolean | Number | String).aka(:value)
8
8
  end
9
9
  end
10
10
  end
data/spec/query_spec.rb CHANGED
@@ -57,15 +57,18 @@ RSpec.describe Query do
57
57
  "verified:f",
58
58
  "verified:off",
59
59
  "verified:no",
60
+ "a b",
61
+ "a or b",
62
+ "a:1 or b",
63
+ "a:1 or b:2..3",
64
+ "(a or b) and (not b ! c)"
60
65
  ].each do |source|
61
66
  it source.inspect do
62
67
  Query.evaluate(source)
63
68
  end
64
69
 
65
70
  it "decompiles #{source.inspect}" do
66
- expect(
67
- Query.evaluate(Query.decompile(Query.evaluate(source)))
68
- ).to eq(
71
+ expect(Query.evaluate(Query.decompile(Query.evaluate(source)))).to eq(
69
72
  Query.evaluate(source)
70
73
  )
71
74
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: query-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dorian Marié
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-04-23 00:00:00.000000000 Z
10
+ date: 2025-04-28 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
@@ -132,23 +132,25 @@ files:
132
132
  - lib/query/node/key_value.rb
133
133
  - lib/query/node/number.rb
134
134
  - lib/query/node/operator.rb
135
- - lib/query/node/part.rb
136
135
  - lib/query/node/query.rb
137
136
  - lib/query/node/range.rb
138
137
  - lib/query/node/string.rb
139
- - lib/query/node/text.rb
140
138
  - lib/query/node/value.rb
141
139
  - lib/query/parser.rb
142
140
  - lib/query/parser/boolean.rb
141
+ - lib/query/parser/group.rb
143
142
  - lib/query/parser/key.rb
144
143
  - lib/query/parser/key_value.rb
144
+ - lib/query/parser/logic_operator.rb
145
+ - lib/query/parser/not_operator.rb
145
146
  - lib/query/parser/number.rb
146
147
  - lib/query/parser/operator.rb
147
148
  - lib/query/parser/part.rb
148
149
  - lib/query/parser/query.rb
149
150
  - lib/query/parser/range.rb
151
+ - lib/query/parser/special.rb
152
+ - lib/query/parser/statement.rb
150
153
  - lib/query/parser/string.rb
151
- - lib/query/parser/text.rb
152
154
  - lib/query/parser/value.rb
153
155
  - lib/query/parser/whitespace.rb
154
156
  - lib/query/version.rb
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Query
4
- class Node
5
- class Part < Node
6
- attr_accessor :part
7
-
8
- def initialize(parsed)
9
- if parsed.key?(:key_value)
10
- self.part = KeyValue.new(parsed.delete(:key_value))
11
- elsif parsed.key?(:text)
12
- self.part = Text.new(parsed.delete(:text))
13
- end
14
- end
15
-
16
- def evaluate(**args)
17
- part&.evaluate(**args)
18
- end
19
- end
20
- end
21
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Query
4
- class Node
5
- class Text < Node
6
- attr_accessor :text
7
-
8
- def initialize(parsed)
9
- self.text = parsed.presence
10
- end
11
-
12
- def evaluate(**args)
13
- text
14
- end
15
- end
16
- end
17
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Query
4
- class Parser
5
- class Text < Language
6
- def double_quote
7
- str('"')
8
- end
9
-
10
- def single_quote
11
- str("'")
12
- end
13
-
14
- def backslash
15
- str("\\")
16
- end
17
-
18
- def double_quoted_string
19
- double_quote.ignore << (
20
- (backslash.ignore << double_quote) | (double_quote.absent << any)
21
- ).repeat << double_quote.maybe.ignore
22
- end
23
-
24
- def single_quoted_string
25
- single_quote.ignore << (
26
- (backslash.ignore << single_quote) | (single_quote.absent << any)
27
- ).repeat << single_quote.maybe.ignore
28
- end
29
-
30
- def root
31
- (
32
- (
33
- double_quoted_string | single_quoted_string |
34
- (Whitespace.absent << any).repeat(1)
35
- ) << Whitespace.present
36
- ).aka(:text)
37
- end
38
- end
39
- end
40
- end