judges 0.0.10 → 0.0.12

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: 550115852b44ae26b7af9cb776bcbf9e5b7e043579ab8eec3930854f573a6768
4
- data.tar.gz: 7efe473542ffd401220d8e5b65d853ba144ad8ceab0273f9531e3d462e072afc
3
+ metadata.gz: 63754c5826506b6066688aa2373dafcd87fc20cd193631c15ce67f1496dc361b
4
+ data.tar.gz: a7c70946e0c24bffc91849d81e15866335ef0ebeb64b7f1cefd2c3d6c2d48506
5
5
  SHA512:
6
- metadata.gz: '096f25e43ee418b94ae0a2f4b3d678c170170dfda2228191a269931bcfa1150e4f50151830e1132b3282ccaa768b2129deb8f29e55faebefd0bc7c2bd5ce2700'
7
- data.tar.gz: d3f8922eeee30008f3883a7592667ce084a1d96785c4b04c51478c028ba99aa28eaddd151ac13a1bb7a101de8cc0f05434d22edcf6bbb1691b4bb76a4bd89067
6
+ metadata.gz: 6360db23fb5fc573d479a43d40b4c1d1e274bd21320cb4c2acba0e71a6ac8775c3398fef776dc1d7412ccd96782394e53d9631180c5a09314aab9e791738829a
7
+ data.tar.gz: e5b7a0c2460d589bf9cb69e42183585d13ac6ac1b400f338e3c73057be0d2fdb9c703bcc2896f73d8dbd5f850b22452d8f5a2b147e299a1ed9fd2b1aabe2f564
@@ -30,7 +30,7 @@ jobs:
30
30
  - uses: actions/checkout@v4
31
31
  - uses: ruby/setup-ruby@v1
32
32
  with:
33
- ruby-version: 2.7
33
+ ruby-version: 3.2
34
34
  - run: bundle update
35
35
  - run: bundle exec rake
36
36
  - uses: codecov/codecov-action@v4.0.0-beta.3
@@ -32,7 +32,7 @@ jobs:
32
32
  strategy:
33
33
  matrix:
34
34
  os: [ubuntu-20.04, macos-12]
35
- ruby: [2.7, 3.2]
35
+ ruby: [3.2]
36
36
  runs-on: ${{ matrix.os }}
37
37
  steps:
38
38
  - uses: actions/checkout@v4
data/.rubocop.yml CHANGED
@@ -49,3 +49,5 @@ Layout/EmptyLineAfterGuardClause:
49
49
  Enabled: false
50
50
  Layout/CaseIndentation:
51
51
  Enabled: false
52
+ Naming/MethodParameterName:
53
+ MinNameLength: 2
data/bin/judges CHANGED
@@ -29,53 +29,63 @@ require 'factbase'
29
29
  Encoding.default_external = Encoding::UTF_8
30
30
  Encoding.default_internal = Encoding::UTF_8
31
31
 
32
- class App
33
- extend GLI::App
32
+ include GLI::App
34
33
 
35
- loog = Loog::REGULAR
34
+ loog = Loog::REGULAR
36
35
 
37
- program_desc 'Automated executor of judges for a factbase'
36
+ program_desc('Automated executor of judges for a factbase')
38
37
 
39
- version '0.0.10'
38
+ version('0.0.12')
40
39
 
41
- desc 'Make it more verbose, logging as much as possible'
42
- switch([:v, :verbose])
40
+ synopsis_format(:full)
43
41
 
44
- pre do |global, command, options, args|
45
- if global[:verbose]
46
- loog = Loog::VERBOSE
47
- end
48
- true
42
+ subcommand_option_handling(:normal)
43
+
44
+ desc 'Make it more verbose, logging as much as possible'
45
+ switch([:v, :verbose])
46
+
47
+ pre do |global, command, options, args|
48
+ if global[:verbose]
49
+ loog = Loog::VERBOSE
50
+ end
51
+ true
52
+ end
53
+
54
+ desc 'Update the factbase by passing all judges one by one'
55
+ command :update do |c|
56
+ c.desc 'Options to pass to every judge'
57
+ c.flag([:o, :option], default_value: 'yes', type: Array, arg_name: '<key=value>')
58
+ c.action do |global, options, args|
59
+ require_relative '../lib/judges/commands/update'
60
+ Judges::Update.new(loog).run(options, args)
49
61
  end
62
+ end
50
63
 
