factbase 0.0.29 → 0.0.31

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: cce648530bed16f5d251c8d84dae0a3c8726f1c688fbfdbf3723a39fe9f3e696
4
- data.tar.gz: e0f31869dab8d231f63d2c075661e2e4ead17576d258bcc6bd19a643a659e35b
3
+ metadata.gz: 016b53cf00bc9efec24f50c333ab1fc14266151487fab61ce7d7ca533ba602a6
4
+ data.tar.gz: 89845aa58068bf78189d62602282dbcffe8f99a5d4b772dcf8bfd3ee4b3698d4
5
5
  SHA512:
6
- metadata.gz: a2e76c6c010157ba7aff6098e44c9cde7958d288b0bb7ea41a69201ab1783763da31f657c52156a2991ac02fe56e48d31acdb3fb71749827eb126b5a363416e5
7
- data.tar.gz: 908fa035b142fea947303b3306b38f3443e782d4a182f158d6674cc8396a704fb45cc68b4533ce61ded2e98a7fe57c2fd23716706df56b0a7254aa8f5dd35539
6
+ metadata.gz: 0d2fecf64f6b62d4ad8796469f4aa937a63e2398f032a812fc15b95ea6be48f0aa25fe032bc469e4afd4ced351f6ba8fbc914bf1dc074dd07ae881b481e39d0b
7
+ data.tar.gz: 9843fdc79bf8b53aae6b366d1ea133f92185a85dc37ed5f6779d478d030655e621d5431cf793ee162f79fddc4c51ae49403850f25849cbcefb7ac793cc4975ae
@@ -0,0 +1,57 @@
1
+ # Copyright (c) 2024 Yegor Bugayenko
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the 'Software'), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+ ---
21
+ name: license
22
+ 'on':
23
+ push:
24
+ branches:
25
+ - master
26
+ pull_request:
27
+ branches:
28
+ - master
29
+ jobs:
30
+ license:
31
+ runs-on: ubuntu-22.04
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ - shell: bash
35
+ run: |
36
+ header="Copyright (c) $(date +%Y) Yegor Bugayenko"
37
+ failed="false"
38
+ while IFS= read -r file; do
39
+ if ! grep -q "${header}" "${file}"; then
40
+ failed="true"
41
+ echo "⚠️ Copyright header is not found in: ${file}"
42
+ else
43
+ echo "File looks good: ${file}"
44
+ fi
45
+ done < <(find . -type f \( \
46
+ -name "Dockerfile" -o \
47
+ -name "LICENSE.txt" -o \
48
+ -name "Makefile" -o \
49
+ -name "Rakefile" -o \
50
+ -name "*.sh" -o \
51
+ -name "*.rb" -o \
52
+ -name "*.fe" -o \
53
+ -name "*.yml" \
54
+ \) -print)
55
+ if [ "${failed}" = "true" ]; then
56
+ exit 1
57
+ fi
data/Gemfile CHANGED
@@ -23,10 +23,10 @@
23
23
  source 'https://rubygems.org'
24
24
  gemspec
25
25
 
26
- gem 'minitest', '5.23.0', require: false
26
+ gem 'minitest', '5.23.1', require: false
27
27
  gem 'rake', '13.2.1', require: false
28
28
  gem 'rspec-rails', '6.1.2', require: false
29
- gem 'rubocop', '1.63.5', require: false
29
+ gem 'rubocop', '1.64.0', require: false
30
30
  gem 'rubocop-performance', '1.21.0', require: false
31
31
  gem 'rubocop-rspec', '2.29.2', require: false
32
32
  gem 'simplecov', '0.22.0', require: false
data/Gemfile.lock CHANGED
@@ -59,7 +59,7 @@ GEM
59
59
  crass (~> 1.0.2)
60
60
  nokogiri (>= 1.12.0)
61
61
  loog (0.5.1)
62
- minitest (5.23.0)
62
+ minitest (5.23.1)
63
63
  mutex_m (0.2.0)
64
64
  nokogiri (1.16.5-arm64-darwin)
65
65
  racc (~> 1.4)
@@ -75,7 +75,7 @@ GEM
75
75
  racc
76
76
  psych (5.1.2)
77
77
  stringio
78
- racc (1.7.3)
78
+ racc (1.8.0)
79
79
  rack (3.0.11)
80
80
  rack-session (2.0.0)
81
81
  rack (>= 3.0.0)
@@ -125,7 +125,7 @@ GEM
125
125
  rspec-mocks (~> 3.13)
126
126
  rspec-support (~> 3.13)
127
127
  rspec-support (3.13.1)
