factbase 0.0.29 → 0.0.31

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: 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"