51
- desc 'Update the factbase by passing all judges one by one'
52
- command :update do |c|
53
- c.desc 'Environment variable to pass to every judge'
54
- c.flag([:e, :environment], default_value: 'yes')
55
- c.action do |global, options, args|
56
- require_relative '../lib/judges/commands/update'
57
- Judges::Update.new(loog).run(options, args)
58
- end
64
+ desc 'Join two factbases'
65
+ command :print do |c|
66
+ c.action do |global, options, args|
67
+ require_relative '../lib/judges/commands/join'
68
+ Judges::Join.new(loog).run(options, args)
59
69
  end
70
+ end
60
71
 
61
- desc 'Print the factbase into a human-readable format (YAML, JSON, etc.)'
62
- command :print do |c|
63
- c.desc 'Output format (xml, json, or yaml)'
64
- c.flag([:format], default_value: 'yaml')
65
- c.switch([:auto])
66
- c.action do |global, options, args|
67
- require_relative '../lib/judges/commands/print'
68
- Judges::Print.new(loog).run(options, args)
69
- end
72
+ desc 'Print the factbase into a human-readable format (YAML, JSON, etc.)'
73
+ command :print do |c|
74
+ c.desc 'Output format (xml, json, or yaml)'
75
+ c.flag([:format], default_value: 'yaml')
76
+ c.switch([:auto])
77
+ c.action do |global, options, args|
78
+ require_relative '../lib/judges/commands/print'
79
+ Judges::Print.new(loog).run(options, args)
70
80
  end
81
+ end
71
82
 
72
- desc 'Run automated tests for all judges'
73
- command :test do |c|
74
- c.action do |global, options, args|
75
- require_relative '../lib/judges/commands/test'
76
- Judges::Test.new(loog).run(options, args)
77
- end
83
+ desc 'Run automated tests for all judges'
84
+ command :test do |c|
85
+ c.action do |global, options, args|
86
+ require_relative '../lib/judges/commands/test'
87
+ Judges::Test.new(loog).run(options, args)
78
88
  end
79
89
  end
80
90
 
81
- exit App.run(ARGV)
91
+ exit run(ARGV)
data/features/cli.feature CHANGED
@@ -24,8 +24,8 @@ Feature: Simple Run
24
24
  And Exit code is zero
25
25
 
26
26
  Scenario: Simple test of a few judges
27
- Given I run bin/judges with "update ./fixtures temp/simple.fb"
28
- Then Stdout contains "judges processed"
27
+ Given I run bin/judges with "test ./fixtures"
28
+ Then Stdout contains "judges tested"
29
29
  And Exit code is zero
30
30
 
31
31
  Scenario: Simple print of a small factbase
@@ -47,7 +47,7 @@ end
47
47
 
48
48
  When(%r{^I run bin/judges with "([^"]*)"$}) do |arg|
49
49
  home = File.join(File.dirname(__FILE__), '../..')
50
- @stdout = `ruby -I#{home}/lib #{home}/bin/judges #{arg}`
50
+ @stdout = `GLI_DEBUG=true ruby -I#{home}/lib #{home}/bin/judges #{arg}`
51
51
  @exitstatus = $CHILD_STATUS.exitstatus
52
52
  end
53
53
 
@@ -25,7 +25,7 @@ input:
25
25
  time: 2024-01-01T03:15:45
26
26
  seen:
27
27
  - one
28
- - this judge
28
+ - reward_for_good_bug
29
29
  - two
30
30
  expected:
31
31
  - /fb[count(f)=1]
@@ -20,7 +20,10 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
22
 
23
- $fb.query("(and (eq kind 'bug was accepted') (not (eq seen 'this judge')))").each do |f|
23
+ $loog.info("Trying to reward a good reported bug (judge=#{$judge})...")
24
+
25
+ once($fb).query("(eq kind 'bug was accepted')").each do |f|
26
+ $loog.info('Good candidate found!')
24
27
  n = $fb.insert
25
28
  n.kind = 'nominate for good bug'
26
29
  n.payee = f.reporter
data/judges.gemspec CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
26
26
  s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
27
27
  s.required_ruby_version = '>=3.2'
28
28
  s.name = 'judges'
29
- s.version = '0.0.10'
29
+ s.version = '0.0.12'
30
30
  s.license = 'MIT'
31
31
  s.summary = 'Command-Line Tool for a Factbase'
32
32
  s.description = '
@@ -41,8 +41,8 @@ Gem::Specification.new do |s|
41
41
  s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
42
42
  s.rdoc_options = ['--charset=UTF-8']
43
43
  s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
