dnf 0.1.0 → 0.2.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: 38d7f35e2dbe89fc5eefae38715e9ffb4fd30c5fb2387afa1af111e7ef3d6ad2
4
- data.tar.gz: 793c5e967fa47434234bdd786c0e46084f0055d9ea76203d883d017dff542173
3
+ metadata.gz: 74bbea5fdc9ad7060421070e8befba31f219bc5ca1822b83632c3fd003544adf
4
+ data.tar.gz: 4e87cc325e331da97a77a224d2710c9ed98d036a6cd5cd8cc8905171377d294e
5
5
  SHA512:
6
- metadata.gz: 368413587764ad6bfb6a3dae501b815238e2141eaa4aea776f8d781e436fcbc0b7d1f1669e7e7527e15a9eeb603917e88315a4309240102e36e65cfb733efd15
7
- data.tar.gz: fafd471e6a1b1a6fc385cda79f9f8ae86148573f165ee7eff184bc1f4190533b66a0df0ea97cb3a9ec3ff35f3ce18dd91224a303a05e8bb074eed120d13543b1
6
+ metadata.gz: c9604fa877fe7ffdaa2ed172913bb450f9a224925aa890e86c06c2447881bbf49c12666a0bfb7ec397c6d41a22cc63509d391c4d6572b42d2c831ae089d99b40
7
+ data.tar.gz: e6b375c293bdd15ad9fac6143d7f3b278b58ede6128eec121412719c1e1d2d3f9d1c7d042557524aae8baeb5ec929789960f3ec3d28c1c4edca7fd705e9f7cff
@@ -0,0 +1,22 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [ master ]
5
+ pull_request:
6
+ branches: [ master ]
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ ruby-version: ['3.2', '3.3']
14
+ steps:
15
+ - uses: actions/checkout@v3
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: ${{ matrix.ruby-version }}
20
+ bundler-cache: true
21
+ - name: Run tests
22
+ run: bundle exec rspec
data/README.md CHANGED
@@ -10,12 +10,25 @@ gem 'dnf'
10
10
 
11
11
  ## Usage
12
12
 
13
+ Basic usage:
14
+
13
15
  ```ruby
14
16
  expression = "(!a | !b) & c"
15
17
  boolean_expression = Dnf::BooleanExpression.new(expression)
16
18
  boolean_expression.to_dnf # => "!a & c | !b & c"
17
19
  ```
18
20
 
21
+ Config:
22
+
23
+ ```ruby
24
+ Dnf::BooleanExpression.new(expression, {
25
+ variable_regex: /\w+/,
26
+ not_symbol: '!',
27
+ and_symbol: '&',
28
+ or_symbol: '|'
29
+ })
30
+ ```
31
+
19
32
  ## License
20
33
 
21
34
  The gem is available as open source under the terms of the MIT License.
@@ -1,9 +1,17 @@
1
1
  module Dnf
2
2
  class BooleanExpression
3
- attr_reader :expression
3
+ attr_reader :expression, :config
4
4
 
5
- def initialize(expression)
5
+ DEFAULT_CONFIG = {
6
+ variable_regex: /\w+/,
7
+ not_symbol: '!',
8
+ and_symbol: '&',
9
+ or_symbol: '|'
10
+ }
11
+
12
+ def initialize(expression, custom_config = {})
6
13
  @expression = expression
14
+ @config = DEFAULT_CONFIG.merge(custom_config)
7
15
  end
8
16
 
9
17
  def to_dnf
@@ -14,13 +22,66 @@ module Dnf
14
22
 
15
23
  private
16
24
 
