dnf 0.1.0 → 0.2.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: 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: []