44
- s.add_runtime_dependency 'backtrace', '~>0.3'
45
- s.add_runtime_dependency 'factbase', '~>0.0'
44
+ s.add_runtime_dependency 'backtrace', '~> 0.3'
45
+ s.add_runtime_dependency 'factbase', '~>0.0.12'
46
46
  s.add_runtime_dependency 'gli', '~>2.21'
47
47
  s.add_runtime_dependency 'loog', '~>0.2'
48
48
  s.add_runtime_dependency 'nokogiri', '~> 1.10'
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'factbase'
24
+ require 'fileutils'
25
+ require_relative '../../judges'
26
+ require_relative '../../judges/to_rel'
27
+ require_relative '../../judges/packs'
28
+ require_relative '../../judges/options'
29
+
30
+ # Join.
31
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
32
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
33
+ # License:: MIT
34
+ class Judges::Join
35
+ def initialize(loog)
36
+ @loog = loog
37
+ end
38
+
39
+ def run(_opts, args)
40
+ raise 'Exactly two arguments required' unless args.size == 2
41
+ master = args[0]
42
+ raise "The master factbase is absent: #{master.to_rel}" unless File.exist?(master)
43
+ slave = args[1]
44
+ raise "The slave factbase is absent: #{slave.to_rel}" unless File.exist?(slave)
45
+ fb = Factbase.new
46
+ fb.import(File.read(master))
47
+ @loog.info("Master factbase imported from #{master.to_rel} (#{File.size(master)} bytes)")
48
+ fb.import(File.read(slave))
49
+ @loog.info("Slave factbase imported from #{slave.to_rel} (#{File.size(slave)} bytes)")
50
+ File.write(master, fb.export)
51
+ @loog.info("Master factbase exported to #{master.to_rel} (#{File.size(master)} bytes)")
52
+ end
53
+ end
@@ -23,6 +23,7 @@
23
23
  require 'fileutils'
24
24
  require 'factbase'
25
25
  require_relative '../../judges'
26
+ require_relative '../../judges/to_rel'
26
27
  require_relative '../../judges/packs'
27
28
 
28
29
  # Update.
@@ -37,7 +38,7 @@ class Judges::Print
37
38
  def run(opts, args)
38
39
  raise 'At lease one argument required' if args.empty?
39
40
  f = args[0]
40
- raise "The file is absent: #{f}" unless File.exist?(f)
41
+ raise "The file is absent: #{f.to_rel}" unless File.exist?(f)
41
42
  o = args[1]
42
43
  if o.nil?
43
44
  raise 'Either provide output file name or use --auto' unless opts[:auto]
@@ -46,7 +47,7 @@ class Judges::Print
46
47
  end
47
48
  fb = Factbase.new
48
49
  fb.import(File.read(f))
49
- @loog.info("Factbase imported from #{f} (#{File.size(f)} bytes)")
50
+ @loog.info("Factbase imported from #{f.to_rel} (#{File.size(f)} bytes)")
50
51
  FileUtils.mkdir_p(File.dirname(o))
51
52
  output =
52
53
  case opts[:format].downcase
@@ -58,6 +59,6 @@ class Judges::Print
58
59
  fb.to_xml
59
60
  end
60
61
  File.write(o, output)
61
- @loog.info("Factbase printed to #{o} (#{File.size(o)} bytes)")
62
+ @loog.info("Factbase printed to #{o.to_rel} (#{File.size(o)} bytes)")
62
63
  end
63
64
  end
@@ -20,10 +20,13 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
22
 
23
- require 'factbase'
24
23
  require 'nokogiri'
24
+ require 'factbase'
25
+ require 'backtrace'
25
26
  require_relative '../../judges'
27
+ require_relative '../../judges/to_rel'
26
28
  require_relative '../../judges/packs'
29
+ require_relative '../../judges/options'
27
30
 
28
31
  # Test.
29
32
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -37,13 +40,27 @@ class Judges::Test
37
40
  def run(_opts, args)
38
41
  raise 'Exactly one argument required' unless args.size == 1
39
42
  dir = args[0]
40
- done = Judges::Packs.new(dir).each_with_index do |p, i|
41
- p.tests.each do |t|
42
- test_one(p, t)
43
+ @loog.info("Testing judges in '#{dir.to_rel}'...")
44
+ errors = []
45
+ done = Judges::Packs.new(dir, @loog).each_with_index do |p, i|
46
+ @loog.info("\n👉 Testing '#{p.script}' (##{i}) in '#{p.dir.to_rel}'...")
47
+ p.tests.each do |f|
48
+ yaml = YAML.load_file(f, permitted_classes: [Time])
49
+ @loog.info("Testing '#{f.to_rel}':")
50
+ begin
51
+ test_one(p, yaml)
52
+ rescue StandardError => e
53
+ @loog.warn(Backtrace.new(e))
54
+ errors << f
55
+ end
43
56
  end