128
- rubocop (1.63.5)
128
+ rubocop (1.64.0)
129
129
  json (~> 2.3)
130
130
  language_server-protocol (>= 3.17.0)
131
131
  parallel (~> 1.10)
@@ -181,10 +181,10 @@ PLATFORMS
181
181
 
182
182
  DEPENDENCIES
183
183
  factbase!
184
- minitest (= 5.23.0)
184
+ minitest (= 5.23.1)
185
185
  rake (= 13.2.1)
186
186
  rspec-rails (= 6.1.2)
187
- rubocop (= 1.63.5)
187
+ rubocop (= 1.64.0)
188
188
  rubocop-performance (= 1.21.0)
189
189
  rubocop-rspec (= 2.29.2)
190
190
  simplecov (= 0.22.0)
data/README.md CHANGED
@@ -14,7 +14,16 @@
14
14
  This Ruby gem manages an in-memory database of facts.
15
15
  A fact is simply a map of properties and values.
16
16
  The values are either atomic literals or non-empty sets of literals.
17
- It is possible to delete a fact, but impossible to delete a property from a fact.
17
+ It is possible to delete a fact, but impossible to delete a property
18
+ from a fact.
19
+
20
+ **ATTENTION**: The current implemention is naive and,
21
+ because of that, very slow. I will be very happy
22
+ if you suggest a better implementation without the change of the interface.
23
+ The `Factbase::query()` method is what mostly needs performance optimization:
24
+ currently it simply iterates through all facts in the factbase in order
25
+ to find those that match the provided terms. Obviously,
26
+ even a simple indexing may significantly increase performance.
18
27
 
