sciolyff 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/sciolyff +7 -10
- data/lib/sciolyff/interpreter/event.rb +20 -8
- data/lib/sciolyff/interpreter/html/helpers.rb +130 -0
- data/lib/sciolyff/interpreter/html/template.html.erb +558 -0
- data/lib/sciolyff/interpreter/html.rb +47 -0
- data/lib/sciolyff/interpreter/placing.rb +31 -6
- data/lib/sciolyff/interpreter/raw.rb +50 -0
- data/lib/sciolyff/interpreter/subdivisions.rb +41 -0
- data/lib/sciolyff/interpreter/team.rb +16 -3
- data/lib/sciolyff/interpreter/tiebreaks.rb +34 -0
- data/lib/sciolyff/interpreter/tournament.rb +28 -5
- data/lib/sciolyff/interpreter.rb +34 -44
- data/lib/sciolyff/validator/checker.rb +20 -0
- data/lib/sciolyff/validator/events.rb +96 -0
- data/lib/sciolyff/validator/logger.rb +47 -0
- data/lib/sciolyff/validator/penalties.rb +19 -0
- data/lib/sciolyff/validator/placings.rb +120 -0
- data/lib/sciolyff/validator/raws.rb +19 -0
- data/lib/sciolyff/validator/sections.rb +32 -0
- data/lib/sciolyff/validator/teams.rb +81 -0
- data/lib/sciolyff/validator/top_level.rb +22 -0
- data/lib/sciolyff/validator/tournament.rb +72 -0
- data/lib/sciolyff/validator.rb +88 -0
- data/lib/sciolyff.rb +2 -45
- metadata +20 -25
- data/lib/sciolyff/events.rb +0 -66
- data/lib/sciolyff/penalties.rb +0 -52
- data/lib/sciolyff/placings.rb +0 -198
- data/lib/sciolyff/scores.rb +0 -148
- data/lib/sciolyff/sections.rb +0 -40
- data/lib/sciolyff/teams.rb +0 -96
- data/lib/sciolyff/top_level.rb +0 -13
- data/lib/sciolyff/tournament.rb +0 -94
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sciolyff/validator/checker'
|
4
|
+
require 'sciolyff/validator/sections'
|
5
|
+
|
6
|
+
module SciolyFF
|
7
|
+
# Checks for one placing in the Placings section of a SciolyFF file
|
8
|
+
class Validator::Placings < Validator::Checker
|
9
|
+
include Validator::Sections
|
10
|
+
|
11
|
+
REQUIRED = {
|
12
|
+
event: String,
|
13
|
+
team: Integer
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
OPTIONAL = {
|
17
|
+
place: Integer,
|
18
|
+
participated: [true, false],
|
19
|
+
disqualified: [true, false],
|
20
|
+
exempt: [true, false],
|
21
|
+
tie: [true, false],
|
22
|
+
unknown: [true, false],
|
23
|
+
raw: Hash
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
def initialize(rep)
|
27
|
+
@events_by_name = rep[:Events].group_by { |e| e[:name] }
|
28
|
+
.transform_values(&:first)
|
29
|
+
@teams_by_number = rep[:Teams].group_by { |t| t[:number] }
|
30
|
+
.transform_values(&:first)
|
31
|
+
@event_names = @events_by_name.keys
|
32
|
+
@team_numbers = @teams_by_number.keys
|
33
|
+
@placings = rep[:Placings]
|
34
|
+
@maximum_place = rep[:Tournament][:'maximum place']
|
35
|
+
end
|
36
|
+
|
37
|
+
def matching_event?(placing, logger)
|
38
|
+
return true if @event_names.include? placing[:event]
|
39
|
+
|
40
|
+
logger.error "'event: #{placing[:event]}' in Placings "\
|
41
|
+
'does not match any event name in Events'
|
42
|
+
end
|
43
|
+
|
44
|
+
def matching_team?(placing, logger)
|
45
|
+
return true if @team_numbers.include? placing[:team]
|
46
|
+
|
47
|
+
logger.error "'team: #{placing[:team]}' in Placings "\
|
48
|
+
'does not match any team number in Teams'
|
49
|
+
end
|
50
|
+
|
51
|
+
def unique_event_and_team?(placing, logger)
|
52
|
+
return true if @placings.count do |other|
|
53
|
+
placing[:event] == other[:event] && placing[:team] == other[:team]
|
54
|
+
end == 1
|
55
|
+
|
56
|
+
logger.error "duplicate #{placing_log(placing)}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def having_a_place_makes_sense?(placing, logger)
|
60
|
+
return true unless placing[:place] &&
|
61
|
+
(placing[:participated] == false ||
|
62
|
+
placing[:disqualified] ||
|
63
|
+
placing[:unknown] ||
|
64
|
+
placing[:raw])
|
65
|
+
|
66
|
+
logger.error 'having a place does not make sense for '\
|
67
|
+
"#{placing_log(placing)}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def having_a_raw_makes_sense?(placing, logger)
|
71
|
+
return true unless placing[:raw] &&
|
72
|
+
(placing[:participated] == false ||
|
73
|
+
placing[:disqualified] ||
|
74
|
+
placing[:unknown] ||
|
75
|
+
placing[:place])
|
76
|
+
|
77
|
+
logger.error 'having raw section does not make sense for '\
|
78
|
+
"#{placing_log(placing)}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def possible_participated_disqualified_combination?(placing, logger)
|
82
|
+
return true unless placing[:participated] == false &&
|
83
|
+
placing[:disqualified]
|
84
|
+
|
85
|
+
logger.error 'impossible participation-disqualified combination for '\
|
86
|
+
"#{placing_log(placing)}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def possible_unknown_disqualified_combination?(placing, logger)
|
90
|
+
return true unless placing[:unknown] && placing[:disqualified]
|
91
|
+
|
92
|
+
logger.error 'impossible unknown-disqualified combination for '\
|
93
|
+
"#{placing_log(placing)}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def unknown_allowed?(placing, logger)
|
97
|
+
event = @events_by_name[placing[:event]]
|
98
|
+
team = @teams_by_number[placing[:team]]
|
99
|
+
return true unless invalid_unknown?(placing, event, team)
|
100
|
+
|
101
|
+
logger.error "unknown place not allowed for #{placing_log(placing)} "\
|
102
|
+
'(either placing must be exempt or event must be trial/trialed)'
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def placing_log(placing)
|
108
|
+
"placing with 'team: #{placing[:team]}' and 'event: #{placing[:event]}'"
|
109
|
+
end
|
110
|
+
|
111
|
+
def invalid_unknown?(placing, event, team)
|
112
|
+
placing[:unknown] &&
|
113
|
+
@maximum_place.nil? &&
|
114
|
+
!placing[:exempt] &&
|
115
|
+
!event[:trial] &&
|
116
|
+
!event[:trialed] &&
|
117
|
+
!team[:exhibition]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sciolyff/validator/checker'
|
4
|
+
require 'sciolyff/validator/sections'
|
5
|
+
|
6
|
+
module SciolyFF
|
7
|
+
# Checks for one raw of a placing in the Placings section of a SciolyFF file
|
8
|
+
class Validator::Raws < Validator::Checker
|
9
|
+
include Validator::Sections
|
10
|
+
|
11
|
+
REQUIRED = {
|
12
|
+
score: Float
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
OPTIONAL = {
|
16
|
+
'tiebreaker rank': Integer
|
17
|
+
}.freeze
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SciolyFF
|
4
|
+
# Generic tests for (sub-)sections and types. Including classes must have two
|
5
|
+
# hashes REQUIRED and OPTIONAL (see other files in this dir for examples)
|
6
|
+
module Validator::Sections
|
7
|
+
def all_required_sections?(rep, logger)
|
8
|
+
missing = self.class::REQUIRED.keys - rep.keys
|
9
|
+
return true if missing.empty?
|
10
|
+
|
11
|
+
logger.error "missing required sections: #{missing.join ', '}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def no_extra_sections?(rep, logger)
|
15
|
+
extra = rep.keys - (self.class::REQUIRED.keys + self.class::OPTIONAL.keys)
|
16
|
+
return true if extra.empty?
|
17
|
+
|
18
|
+
logger.error "extra section(s) found: #{extra.join ', '}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def sections_are_correct_type?(rep, logger)
|
22
|
+
correct_types = self.class::REQUIRED.merge self.class::OPTIONAL
|
23
|
+
rep.all? do |key, value|
|
24
|
+
correct = correct_types[key]
|
25
|
+
next true if (correct.instance_of?(Array) && correct.include?(value)) ||
|
26
|
+
(value.instance_of? correct)
|
27
|
+
|
28
|
+
logger.error "#{key}: #{value} is not #{correct}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sciolyff/validator/checker'
|
4
|
+
require 'sciolyff/validator/sections'
|
5
|
+
|
6
|
+
module SciolyFF
|
7
|
+
# Checks for one team in the Teams section of a SciolyFF file
|
8
|
+
class Validator::Teams < Validator::Checker
|
9
|
+
include Validator::Sections
|
10
|
+
|
11
|
+
REQUIRED = {
|
12
|
+
number: Integer,
|
13
|
+
school: String,
|
14
|
+
state: String
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
OPTIONAL = {
|
18
|
+
'school abbreviation': String,
|
19
|
+
subdivision: String,
|
20
|
+
suffix: String,
|
21
|
+
city: String,
|
22
|
+
disqualified: [true, false],
|
23
|
+
exhibition: [true, false]
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
def initialize(rep)
|
27
|
+
initialize_teams_info(rep[:Teams])
|
28
|
+
@placings = rep[:Placings].group_by { |p| p[:team] }
|
29
|
+
@exempt = rep[:Tournament][:'exempt placings'] || 0
|
30
|
+
end
|
31
|
+
|
32
|
+
def unique_number?(team, logger)
|
33
|
+
return true if @numbers.count(team[:number]) == 1
|
34
|
+
|
35
|
+
logger.error "duplicate team number: #{team[:number]}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def unique_suffix_per_school?(team, logger)
|
39
|
+
full_school = [team[:school], team[:city], team[:state]]
|
40
|
+
return true if @schools[full_school].count do |other|
|
41
|
+
!other[:suffix].nil? && other[:suffix] == team[:suffix]
|
42
|
+
end <= 1
|
43
|
+
|
44
|
+
logger.error "team number #{team[:number]} has the same suffix "\
|
45
|
+
'as another team from the same school'
|
46
|
+
end
|
47
|
+
|
48
|
+
def unambiguous_cities_per_school?(team, logger)
|
49
|
+
return true unless @schools.keys.find do |other|
|
50
|
+
team[:city].nil? && !other[1].nil? &&
|
51
|
+
team[:school] == other[0] &&
|
52
|
+
team[:state] == other[2]
|
53
|
+
end
|
54
|
+
|
55
|
+
logger.error "city for team number #{team[:number]} is ambiguous, "\
|
56
|
+
'value is required for unambiguity'
|
57
|
+
end
|
58
|
+
|
59
|
+
def correct_number_of_exempt_placings?(team, logger)
|
60
|
+
count = @placings[team[:number]].count { |p| p[:exempt] }
|
61
|
+
return true if count == @exempt || team[:exhibition]
|
62
|
+
|
63
|
+
logger.error "'team: #{team[:number]}' has incorrect number of "\
|
64
|
+
"exempt placings (#{count} insteand of #{@exempt})"
|
65
|
+
end
|
66
|
+
|
67
|
+
def in_a_subdivision_if_possible?(team, logger)
|
68
|
+
return true unless @subdivisions && !team[:subdivision]
|
69
|
+
|
70
|
+
logger.warn "missing subdivision for 'team: #{team[:number]}'"
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def initialize_teams_info(teams)
|
76
|
+
@numbers = teams.map { |t| t[:number] }
|
77
|
+
@schools = teams.group_by { |t| [t[:school], t[:city], t[:state]] }
|
78
|
+
@subdivisions = teams.find { |t| t[:subdivision] }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sciolyff/validator/checker'
|
4
|
+
require 'sciolyff/validator/sections'
|
5
|
+
|
6
|
+
module SciolyFF
|
7
|
+
# Top-level sections of a SciolyFF file
|
8
|
+
class Validator::TopLevel < Validator::Checker
|
9
|
+
include Validator::Sections
|
10
|
+
|
11
|
+
REQUIRED = {
|
12
|
+
Tournament: Hash,
|
13
|
+
Events: Array,
|
14
|
+
Teams: Array,
|
15
|
+
Placings: Array
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
OPTIONAL = {
|
19
|
+
Penalties: Array
|
20
|
+
}.freeze
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sciolyff/validator/checker'
|
4
|
+
require 'sciolyff/validator/sections'
|
5
|
+
|
6
|
+
module SciolyFF
|
7
|
+
# Checks for Tournament section of a SciolyFF file
|
8
|
+
class Validator::Tournament < Validator::Checker
|
9
|
+
include Validator::Sections
|
10
|
+
|
11
|
+
REQUIRED = {
|
12
|
+
location: String,
|
13
|
+
level: %w[Invitational Regionals States Nationals],
|
14
|
+
division: %w[A B C],
|
15
|
+
year: Integer,
|
16
|
+
date: Date
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
OPTIONAL = {
|
20
|
+
name: String,
|
21
|
+
state: String,
|
22
|
+
'short name': String,
|
23
|
+
'worst placings dropped': Integer,
|
24
|
+
'exempt placings': Integer,
|
25
|
+
'maximum place': Integer,
|
26
|
+
'per-event n': [true, false],
|
27
|
+
'n offset': Integer
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
def initialize(rep)
|
31
|
+
@maximum_place = rep[:Teams].count { |t| !t[:exhibition] }
|
32
|
+
end
|
33
|
+
|
34
|
+
def name_for_not_states_or_nationals?(tournament, logger)
|
35
|
+
level = tournament[:level]
|
36
|
+
return true if %w[States Nationals].include?(level) || tournament[:name]
|
37
|
+
|
38
|
+
logger.error 'name for Tournament required '\
|
39
|
+
"('level: #{level}' is not States or Nationals)"
|
40
|
+
end
|
41
|
+
|
42
|
+
def state_for_not_nationals?(tournament, logger)
|
43
|
+
return true if tournament[:level] == 'Nationals' || tournament[:state]
|
44
|
+
|
45
|
+
logger.error 'state for Tournament required '\
|
46
|
+
"('level: #{tournament[:level]}' is not Nationals)"
|
47
|
+
end
|
48
|
+
|
49
|
+
def short_name_is_relevant?(tournament, logger)
|
50
|
+
return true unless tournament[:'short name'] && !tournament[:name]
|
51
|
+
|
52
|
+
logger.error "'short name: #{tournament[:'short name']}' for Tournament "\
|
53
|
+
"requires a normal 'name:' as well"
|
54
|
+
end
|
55
|
+
|
56
|
+
def short_name_is_short?(tournament, logger)
|
57
|
+
return true if tournament[:'short name'].nil? ||
|
58
|
+
tournament[:'short name'].length < tournament[:name].length
|
59
|
+
|
60
|
+
logger.error "'short name: #{tournament[:'short name']}' for Tournament "\
|
61
|
+
"is longer than normal 'name: #{tournament[:name]}'"
|
62
|
+
end
|
63
|
+
|
64
|
+
def maximum_place_within_range?(tournament, logger)
|
65
|
+
return true if tournament[:'maximum place'].nil? ||
|
66
|
+
tournament[:'maximum place'].between?(1, @maximum_place)
|
67
|
+
|
68
|
+
logger.error "custom 'maximum place: #{tournament[:'maximum place']}' "\
|
69
|
+
"is not within range [1, #{@maximum_place}]"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SciolyFF
|
4
|
+
# Checks if SciolyFF YAML files and/or representations (i.e. hashes that can
|
5
|
+
# be directly converted to YAML) comply with spec (i.e. safe for interpreting)
|
6
|
+
class Validator
|
7
|
+
require 'sciolyff/validator/logger'
|
8
|
+
require 'sciolyff/validator/checker'
|
9
|
+
require 'sciolyff/validator/sections'
|
10
|
+
|
11
|
+
require 'sciolyff/validator/top_level'
|
12
|
+
require 'sciolyff/validator/tournament'
|
13
|
+
require 'sciolyff/validator/events'
|
14
|
+
require 'sciolyff/validator/teams'
|
15
|
+
require 'sciolyff/validator/placings'
|
16
|
+
require 'sciolyff/validator/penalties'
|
17
|
+
require 'sciolyff/validator/raws'
|
18
|
+
|
19
|
+
def initialize(loglevel = Logger::WARN)
|
20
|
+
@logger = Logger.new loglevel
|
21
|
+
@checkers = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid?(rep_or_file)
|
25
|
+
@logger.flush
|
26
|
+
|
27
|
+
if rep_or_file.instance_of? String
|
28
|
+
valid_file?(rep_or_file, @logger)
|
29
|
+
else
|
30
|
+
valid_rep?(rep_or_file, @logger)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def last_log
|
35
|
+
@logger.log
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def valid_rep?(rep, logger)
|
41
|
+
unless rep.instance_of? Hash
|
42
|
+
logger.error 'improper file structure'
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
|
46
|
+
result = check_all(rep, logger)
|
47
|
+
|
48
|
+
@checkers.clear # aka this method is not thread-safe
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
def valid_file?(path, logger)
|
53
|
+
rep = YAML.safe_load(
|
54
|
+
File.read(path),
|
55
|
+
permitted_classes: [Date],
|
56
|
+
symbolize_names: true
|
57
|
+
)
|
58
|
+
rescue StandardError => e
|
59
|
+
logger.error "could not read file as YAML:\n#{e.message}"
|
60
|
+
else
|
61
|
+
valid_rep?(rep, logger)
|
62
|
+
end
|
63
|
+
|
64
|
+
def check_all(rep, logger)
|
65
|
+
check(TopLevel, rep, rep, logger) &&
|
66
|
+
check(Tournament, rep, rep[:Tournament], logger) &&
|
67
|
+
[Events, Teams, Placings, Penalties].all? do |klass|
|
68
|
+
check_list(klass, rep, logger)
|
69
|
+
end &&
|
70
|
+
rep[:Placings].map { |p| p[:raw] }.compact.all? do |r|
|
71
|
+
check(Raws, rep, r, logger)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def check_list(klass, rep, logger)
|
76
|
+
key = klass.to_s.split('::').last.to_sym
|
77
|
+
return true unless rep.key? key # ignore optional sections like Penalties
|
78
|
+
|
79
|
+
rep[key].map { |e| check(klass, rep, e, logger) }.all?
|
80
|
+
end
|
81
|
+
|
82
|
+
def check(klass, top_level_rep, rep, logger)
|
83
|
+
@checkers[klass] ||= klass.new top_level_rep
|
84
|
+
checks = klass.instance_methods - Checker.instance_methods
|
85
|
+
checks.map { |im| @checkers[klass].send im, rep, logger }.all?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/sciolyff.rb
CHANGED
@@ -2,49 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'yaml'
|
4
4
|
require 'date'
|
5
|
-
require 'sciolyff/top_level'
|
6
|
-
require 'sciolyff/sections'
|
7
|
-
require 'sciolyff/tournament'
|
8
|
-
require 'sciolyff/events'
|
9
|
-
require 'sciolyff/teams'
|
10
|
-
require 'sciolyff/placings'
|
11
|
-
require 'sciolyff/scores'
|
12
|
-
require 'sciolyff/penalties'
|
13
|
-
require 'sciolyff/interpreter'
|
14
|
-
|
15
|
-
# API methods for the Scioly File Format
|
16
|
-
#
|
17
|
-
module SciolyFF
|
18
|
-
class << self
|
19
|
-
attr_accessor :rep
|
20
|
-
end
|
21
|
-
|
22
|
-
# Assumes rep is the output of YAML.load
|
23
|
-
def self.validate(rep, opts: {})
|
24
|
-
SciolyFF.rep = rep
|
25
|
-
|
26
|
-
mt_args = []
|
27
|
-
mt_args << '--verbose' if opts[:verbose]
|
28
|
-
|
29
|
-
Minitest.run mt_args
|
30
|
-
end
|
31
5
|
|
32
|
-
|
33
|
-
|
34
|
-
rep = YAML.safe_load(file, permitted_classes: [Date], symbolize_names: true)
|
35
|
-
rescue StandardError => e
|
36
|
-
puts 'Error: could not read file as YAML.'
|
37
|
-
warn e.message
|
38
|
-
else
|
39
|
-
puts FILE_VALIDATION_MESSAGE
|
40
|
-
validate(rep, opts: opts)
|
41
|
-
end
|
42
|
-
|
43
|
-
FILE_VALIDATION_MESSAGE = <<~STRING
|
44
|
-
Validating file with Minitest...
|
45
|
-
|
46
|
-
Overkill? Probably.
|
47
|
-
Doesn't give line numbers from original file? Yeah.
|
48
|
-
|
49
|
-
STRING
|
50
|
-
end
|
6
|
+
require 'sciolyff/interpreter'
|
7
|
+
require 'sciolyff/validator'
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sciolyff
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Em Zhan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-04-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: minitest
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '5.11'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '5.11'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: optimist
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -61,22 +47,31 @@ extra_rdoc_files: []
|
|
61
47
|
files:
|
62
48
|
- bin/sciolyff
|
63
49
|
- lib/sciolyff.rb
|
64
|
-
- lib/sciolyff/events.rb
|
65
50
|
- lib/sciolyff/interpreter.rb
|
66
51
|
- lib/sciolyff/interpreter/event.rb
|
52
|
+
- lib/sciolyff/interpreter/html.rb
|
53
|
+
- lib/sciolyff/interpreter/html/helpers.rb
|
54
|
+
- lib/sciolyff/interpreter/html/template.html.erb
|
67
55
|
- lib/sciolyff/interpreter/model.rb
|
68
56
|
- lib/sciolyff/interpreter/penalty.rb
|
69
57
|
- lib/sciolyff/interpreter/placing.rb
|
58
|
+
- lib/sciolyff/interpreter/raw.rb
|
59
|
+
- lib/sciolyff/interpreter/subdivisions.rb
|
70
60
|
- lib/sciolyff/interpreter/team.rb
|
61
|
+
- lib/sciolyff/interpreter/tiebreaks.rb
|
71
62
|
- lib/sciolyff/interpreter/tournament.rb
|
72
63
|
- lib/sciolyff/interpreter/ztestscript.rb
|
73
|
-
- lib/sciolyff/
|
74
|
-
- lib/sciolyff/
|
75
|
-
- lib/sciolyff/
|
76
|
-
- lib/sciolyff/
|
77
|
-
- lib/sciolyff/
|
78
|
-
- lib/sciolyff/
|
79
|
-
- lib/sciolyff/
|
64
|
+
- lib/sciolyff/validator.rb
|
65
|
+
- lib/sciolyff/validator/checker.rb
|
66
|
+
- lib/sciolyff/validator/events.rb
|
67
|
+
- lib/sciolyff/validator/logger.rb
|
68
|
+
- lib/sciolyff/validator/penalties.rb
|
69
|
+
- lib/sciolyff/validator/placings.rb
|
70
|
+
- lib/sciolyff/validator/raws.rb
|
71
|
+
- lib/sciolyff/validator/sections.rb
|
72
|
+
- lib/sciolyff/validator/teams.rb
|
73
|
+
- lib/sciolyff/validator/top_level.rb
|
74
|
+
- lib/sciolyff/validator/tournament.rb
|
80
75
|
homepage: https://github.com/zqianem/sciolyff
|
81
76
|
licenses:
|
82
77
|
- MIT
|
@@ -96,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
91
|
- !ruby/object:Gem::Version
|
97
92
|
version: '0'
|
98
93
|
requirements: []
|
99
|
-
rubygems_version: 3.
|
94
|
+
rubygems_version: 3.1.2
|
100
95
|
signing_key:
|
101
96
|
specification_version: 4
|
102
97
|
summary: A file format for Science Olympiad tournament results.
|
data/lib/sciolyff/events.rb
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'minitest/test'
|
4
|
-
require 'set'
|
5
|
-
|
6
|
-
module SciolyFF
|
7
|
-
# Tests that also serve as the specification for the sciolyff file format
|
8
|
-
#
|
9
|
-
class Events < Minitest::Test
|
10
|
-
def setup
|
11
|
-
skip unless SciolyFF.rep.instance_of? Hash
|
12
|
-
@events = SciolyFF.rep[:Events]
|
13
|
-
skip unless @events.instance_of? Array
|
14
|
-
end
|
15
|
-
|
16
|
-
def test_has_valid_events
|
17
|
-
@events.each do |event|
|
18
|
-
assert_instance_of Hash, event
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def test_each_event_does_not_have_extra_info
|
23
|
-
@events.select { |e| e.instance_of? Hash }.each do |event|
|
24
|
-
info = Set.new %i[name trial trialed scoring tiers]
|
25
|
-
assert Set.new(event.keys).subset? info
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def test_each_event_has_valid_name
|
30
|
-
@events.select { |e| e.instance_of? Hash }.each do |event|
|
31
|
-
assert_instance_of String, event[:name]
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def test_each_event_has_valid_trial
|
36
|
-
@events.select { |e| e.instance_of? Hash }.each do |event|
|
37
|
-
assert_includes [true, false], event[:trial] if event.key? :trial
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def test_each_event_has_valid_trialed
|
42
|
-
@events.select { |e| e.instance_of? Hash }.each do |event|
|
43
|
-
assert_includes [true, false], event[:trialed] if event.key? :trialed
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def test_each_event_has_valid_scoring
|
48
|
-
@events.select { |e| e.instance_of? Hash }.each do |event|
|
49
|
-
assert_includes %w[high low], event[:scoring] if event.key? :scoring
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def test_each_event_has_valid_tiers
|
54
|
-
@events.select { |e| e.instance_of? Hash }.each do |event|
|
55
|
-
assert_instance_of Integer, event[:tiers] if event.key? :tiers
|
56
|
-
assert_includes (1..), event[:tiers] if event.key? :tiers
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def test_each_event_has_unique_name
|
61
|
-
names = @events.select { |e| e.instance_of? Hash }
|
62
|
-
.map { |e| e[:name] }
|
63
|
-
assert_nil names.uniq!
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
data/lib/sciolyff/penalties.rb
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'minitest/test'
|
4
|
-
require 'set'
|
5
|
-
|
6
|
-
module SciolyFF
|
7
|
-
# Tests that also serve as the specification for the sciolyff file format
|
8
|
-
#
|
9
|
-
class Penalties < Minitest::Test
|
10
|
-
def setup
|
11
|
-
skip unless SciolyFF.rep.instance_of? Hash
|
12
|
-
@penalties = SciolyFF.rep[:Penalties]
|
13
|
-
skip unless @penalties.instance_of? Array
|
14
|
-
end
|
15
|
-
|
16
|
-
def test_has_valid_penalties
|
17
|
-
@penalties.each do |penalty|
|
18
|
-
assert_instance_of Hash, penalty
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def test_each_penalty_does_not_have_extra_info
|
23
|
-
@penalties.select { |p| p.instance_of? Hash }.each do |penalty|
|
24
|
-
info = Set.new %i[team points]
|
25
|
-
assert Set.new(penalty.keys).subset? info
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def test_each_penalty_has_valid_team
|
30
|
-
@penalties.select { |p| p.instance_of? Hash }.each do |penalty|
|
31
|
-
assert_instance_of Integer, penalty[:team]
|
32
|
-
skip unless SciolyFF.rep[:Teams].instance_of? Array
|
33
|
-
|
34
|
-
team_numbers = SciolyFF.rep[:Teams].map { |t| t[:number] }
|
35
|
-
assert_includes team_numbers, penalty[:team]
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def test_each_penalty_has_valid_points
|
40
|
-
@penalties.select { |p| p.instance_of? Hash }.each do |penalty|
|
41
|
-
assert_instance_of Integer, penalty[:points]
|
42
|
-
assert penalty[:points] >= 0
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def test_penalties_are_unique_for_team
|
47
|
-
teams = @penalties.select { |p| p.instance_of? Hash }
|
48
|
-
.map { |p| p[:team] }
|
49
|
-
assert_nil teams.uniq!
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|