44
- @loog.info("Pack ##{i} found in #{p.dir}")
45
57
  end
46
- @loog.info("#{done} judges tested")
58
+ if errors.empty?
59
+ @loog.info("\nAll #{done} judges tested successfully")
60
+ else
61
+ @loog.info("\n#{done} judges tested, #{errors.size} of them failed")
62
+ raise "#{errors.size} tests failed" unless errors.empty?
63
+ end
47
64
  end
48
65
 
49
66
  private
@@ -55,17 +72,17 @@ class Judges::Test
55
72
  i.each do |k, vv|
56
73
  if vv.is_a?(Array)
57
74
  vv.each do |v|
58
- send(f, "#{k}=", v)
75
+ f.send("#{k}=", v)
59
76
  end
60
77
  else
61
- f.send('foo=', 42)
78
+ f.send("#{k}=", vv)
62
79
  end
63
80
  end
64
81
  end
65
- pack.run(fb, {})
82
+ pack.run(fb, Judges::Options.new(yaml['options']))
66
83
  xml = Nokogiri::XML.parse(fb.to_xml)
67
84
  yaml['expected'].each do |xp|
68
- raise "#{pack.script} with '#{xp}' doesn't match:\n#{xml}" if xml.xpath(xp).empty?
85
+ raise "#{pack.script} doesn't match '#{xp}':\n#{xml}" if xml.xpath(xp).empty?
69
86
  end
70
87
  end
71
88
  end
@@ -22,8 +22,11 @@
22
22
 
23
23
  require 'factbase'
24
24
  require 'fileutils'
25
+ require 'backtrace'
25
26
  require_relative '../../judges'
27
+ require_relative '../../judges/to_rel'
26
28
  require_relative '../../judges/packs'
29
+ require_relative '../../judges/options'
27
30
 
28
31
  # Update.
29
32
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -34,7 +37,7 @@ class Judges::Update
34
37
  @loog = loog
35
38
  end
36
39
 
37
- def run(_opts, args)
40
+ def run(opts, args)
38
41
  raise 'Exactly two arguments required' unless args.size == 2
39
42
  dir = args[0]
40
43
  raise "The directory is absent: #{dir}" unless File.exist?(dir)
@@ -42,17 +45,26 @@ class Judges::Update
42
45
  fb = Factbase.new
43
46
  if File.exist?(file)
44
47
  fb.import(File.read(file))
45
- @loog.info("Factbase imported from #{file} (#{File.size(file)} bytes)")
48
+ @loog.info("Factbase imported from '#{file.to_rel}' (#{File.size(file)} bytes)")
46
49
  else
47
- @loog.info("There is no Factbase to import from #{file}")
50
+ @loog.info("There is no Factbase to import from '#{file.to_rel}' (file is absent)")
48
51
  end
49
- done = Judges::Packs.new(dir).each_with_index do |p, i|
50
- @loog.info("Pack ##{i} found in #{p.dir}")
51
- p.run(fb, {})
52
+ options = Judges::Options.new(opts['options'])
53
+ @loog.debug("The following options provided:\n\t#{options.to_s.gsub("\n", "\n\t")}")
54
+ errors = []
55
+ done = Judges::Packs.new(dir, @loog).each_with_index do |p, i|
56
+ @loog.info("Pack ##{i} found in #{p.dir.to_rel}")
57
+ begin
58
+ p.run(fb, options)
59
+ rescue StandardError => e
60
+ @loog.warn(Backtrace.new(e))
61
+ errors << p.script
62
+ end
52
63
  end
53
- @loog.info("#{done} judges processed")
64
+ @loog.info("#{done} judges processed (#{errors.size} errors)")
54
65
  FileUtils.mkdir_p(File.dirname(file))
55
66
  File.write(file, fb.export)
56
- @loog.info("Factbase exported to #{file} (#{File.size(file)} bytes)")
67
+ @loog.info("Factbase exported to '#{file.to_rel}' (#{File.size(file)} bytes)")
68
+ raise "Failed to update correctly (#{errors.size} errors)" unless errors.empty?
57
69
  end