19
28
  Here is how you use it (it's thread-safe, by the way):
20
29
 
@@ -60,12 +69,19 @@ All terms available in a query:
60
69
  * `(gt a b)` returns true if `a` is greater than `b`
61
70
  * `(size k)` returns cardinality of `k` property (zero if property is absent)
62
71
  * `(type a)` returns type of `a` ("String", "Integer", "Float", or "Time")
63
- * `(matches a re)` returns type when `a` matches regular expression `re`
72
+ * `(matches a re)` returns true when `a` matches regular expression `re`
64
73
  * `(defn foo "self.to_s")` defines a new term using Ruby syntax and returns true
65
74
 
75
+ There are also terms that match the entire factbase:
76
+
77
+ * `(max k)` returns true if the value of `k` property
78
+ is the largest in the entire factbase
79
+ * `(min k)` returns true if the value of `k` is the smallest
80
+
66
81
  ## How to contribute
67
82
 
68
- Read [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html).
83
+ Read
84
+ [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html).
69
85
  Make sure you build is green before you contribute
70
86
  your pull request. You will need to have
71
87
  [Ruby](https://www.ruby-lang.org/en/) 3.2+ and
@@ -42,7 +42,7 @@ class Factbase::Looged
42
42
 
43
43
  def insert
44
44
  f = @fb.insert
45
- @loog.debug('Inserted new fact')
45
+ @loog.debug("Inserted new fact ##{@fb.size}")
46
46
  Fact.new(f, @loog)
47
47
  end
48
48
 
@@ -79,6 +79,7 @@ class Factbase::Looged
79
79
  v = args[1]
80
80
  s = v.is_a?(Time) ? v.utc.iso8601 : v.to_s
81
81
  s = v.to_s.inspect if v.is_a?(String)
82
+ s = "#{s[0..40]}...#{s[-40..]}" if s.length > 80
82
83
  @loog.debug("Set '#{k[0..-2]}' to #{s} (#{v.class})") if k.end_with?('=')
83
84
  r
84
85
  end
@@ -103,13 +104,14 @@ class Factbase::Looged
103
104
  end
104
105
 
105
106
  def each(&)
107
+ q = Factbase::Syntax.new(@expr).to_term.to_s
106
108
  if block_given?
107
109
  r = @query.each(&)
108
110
  raise ".each of #{@query.class} returned #{r.class}" unless r.is_a?(Integer)
109
111
  if r.zero?
110
- @loog.debug("Nothing found by '#{@expr}'")
112
+ @loog.debug("Nothing found by '#{q}'")
111
113
  else
112
- @loog.debug("Found #{r} fact(s) by '#{@expr}'")
114
+ @loog.debug("Found #{r} fact(s) by '#{q}'")
113
115
  end
114
116
  r
115
117
  else
@@ -120,9 +122,9 @@ class Factbase::Looged
120
122
  end
121
123
  # rubocop:enable Style/MapIntoArray
122
124
  if array.empty?
123
- @loog.debug("Nothing found by '#{@expr}'")
125
+ @loog.debug("Nothing found by '#{q}'")
124
126
  else
125
- @loog.debug("Found #{array.size} fact(s) by '#{@expr}'")
127
+ @loog.debug("Found #{array.size} fact(s) by '#{q}'")
126
128
  end
127
129
  array
128
130
  end
@@ -40,7 +40,7 @@ class Factbase::Query
40
40
  # @return [Integer] Total number of facts yielded
41
41
  def each
42
42
  return to_enum(__method__) unless block_given?
43
- term = Factbase::Syntax.new(@query).to_term
43
+ term = Factbase::Syntax.new(@query).to_term.on(@maps)
44
44
  yielded = 0
45
45
  @maps.each do |m|
46
46
  f = Factbase::Fact.new(@mutex, m)
@@ -39,17 +39,26 @@ class Factbase::Syntax
39
39
  # Convert it to a term.
40
40
  # @return [Term] The term detected
41
41
  def to_term
42
+ build.simplify
43
+ rescue StandardError => e
44
+ raise "#{e.message} in \"#{@query}\""
45
+ end
46
+
47
+ private
48
+
49
+ # Convert it to a term.
50
+ # @return [Term] The term detected
51
+ def build
42
52
  @tokens ||= to_tokens
53
+ raise 'No tokens' if @tokens.empty?
43
54
  @ast ||= to_ast(@tokens, 0)
44
- raise "Too many terms: #{@query}" if @ast[1] != @tokens.size
55
+ raise 'Too many terms' if @ast[1] != @tokens.size
45
56
  term = @ast[0]
46
- raise "No terms found: #{@query}" if term.nil?
47
- raise "Not a term: #{@query}" unless term.is_a?(Factbase::Term)
57
+ raise 'No terms found' if term.nil?
58
+ raise 'Not a term' unless term.is_a?(Factbase::Term)
48
59
  term
49
60
  end
50
61
 
51
- private
52
-
53
62
  # Reads the stream of tokens, starting at the +at+ position. If the
54
63
  # token at the position is not a literal (like 42 or "Hello") but a term,
55
64
  # the function recursively calls itself.
@@ -58,7 +67,7 @@ class Factbase::Syntax
58
67
  # is the term/literal and the second one is the position where the
59
68
  # scanning should continue.
60
69
  def to_ast(tokens, at)
61
- raise "Closing too soon at ##{at}: #{@query}" if tokens[at] == :close
70
+ raise "Closing too soon at ##{at}" if tokens[at] == :close
62
71
  return [tokens[at], at + 1] unless tokens[at] == :open
63
72
  at += 1
64
73
  op = tokens[at]
@@ -66,11 +75,11 @@ class Factbase::Syntax
66
75
  operands = []
67
76
  at += 1
68
77
  loop do
69
- raise "End of token stream at ##{at}: #{@query}" if tokens[at].nil?
78
+ raise "End of token stream at ##{at}" if tokens[at].nil?
70
79
  break if tokens[at] == :close
71
80
  (operand, at1) = to_ast(tokens, at)
72
- raise "Stuck at position ##{at}: #{@query}" if at == at1
73
- raise "Jump back at position ##{at}: #{@query}" if at1 < at
81
+ raise "Stuck at position ##{at}" if at == at1
82
+ raise "Jump back at position ##{at}" if at1 < at
74
83
  at = at1
75
84
  operands << operand
76
85
  break if tokens[at] == :close
@@ -110,12 +119,12 @@ class Factbase::Syntax
110
119
  acc += c
111
120
  end
112
121
  end
113
- raise "String not closed: : #{@query}" if string
122
+ raise 'String not closed' if string
114
123
  list.map do |t|
115
124
  if t.is_a?(Symbol)
116
125
  t
117
126
  elsif t.start_with?('\'', '"')
118
- raise "String literal can't be empty: #{@query}" if t.length <= 2
127
+ raise 'String literal can\'t be empty' if t.length <= 2
119
128
  t[1..-2]
120
129
  elsif t.match?(/^[0-9]+$/)
121
130
  t.to_i
@@ -124,7 +133,7 @@ class Factbase::Syntax
124
133
  elsif t.match?(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/)
125
134
  Time.parse(t)
126
135
  else
127
- raise "Wrong symbol format (#{t}): #{@query}" unless t.match?(/^[a-z][a-zA-Z0-9_]*$/)
136
+ raise "Wrong symbol format (#{t})" unless t.match?(/^[a-z][a-zA-Z0-9_]*$/)
128
137
  t.to_sym
129
138
  end
130
139
  end
data/lib/factbase/term.rb CHANGED
@@ -45,6 +45,29 @@ class Factbase::Term
45
45
  send(@op, fact)
46
46
  end
47
47
 
48
+ # Put it into the context: let it see the entire array of maps.
49
+ # @param [Array] maps The maps
50
+ # @return [Factbase::Term] Itself
51
+ def on(maps)
52
+ m = "#{@op}_on"
53
+ send(m, maps) if respond_to?(m, true)
54
+ @operands.each do |o|
55
+ o.on(maps) if o.is_a?(Factbase::Term)
56
+ end
57
+ self
58
+ end
59
+
60
+ # Simplify it if possible.
61
+ # @return [Factbase::Term] New term or itself
62
+ def simplify
63
+ m = "#{@op}_simplify"
64
+ if respond_to?(m, true)
65
+ send(m)
66
+ else
67
+ self
68
+ end
69
+ end
70
+
48
71
  # Turns it into a string.
49
72
  # @return [String] The string of it
50
73
  def to_s
@@ -71,29 +94,45 @@ class Factbase::Term
71
94
 
72
95
  def not(fact)
73
96
  assert_args(1)
74
- r = @operands[0].evaluate(fact)
75
- raise 'Boolean expected' unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
76
- !r
97
+ !only_bool(the_value(0, fact))
77
98
  end
78
99
 
79
100
  def or(fact)
80
- @operands.each do |o|
81
- r = o.evaluate(fact)
82
- raise 'Boolean expected' unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
83
- return true if r
101
+ (0..@operands.size - 1).each do |i|
102
+ return true if only_bool(the_value(i, fact))
84
103
  end
85
104
  false
86
105
  end
87
106
 
88
107
  def and(fact)
89
- @operands.each do |o|
90
- r = o.evaluate(fact)
91
- raise 'Boolean expected' unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
92
- return false unless r
108
+ (0..@operands.size - 1).each do |i|
109
+ return false unless only_bool(the_value(i, fact))
93
110
  end
94
111
  true
95
112
  end
96
113
 
114
+ def and_or_simplify
115
+ strs = []
116
+ ops = []
117
+ @operands.each do |o|
118
+ o = o.simplify
119
+ s = o.to_s
120
+ next if strs.include?(s)
121
+ strs << s
122
+ ops << o
123
+ end
124
+ return ops[0] if ops.size == 1
125
+ Factbase::Term.new(@op, ops)
126
+ end
127
+
128
+ def and_simplify
129
+ and_or_simplify
130
+ end
131
+
132
+ def or_simplify
133
+ and_or_simplify
134
+ end
135
+
97
136
  def when(fact)
98
137
  assert_args(2)
99
138
  a = @operands[0]
@@ -174,6 +213,26 @@ class Factbase::Term
174
213
  true
175
214
  end
176
215
 
216
+ def min(fact)
217
+ vv = the_value(0, fact)
218
+ return nil if vv.nil?
219
+ vv.any? { |v| v == @min }
220
+ end
221
+
222
+ def max(fact)
223
+ vv = the_value(0, fact)
224
+ return nil if vv.nil?
225
+ vv.any? { |v| v == @max }
226
+ end
227
+
228
+ def max_on(maps)
229
+ @max = best(maps) { |v, b| v > b }
230
+ end
231
+
232
+ def min_on(maps)
233
+ @min = best(maps) { |v, b| v < b }
234
+ end
235
+
177
236
  def assert_args(num)
178
237
  c = @operands.size
179
238
  raise "Too many (#{c}) operands for '#{@op}' (#{num} expected)" if c > num
@@ -195,4 +254,26 @@ class Factbase::Term
195
254
  v = [v] unless v.is_a?(Array)
196
255
  v
197
256
  end
257
+
258
+ def only_bool(val)
259
+ val = val[0] if val.is_a?(Array)
260
+ return false if val.nil?
261
+ raise "Boolean expected, while #{val.class} received" unless val.is_a?(TrueClass) || val.is_a?(FalseClass)
262
+ val
263
+ end
264
+
265
+ def best(maps)
266
+ k = @operands[0]
267
+ raise "A symbol expected, but provided: #{k}" unless k.is_a?(Symbol)
268
+ best = nil
269
+ maps.each do |m|
270
+ vv = m[k.to_s]
271
+ next if vv.nil?
272
+ vv = [vv] unless vv.is_a?(Array)
273
+ vv.each do |v|
274
+ best = v if best.nil? || yield(v, best)
275
+ end
276
+ end
277
+ best
278
+ end
198
279
  end
data/lib/factbase.rb CHANGED
@@ -29,7 +29,7 @@ require 'yaml'
29
29
  # License:: MIT
30
30
  class Factbase
31
31
  # Current version of the gem (changed by .rultor.yml on every release)
32
- VERSION = '0.0.29'
32
+ VERSION = '0.0.31'
33
33
 
34
34
  # Constructor.
35
35
  def initialize(facts = [])
@@ -84,6 +84,14 @@ class TestFact < Minitest::Test
84
84
  assert_equal(42, f.foo_bar, f.to_s)
85
85
  end
86
86
 
87
+ def test_set_twice_same_value
88
+ map = {}
89
+ f = Factbase::Fact.new(Mutex.new, map)
90
+ f.foo = 42
91
+ f.foo = 42
92
+ assert_equal(42, map['foo'])
93
+ end
94
+
87
95
  def test_time_in_utc
88
96
  f = Factbase::Fact.new(Mutex.new, {})
89
97
  t = Time.now
@@ -69,13 +69,30 @@ class TestLooged < Minitest::Test
69
69
  fb.insert
70
70
  fb.insert.bar = 3
71
71
  fb.insert
72
+ fb.insert.str =
73
+ "Он поскорей звонит. Вбегает
74
+ К нему слуга француз Гильо,
75
+ Халат и туфли предлагает
76
+ И подает ему белье.
77
+ Спешит Онегин одеваться,
78
+ Слуге велит приготовляться
79
+ С ним вместе ехать и с собой
80
+ Взять также ящик боевой.
81
+ Готовы санки беговые.
82
+ Он сел, на мельницу летит.
83
+ Примчались. Он слуге велит
84
+ Лепажа стволы роковые
85
+ Нести за ним, а лошадям
86
+ Отъехать в поле к двум дубкам."
72
87
  fb.query('(exists bar)').each(&:to_s)
73
88
  fb.query('(not (exists bar))').delete!
74
89
  [
75
- 'Inserted new fact',
90
+ 'Inserted new fact #1',
91
+ 'Inserted new fact #2',
76
92
  'Set \'bar\' to 3 (Integer)',
93
+ 'Set \'str\' to "Он поскорей звонит. Вбегает\n К нем...м\n Отъехать в поле к двум дубкам." (String)',
77
94
  'Found 1 fact(s) by \'(exists bar)\'',
78
- 'Deleted 2 fact(s) by \'(not (exists bar))\''
95
+ 'Deleted 3 fact(s) by \'(not (exists bar))\''
79
96
  ].each do |s|
80
97
  assert(log.to_s.include?("#{s}\n"), "#{log}\n")
81
98
  end
@@ -60,6 +60,10 @@ class TestQuery < Minitest::Test
60
60
  '(eq (size hello) 0)' => 3,
61
61
  '(eq num pi)' => 0,
62
62
  '(absent time)' => 2,
63
+ '(max num)' => 1,
64
+ '(and (exists time) (max num))' => 0,
65
+ '(and (exists pi) (max num))' => 1,
66
+ '(min time)' => 1,
63
67
  '(and (absent time) (exists pi))' => 1,
64
68
  "(and (exists time) (not (\t\texists pi)))" => 1,
65
69
  "(or (eq num 66) (lt time #{(Time.now - 200).utc.iso8601}))" => 1
@@ -106,9 +106,21 @@ class TestSyntax < Minitest::Test
106
106
  ')',
107
107
  '"'
108
108
  ].each do |q|
109
- assert_raises(q) do
110
- Factbase::Syntax.new(q).to_term
111
- end
109
+ assert(
110
+ assert_raises(q) do
111
+ Factbase::Syntax.new(q).to_term
112
+ end.message.include?(q)
113
+ )
114
+ end
115
+ end
116
+
117
+ def test_simplification
118
+ {
119
+ '(foo)' => '(foo)',
120
+ '(and (foo) (foo))' => '(foo)',
121
+ '(and (foo) (or (and (eq a 1))) (eq a 1) (foo))' => '(and (foo) (eq a 1))'
122
+ }.each do |s, t|
123
+ assert_equal(t, Factbase::Syntax.new(s).to_term.to_s)
112
124
  end
113
125
  end
114
126
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factbase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.29
4
+ version: 0.0.31
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-21 00:00:00.000000000 Z
11
+ date: 2024-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -78,6 +78,7 @@ files:
78
78
  - ".gitattributes"
79
79
  - ".github/workflows/actionlint.yml"
80
80
  - ".github/workflows/codecov.yml"
81
+ - ".github/workflows/license.yml"
81
82
  - ".github/workflows/markdown-lint.yml"
82
83
  - ".github/workflows/pdd.yml"
83
84
  - ".github/workflows/rake.yml"