25
+ def validate_expression(tokens)
26
+ raise ExpressionSyntaxError, "Expression cannot be empty" if tokens.empty?
27
+ validate_tokens(tokens)
28
+ validate_syntax(tokens)
29
+ validate_parentheses(tokens)
30
+ end
31
+
32
+ def validate_tokens(tokens)
33
+ valid_tokens = [
34
+ config[:not_symbol], config[:and_symbol], config[:or_symbol], '(', ')'
35
+ ]
36
+ tokens.each do |token|
37
+ next if valid_tokens.include?(token) || token.match?(/\A#{config[:variable_regex].source}\z/)
38
+ raise ExpressionSyntaxError, "Invalid token: #{token}"
39
+ end
40
+ end
41
+
42
+ def validate_syntax(tokens)
43
+ tokens.each_cons(2) do |prev, curr|
44
+ case prev
45
+ when config[:variable_regex], ')'
46
+ raise ExpressionSyntaxError, "Unexpected token: #{prev} #{curr}" unless curr == config[:and_symbol] || curr == config[:or_symbol] || curr == ')'
47
+ when config[:not_symbol]
48
+ raise ExpressionSyntaxError, "Unexpected token: #{prev} #{curr}" unless curr.match?(/\A#{config[:variable_regex].source}\z/) || curr == '('
49
+ when config[:and_symbol], config[:or_symbol], '('
50
+ raise ExpressionSyntaxError, "Unexpected token: #{prev} #{curr}" unless curr.match?(/\A#{config[:variable_regex].source}\z/) || curr == config[:not_symbol] || curr == '('
51
+ end
52
+ end
53
+ raise ExpressionSyntaxError, "Unexpected end: #{tokens.last}" unless tokens.last.match?(/\A#{config[:variable_regex].source}\z/) || tokens.last == ')'
54
+ end
55
+
56
+ def validate_parentheses(tokens)
57
+ stack = []
58
+ tokens.each do |token|
59
+ if token == '('
60
+ stack.push(token)
61
+ elsif token == ')'
62
+ raise ExpressionSyntaxError, "Mismatched parentheses" if stack.empty?
63
+ stack.pop
64
+ end
65
+ end
66
+ raise ExpressionSyntaxError, "Mismatched parentheses" unless stack.empty?
67
+ end
68
+
17
69
  def parse(expr)
18
70
  tokens = tokenize(expr)
71
+ validate_expression(tokens)
19
72
  parse_expression(tokens)
20
73
  end
21
74
 
22
75
  def tokenize(expr)
23
- expr.scan(/\w+|[&|!()]/)
76
+ regex = Regexp.union(
77
+ config[:variable_regex],
78
+ config[:not_symbol],
79
+ config[:and_symbol],
80
+ config[:or_symbol],
81
+ /[()]/,
82
+ /\S+/
83
+ )
84
+ expr.scan(regex)
24
85
  end
25
86
 
26
87
  def parse_expression(tokens)
@@ -29,7 +90,7 @@ module Dnf
29
90
 
30
91
  def parse_or(tokens)
31
92
  left = parse_and(tokens)
32
- while tokens.first == '|'
93
+ while tokens.first == config[:or_symbol]
33
94
  tokens.shift
34
95
  right = parse_and(tokens)
35
96
  left = [:or, left, right]
@@ -39,7 +100,7 @@ module Dnf
39
100
 
40
101
  def parse_and(tokens)
41
102
  left = parse_not(tokens)
42
- while tokens.first == '&'
103
+ while tokens.first == config[:and_symbol]
43
104
  tokens.shift
44
105
  right = parse_not(tokens)
45
106
  left = [:and, left, right]
@@ -48,7 +109,7 @@ module Dnf
48
109
  end
49
110
 
50
111
  def parse_not(tokens)
51
- if tokens.first == '!'
112
+ if tokens.first == config[:not_symbol]
52
113
  tokens.shift
53
114
  expr = parse_primary(tokens)
54
115
  [:not, expr]
@@ -119,11 +180,11 @@ module Dnf
119
180
  when :var
120
181
  expr[1]
121
182
  when :not
122
- "!#{dnf_to_string(expr[1])}"
183
+ "#{config[:not_symbol]}#{dnf_to_string(expr[1])}"
123
184
  when :and
124
- "#{dnf_to_string(expr[1])} & #{dnf_to_string(expr[2])}"
185
+ "#{dnf_to_string(expr[1])} #{config[:and_symbol]} #{dnf_to_string(expr[2])}"
125
186
  when :or
126
- "#{dnf_to_string(expr[1])} | #{dnf_to_string(expr[2])}"
187
+ "#{dnf_to_string(expr[1])} #{config[:or_symbol]} #{dnf_to_string(expr[2])}"
127
188
  end
128
189
  end
129
190
  end
data/lib/dnf/errors.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Dnf
2
+ class ExpressionSyntaxError < StandardError; end
3
+ end
data/lib/dnf/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dnf
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
data/lib/dnf.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'dnf/version'
2
+ require 'dnf/errors'
2
3
  require 'dnf/boolean_expression'
3
4
 
4
5
  module Dnf
@@ -2,6 +2,51 @@ require 'spec_helper'
2
2
  require 'dnf'
3
3
 
4
4
  RSpec.describe Dnf::BooleanExpression do
5
+
6
+ it 'allows custom regex for variables' do
7
+ expect(Dnf::BooleanExpression.new('👍 & (b | my-var-c)', variable_regex: /[\w\-👍]+/).to_dnf).to eq('👍 & b | 👍 & my-var-c')
8
+ end
9
+
10
+ it 'allows custom symbols for boolean operators' do
11
+ expect(Dnf::BooleanExpression.new('¬a ∧ (b ∨ c)', not_symbol: '¬', and_symbol: '∧', or_symbol: '∨').to_dnf).to eq('¬a ∧ b ∨ ¬a ∧ c')
12
+ end
13
+
14
+ it 'allows multiple symbols for boolean operators' do
15
+ expect(Dnf::BooleanExpression.new('!a && (b || c)', not_symbol: '!', and_symbol: '&&', or_symbol: '||').to_dnf).to eq('!a && b || !a && c')
16
+ end
17
+
18
+ describe 'validation' do
19
+ it 'raises an exception when the expression is empty' do
20
+ expect {
21
+ Dnf::BooleanExpression.new(' ').to_dnf
22
+ }.to raise_error(Dnf::ExpressionSyntaxError, 'Expression cannot be empty')
23
+ end
24
+
25
+ it 'raises an exception when a token is invalid' do
26
+ expect {
27
+ Dnf::BooleanExpression.new('a & ### b').to_dnf
28
+ }.to raise_error(Dnf::ExpressionSyntaxError, 'Invalid token: ###')
29
+ end
30
+
31
+ it 'raises an exception when the syntax is invalid (unexpected token)' do
32
+ expect {
33
+ Dnf::BooleanExpression.new('a & | b').to_dnf
34
+ }.to raise_error(Dnf::ExpressionSyntaxError, 'Unexpected token: & |')
35
+ end
36
+
37
+ it 'raises an exception when the syntax is invalid (unexpected end)' do
38
+ expect {
39
+ Dnf::BooleanExpression.new('a &').to_dnf
40
+ }.to raise_error(Dnf::ExpressionSyntaxError, 'Unexpected end: &')
41
+ end
42
+
43
+ it 'raises an exception when the parentheses are mismatched' do
44
+ expect {
45
+ Dnf::BooleanExpression.new('a & ((b | c)').to_dnf
46
+ }.to raise_error(Dnf::ExpressionSyntaxError, 'Mismatched parentheses')
47
+ end
48
+ end
49
+
5
50
  describe '#to_dnf' do
6
51
  it 'converts simple variable' do
7
52
  expect(Dnf::BooleanExpression.new('a').to_dnf).to eq('a')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dnf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marco Colli
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-11 00:00:00.000000000 Z
11
+ date: 2024-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -24,12 +24,13 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- description:
28
- email:
27
+ description:
28
+ email:
29
29
  executables: []
30
30
  extensions: []
31
31
  extra_rdoc_files: []
32
32
  files:
33
+ - ".github/workflows/ci.yml"
33
34
  - ".gitignore"
34
35
  - ".rspec"
35
36
  - Gemfile
@@ -38,6 +39,7 @@ files:
38
39
  - dnf.gemspec
39
40
  - lib/dnf.rb
40
41
  - lib/dnf/boolean_expression.rb
42
+ - lib/dnf/errors.rb
41
43
  - lib/dnf/version.rb
42
44
  - spec/dnf/boolean_expression_spec.rb
43
45
  - spec/spec_helper.rb
@@ -45,7 +47,7 @@ homepage: https://github.com/collimarco/dnf-ruby
45
47
  licenses:
46
48
  - MIT
47
49
  metadata: {}
48
- post_install_message:
50
+ post_install_message:
49
51
  rdoc_options: []
50
52
  require_paths:
51
53
  - lib
@@ -60,8 +62,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
60
62
  - !ruby/object:Gem::Version
61
63
  version: '0'
62
64
  requirements: []
63
- rubygems_version: 3.0.3.1
64
- signing_key:
65
+ rubygems_version: 3.5.11
66
+ signing_key:
65
67
  specification_version: 4
66
68
  summary: Convert any boolean expression to disjunctive normal form (DNF).
67
69
  test_files: []