58
70
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ # Returns a decorated global factbase, which only touches facts once
24
+ def once(fb, judge: $judge)
25
+ Factbase::Once.new(fb, judge)
26
+ end
27
+
28
+ # Runs only once.
29
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
31
+ # License:: MIT
32
+ class Factbase::Once
33
+ def initialize(fb, func)
34
+ @fb = fb
35
+ @func = func
36
+ end
37
+
38
+ def query(expr)
39
+ expr = "(and #{expr} (not (eq seen '#{@func}')))"
40
+ After.new(@fb.query(expr), @func)
41
+ end
42
+
43
+ def insert
44
+ @fb.insert
45
+ end
46
+
47
+ # What happens after a fact is processed.
48
+ class After
49
+ def initialize(query, func)
50
+ @query = query
51
+ @func = func
52
+ end
53
+
54
+ def each
55
+ @query.each do |f|
56
+ yield f
57
+ f.seen = @func
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require_relative '../judges'
24
+
25
+ # Options for Ruby scripts in the judges.
26
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
27
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
28
+ # License:: MIT
29
+ class Judges::Options
30
+ # Ctor.
31
+ # @param pairs [Array<String>] List of pairs, like ["token=af73cd3", "max_speed=1"]
32
+ def initialize(pairs)
33
+ @pairs = pairs
34
+ end
35
+
36
+ # Convert them all to a string (printable in a log).
37
+ def to_s
38
+ touch # this will trigger method_missing() method, which will create @hash
39
+ @hash.map do |k, v|
40
+ v = v.to_s
41
+ v = "#{v[0..3]}#{'*' * (v.length - 4)}" if v.length > 8
42
+ "#{k}=#{v}"
43
+ end.join("\n")
44
+ end
45
+
46
+ # Get option by name.
47
+ def method_missing(*args)
48
+ @hash ||= begin
49
+ pp = @pairs || []
50
+ pp = @pairs.map { |k, v| "#{k}=#{v}" } if pp.is_a?(Hash)
51
+ pp.to_h do |pair|
52
+ p = pair.split('=', 2)
53
+ [p[0].to_sym, p[1].match?(/^[0-9]+$/) ? p[1].to_i : p[1]]
54
+ end
55
+ end
56
+ k = args[0].downcase
57
+ @hash[k]
58
+ end
59
+
60
+ # rubocop:disable Style/OptionalBooleanParameter
61
+ def respond_to?(_method, _include_private = false)
62
+ # rubocop:enable Style/OptionalBooleanParameter
63
+ true
64
+ end
65
+
66
+ def respond_to_missing?(_method, _include_private = false)
67
+ true
68
+ end
69
+ end
data/lib/judges/pack.rb CHANGED
@@ -21,7 +21,9 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require 'yaml'
24
+ require 'time'
24
25
  require_relative '../judges'
26
+ require_relative '../judges/fb/once'
25
27
 
26
28
  # A single pack.
27
29
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -30,21 +32,24 @@ require_relative '../judges'
30
32
  class Judges::Pack
31
33
  attr_reader :dir
32
34
 
33
- def initialize(dir)
35
+ def initialize(dir, loog)
34
36
  @dir = dir
37
+ @loog = loog
35
38
  end
36
39
 
37
40
  # Run it with the given Factbase and environment variables.
38
- def run(fbase, env)
41
+ def run(fbase, options)
39
42
  $fb = fbase
40
- env.each do |k, v|
41
- # rubocop:disable Security/Eval
42
- eval("$#{k} = '#{v}'", binding, __FILE__, __LINE__) # $foo = 42
43
- # rubocop:enable Security/Eval
44
- end
43
+ $judge = File.basename(@dir)
44
+ $options = options
45
+ $loog = @loog
45
46
  s = File.join(@dir, script)
46
47
  raise "Can't load '#{s}'" unless File.exist?(s)
47
- load s
48
+ begin
49
+ load(s, true)
50
+ ensure
51
+ $fb = $judge = $options = $loog = nil
52
+ end
48
53
  end
49
54
 
50
55
  # Get the name of the .rb script in the pack.
@@ -52,10 +57,8 @@ class Judges::Pack
52
57
  File.basename(Dir.glob(File.join(@dir, '*.rb')).first)
53
58
  end
54
59
 
55
- # Iterate over .yml tests.
60
+ # Return all .yml tests files.
56
61
  def tests
57
- Dir.glob(File.join(@dir, '*.yml')).map do |f|
58
- YAML.load_file(f)
59
- end
62
+ Dir.glob(File.join(@dir, '*.yml'))
60
63
  end
61
64
  end
data/lib/judges/packs.rb CHANGED
@@ -28,8 +28,9 @@ require_relative 'pack'
28
28
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
29
  # License:: MIT
30
30
  class Judges::Packs
