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 +4 -4
- data/.github/workflows/ci.yml +22 -0
- data/README.md +13 -0
- data/lib/dnf/boolean_expression.rb +70 -9
- data/lib/dnf/errors.rb +3 -0
- data/lib/dnf/version.rb +1 -1
- data/lib/dnf.rb +1 -0
- data/spec/dnf/boolean_expression_spec.rb +45 -0
- metadata +10 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74bbea5fdc9ad7060421070e8befba31f219bc5ca1822b83632c3fd003544adf
|
4
|
+
data.tar.gz: 4e87cc325e331da97a77a224d2710c9ed98d036a6cd5cd8cc8905171377d294e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
-
"
|
183
|
+
"#{config[:not_symbol]}#{dnf_to_string(expr[1])}"
|
123
184
|
when :and
|
124
|
-
"#{dnf_to_string(expr[1])}
|
185
|
+
"#{dnf_to_string(expr[1])} #{config[:and_symbol]} #{dnf_to_string(expr[2])}"
|
125
186
|
when :or
|
126
|
-
"#{dnf_to_string(expr[1])}
|
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
data/lib/dnf/version.rb
CHANGED
data/lib/dnf.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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: []
|