31
- def initialize(dir)
31
+ def initialize(dir, loog)
32
32
  @dir = dir
33
+ @loog = loog
33
34
  end
34
35
 
35
36
  # Iterate over them all.
@@ -37,7 +38,7 @@ class Judges::Packs
37
38
  def each
38
39
  Dir.glob(File.join(@dir, '**/*.rb')).each do |f|
39
40
  d = File.dirname(File.absolute_path(f))
40
- yield Judges::Pack.new(d)
41
+ yield Judges::Pack.new(d, @loog)
41
42
  end
42
43
  end
43
44
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright (c) 2024 Yegor Bugayenko
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -17,26 +19,22 @@
17
19
  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
21
  # SOFTWARE.
20
- ---
21
- name: zerocracy
22
- on:
23
- schedule:
24
- - cron: '0,10,20,30,50,50 * * * *'
25
- concurrency:
26
- group: zerocracy
27
- cancel-in-progress: true
28
- jobs:
29
- yamllint:
30
- runs-on: ubuntu-22.04
31
- steps:
32
- - uses: actions/checkout@v4
33
- - uses: zerocracy/judges-action@master
34
- with:
35
- factbase: recent.fb
36
- - run: mkdir gh-pages && cp recent.yml gh-pages
37
- - uses: JamesIves/github-pages-deploy-action@v4.6.0
38
- with:
39
- branch: gh-pages
40
- folder: gh-pages/recent.yml
41
- factbase: recent.fb
42
- clean: true
22
+
23
+ require 'pathname'
24
+
25
+ # Adding method +to_rel+ to all Ruby objects.
26
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
27
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
28
+ # License:: MIT
29
+ class Object
30
+ # Generates a relative name of a file (to the current dir).
31
+ def to_rel
32
+ s = File.absolute_path(to_s)
33
+ p = Pathname.new(s).relative_path_from(Dir.getwd)
34
+ if p.directory?
35
+ "#{p}/"
36
+ else
37
+ p.to_s
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+ require 'loog'
25
+ require 'nokogiri'
26
+ require_relative '../../lib/judges'
27
+ require_relative '../../lib/judges/commands/join'
28
+
29
+ # Test.
30
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
31
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
32
+ # License:: MIT
33
+ class TestJoin < Minitest::Test
34
+ def test_simple_join
35
+ Dir.mktmpdir do |d|
36
+ master = File.join(d, 'master.fb')
37
+ fb1 = Factbase.new
38
+ fb1.insert.zz = 5
39
+ File.write(master, fb1.export)
40
+ slave = File.join(d, 'slave.fb')
41
+ fb2 = Factbase.new
42
+ fb2.insert.foo_bar = 42
43
+ File.write(slave, fb2.export)
44
+ Judges::Join.new(Loog::VERBOSE).run({}, [master, slave])
45
+ fb = Factbase.new
46
+ fb.import(File.read(master))
47
+ xml = Nokogiri::XML.parse(fb.to_xml)
48
+ assert(!xml.xpath('/fb/f[zz="5"]').empty?, fb.to_xml)
49
+ assert(!xml.xpath('/fb/f[foo_bar="42"]').empty?, fb.to_xml)
50
+ end
51
+ end
52
+ end
@@ -69,4 +69,22 @@ class TestTest < Minitest::Test
69
69
  end
70
70
  end
71
71
  end
72
+
73
+ def test_with_options
74
+ Dir.mktmpdir do |d|
75
+ File.write(File.join(d, 'foo.rb'), '$fb.insert.foo = $options.bar')
76
+ File.write(
77
+ File.join(d, 'something.yml'),
78
+ <<-YAML
79
+ input: []
80
+ options:
81
+ bar: 42
82
+ expected:
83
+ - /fb[count(f)=1]
84
+ - /fb/f[foo='42']
85
+ YAML
86
+ )
87
+ Judges::Test.new(Loog::VERBOSE).run(nil, [d])
88
+ end
89
+ end
72
90
  end
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'minitest/autorun'
24
24
  require 'loog'
25
+ require 'nokogiri'
25
26
  require_relative '../../lib/judges'
26
27
  require_relative '../../lib/judges/commands/update'
27
28
 
@@ -30,12 +31,31 @@ require_relative '../../lib/judges/commands/update'
30
31
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
31
32
  # License:: MIT
32
33
  class TestUpdate < Minitest::Test
33
- def test_simple_update
34
+ def test_build_factbase_from_scratch
34
35
  Dir.mktmpdir do |d|
35
- File.write(File.join(d, 'foo.rb'), '$fb.query("(eq foo 42)").each { |f| f.bar = 4 }')
36
- fb = File.join(d, 'base.fb')
37
- Judges::Update.new(Loog::VERBOSE).run(nil, [d, fb])
38
- assert(File.exist?(fb))
36
+ File.write(File.join(d, 'foo.rb'), '$fb.insert.zzz = $options.foo_bar + 1')
37
+ file = File.join(d, 'base.fb')
38
+ Judges::Update.new(Loog::VERBOSE).run({ 'options' => ['foo_bar=42'] }, [d, file])
39
+ fb = Factbase.new
40
+ fb.import(File.read(file))
41
+ xml = Nokogiri::XML.parse(fb.to_xml)
42
+ assert(!xml.xpath('/fb/f[zzz="43"]').empty?)
43
+ end
44
+ end
45
+
46
+ def test_extend_existing_factbase
47
+ Dir.mktmpdir do |d|
48
+ file = File.join(d, 'base.fb')
49
+ fb = Factbase.new
50
+ fb.insert.foo_bar = 42
51
+ File.write(file, fb.export)
52
+ File.write(File.join(d, 'foo.rb'), '$fb.insert.tt = 4')
53
+ Judges::Update.new(Loog::VERBOSE).run({}, [d, file])
54
+ fb = Factbase.new
55
+ fb.import(File.read(file))
56
+ xml = Nokogiri::XML.parse(fb.to_xml)
57
+ assert(!xml.xpath('/fb/f[tt="4"]').empty?)
58
+ assert(!xml.xpath('/fb/f[foo_bar="42"]').empty?)
39
59
  end
40
60
  end
41
61
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+ require 'tmpdir'
25
+ require 'factbase'
26
+ require_relative '../../lib/judges'
27
+ require_relative '../../lib/judges/fb/once'
28
+
29
+ # Test.
30
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
31
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
32
+ # License:: MIT
33
+ class TestOnce < Minitest::Test
34
+ def test_touch_once
35
+ fb = once(Factbase.new, judge: 'something')
36
+ fb.insert
37
+ fb.query('()').each { |f| f.foo = 42 }
38
+ assert(fb.query('()').extend(Enumerable).to_a.empty?)
39
+ end
40
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+ require_relative '../lib/judges'
25
+ require_relative '../lib/judges/options'
26
+
27
+ # Test.
28
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
+ # License:: MIT
31
+ class TestOptions < Minitest::Test
32
+ def test_basic
33
+ opts = Judges::Options.new(['token=a77', 'max=42'])
34
+ assert_equal('a77', opts.token)
35
+ assert_equal(42, opts.max)
36
+ end
37
+
38
+ def test_with_nil
39
+ opts = Judges::Options.new(nil)
40
+ assert(opts.foo.nil?)
41
+ end
42
+
43
+ def test_with_hash
44
+ opts = Judges::Options.new('foo' => 42, 'bar' => 'hello')
45
+ assert_equal(42, opts.foo)
46
+ assert_equal('hello', opts.bar)
47
+ assert(opts.xxx.nil?)
48
+ end
49
+
50
+ def test_converts_to_string
51
+ opts = Judges::Options.new('foo' => 44, 'bar' => 'long-string-maybe-secret')
52
+ assert_equal("foo=44\nbar=long********************", opts.to_s)
53
+ end
54
+ end
data/test/test_pack.rb CHANGED
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'minitest/autorun'
24
24
  require 'tmpdir'
25
+ require 'loog'
25
26
  require 'factbase'
26
27
  require_relative '../lib/judges'
27
28
  require_relative '../lib/judges/pack'
@@ -34,7 +35,7 @@ class TestPack < Minitest::Test
34
35
  def test_basic_run
35
36
  Dir.mktmpdir do |d|
36
37
  File.write(File.join(d, 'foo.rb'), '$fb.insert')
37
- pack = Judges::Pack.new(d)
38
+ pack = Judges::Pack.new(d, Loog::VERBOSE)
38
39
  fb = Factbase.new
39
40
  pack.run(fb, {})
40
41
  assert_equal(1, fb.size)
@@ -44,7 +45,7 @@ class TestPack < Minitest::Test
44
45
  def test_run_isolated
45
46
  Dir.mktmpdir do |d|
46
47
  File.write(File.join(d, 'bar.rb'), '$fb.insert')
47
- pack = Judges::Pack.new(d)
48
+ pack = Judges::Pack.new(d, Loog::VERBOSE)
48
49
  fb1 = Factbase.new
49
50
  pack.run(fb1, {})
50
51
  assert_equal(1, fb1.size)
@@ -53,4 +54,24 @@ class TestPack < Minitest::Test
53
54
  assert_equal(1, fb2.size)
54
55
  end
55
56
  end
57
+
58
+ def test_with_supplemenary_functions
59
+ Dir.mktmpdir do |d|
60
+ File.write(File.join(d, 'x.rb'), 'once($fb).insert')
61
+ pack = Judges::Pack.new(d, Loog::VERBOSE)
62
+ pack.run(Factbase.new, {})
63
+ end
64
+ end
65
+
66
+ def test_sets_judge_value_correctly
67
+ Dir.mktmpdir do |d|
68
+ j = 'this_is_it'
69
+ dir = File.join(d, j)
70
+ FileUtils.mkdir(dir)
71
+ File.write(File.join(dir, 'foo.rb'), '$loog.info("judge=" + $judge)')
72
+ log = Loog::Buffer.new
73
+ Judges::Pack.new(dir, log).run(Factbase.new, {})
74
+ assert(log.to_s.include?("judge=#{j}"))
75
+ end
76
+ end
56
77
  end
data/test/test_packs.rb CHANGED
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'minitest/autorun'
24
24
  require 'tmpdir'
25
+ require 'loog'
25
26
  require_relative '../lib/judges'
26
27
  require_relative '../lib/judges/packs'
27
28
 
@@ -35,10 +36,10 @@ class TestPacks < Minitest::Test
35
36
  File.write(File.join(d, 'foo.rb'), 'hey')
36
37
  File.write(File.join(d, 'something.yml'), "---\nfoo: 42")
37
38
  found = 0
38
- Judges::Packs.new(d).each do |p|
39
+ Judges::Packs.new(d, Loog::VERBOSE).each do |p|
39
40
  assert_equal('foo.rb', p.script)
40
41
  found += 1
41
- assert_equal(42, p.tests.first['foo'])
42
+ assert_equal('something.yml', File.basename(p.tests.first))
42
43
  end
43
44
  assert_equal(1, found)
44
45
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+ require_relative '../lib/judges'
25
+ require_relative '../lib/judges/to_rel'
26
+
27
+ # Test.
28
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
+ # License:: MIT
31
+ class TestToRel < Minitest::Test
32
+ def test_simple_mapping
33
+ n = File.absolute_path(File.join('.', 'lib/../lib/commands/update.rb'))
34
+ assert_equal('lib/commands/update.rb', n.to_rel)
35
+ end
36
+
37
+ def test_maps_dir_name
38
+ n = File.absolute_path(File.join('.', 'lib/../lib/judges/commands'))
39
+ assert_equal('lib/judges/commands/', n.to_rel)
40
+ end
41
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: judges
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.12
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-12 00:00:00.000000000 Z
11
+ date: 2024-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: backtrace
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.0'
33
+ version: 0.0.12
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.0'
40
+ version: 0.0.12
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: gli
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -101,7 +101,6 @@ files:
101
101
  - ".github/workflows/rake.yml"
102
102
  - ".github/workflows/xcop.yml"
103
103
  - ".github/workflows/yamllint.yml"
104
- - ".github/workflows/zerocracy.yaml"
105
104
  - ".gitignore"
106
105
  - ".pdd"
107
106
  - ".rubocop.yml"
@@ -122,19 +121,27 @@ files:
122
121
  - fixtures/reward_for_good_bug/simple-reward.yml
123
122
  - judges.gemspec
124
123
  - lib/judges.rb
124
+ - lib/judges/commands/join.rb
125
125
  - lib/judges/commands/print.rb
126
126
  - lib/judges/commands/test.rb
127
127
  - lib/judges/commands/update.rb
128
+ - lib/judges/fb/once.rb
129
+ - lib/judges/options.rb
128
130
  - lib/judges/pack.rb
129
131
  - lib/judges/packs.rb
132
+ - lib/judges/to_rel.rb
130
133
  - renovate.json
134
+ - test/commands/test_join.rb
131
135
  - test/commands/test_print.rb
132
136
  - test/commands/test_test.rb
133
137
  - test/commands/test_update.rb
138
+ - test/fb/test_once.rb
134
139
  - test/test__helper.rb
135
140
  - test/test_judges.rb
141
+ - test/test_options.rb
136
142
  - test/test_pack.rb
137
143
  - test/test_packs.rb
144
+ - test/test_to_rel.rb
138
145
  homepage: http://github.com/yegor256/judges
139
146
  licenses:
140
147
  - MIT