aoc_cli 0.2.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +88 -0
- data/.ruby-version +1 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +187 -0
- data/README.md +88 -282
- data/Rakefile +9 -2
- data/Steepfile +13 -0
- data/aoc_cli.gemspec +36 -26
- data/db/migrate/1_create_events.rb +14 -0
- data/db/migrate/2_create_puzzles.rb +21 -0
- data/db/migrate/3_create_stats.rb +19 -0
- data/db/migrate/4_create_attempts.rb +19 -0
- data/db/migrate/5_create_locations.rb +17 -0
- data/db/migrate/6_create_puzzle_dir_sync_logs.rb +18 -0
- data/exe/aoc +5 -0
- data/lib/aoc_cli/components/attempts_table.erb +5 -0
- data/lib/aoc_cli/components/attempts_table.rb +58 -0
- data/lib/aoc_cli/components/docs_component.erb +19 -0
- data/lib/aoc_cli/components/docs_component.rb +41 -0
- data/lib/aoc_cli/components/errors_component.erb +8 -0
- data/lib/aoc_cli/components/errors_component.rb +36 -0
- data/lib/aoc_cli/components/progress_table.erb +1 -0
- data/lib/aoc_cli/components/progress_table.rb +43 -0
- data/lib/aoc_cli/components/puzzle_sync_component.erb +2 -0
- data/lib/aoc_cli/components/puzzle_sync_component.rb +26 -0
- data/lib/aoc_cli/configurators/session_configurator.rb +7 -0
- data/lib/aoc_cli/controllers/application_controller.rb +27 -0
- data/lib/aoc_cli/controllers/concerns/error_concern.rb +17 -0
- data/lib/aoc_cli/controllers/concerns/location_concern.rb +79 -0
- data/lib/aoc_cli/controllers/default_controller.rb +9 -0
- data/lib/aoc_cli/controllers/event_controller.rb +35 -0
- data/lib/aoc_cli/controllers/help/event_controller.rb +11 -0
- data/lib/aoc_cli/controllers/help/puzzle_controller.rb +13 -0
- data/lib/aoc_cli/controllers/puzzle_controller.rb +39 -0
- data/lib/aoc_cli/core/attempt_parser.rb +69 -0
- data/lib/aoc_cli/core/processor.rb +32 -0
- data/lib/aoc_cli/core/repository.rb +74 -0
- data/lib/aoc_cli/core/request.rb +37 -0
- data/lib/aoc_cli/core/resource.rb +39 -0
- data/lib/aoc_cli/core/stats_parser.rb +43 -0
- data/lib/aoc_cli/helpers/table_generator.rb +64 -0
- data/lib/aoc_cli/helpers/view_helper.rb +35 -0
- data/lib/aoc_cli/models/attempt.rb +67 -0
- data/lib/aoc_cli/models/event.rb +9 -0
- data/lib/aoc_cli/models/location.rb +24 -0
- data/lib/aoc_cli/models/puzzle.rb +28 -0
- data/lib/aoc_cli/models/puzzle_dir_sync_log.rb +14 -0
- data/lib/aoc_cli/models/stats.rb +43 -0
- data/lib/aoc_cli/presenters/attempt_presenter.rb +29 -0
- data/lib/aoc_cli/presenters/puzzle_presenter.rb +29 -0
- data/lib/aoc_cli/presenters/stats_presenter.rb +32 -0
- data/lib/aoc_cli/processors/event_initialiser.rb +64 -0
- data/lib/aoc_cli/processors/puzzle_dir_synchroniser.rb +80 -0
- data/lib/aoc_cli/processors/puzzle_initialiser.rb +91 -0
- data/lib/aoc_cli/processors/puzzle_refresher.rb +27 -0
- data/lib/aoc_cli/processors/resource_attacher.rb +22 -0
- data/lib/aoc_cli/processors/solution_poster.rb +72 -0
- data/lib/aoc_cli/processors/stats_initialiser.rb +36 -0
- data/lib/aoc_cli/processors/stats_refresher.rb +23 -0
- data/lib/aoc_cli/validators/collection_type_validator.rb +57 -0
- data/lib/aoc_cli/validators/event_year_validator.rb +42 -0
- data/lib/aoc_cli/validators/included_validator.rb +21 -0
- data/lib/aoc_cli/validators/integer_validator.rb +32 -0
- data/lib/aoc_cli/validators/path_validator.rb +39 -0
- data/lib/aoc_cli/validators/type_validator.rb +54 -0
- data/lib/aoc_cli/version.rb +1 -1
- data/lib/aoc_cli/views/event/attach.erb +3 -0
- data/lib/aoc_cli/views/event/init.erb +3 -0
- data/lib/aoc_cli/views/help/event/attach.erb +32 -0
- data/lib/aoc_cli/views/help/event/init.erb +38 -0
- data/lib/aoc_cli/views/help/event/progress.erb +12 -0
- data/lib/aoc_cli/views/help/puzzle/attempts.erb +12 -0
- data/lib/aoc_cli/views/help/puzzle/init.erb +31 -0
- data/lib/aoc_cli/views/help/puzzle/solve.erb +33 -0
- data/lib/aoc_cli/views/help/puzzle/sync.erb +32 -0
- data/lib/aoc_cli/views/puzzle/init.erb +3 -0
- data/lib/aoc_cli/views/puzzle/solve.erb +10 -0
- data/lib/aoc_cli.rb +32 -16
- data/rbs_collection.lock.yaml +168 -0
- data/rbs_collection.yaml +28 -0
- data/sig/aoc_cli/components/attempts_table.rbs +29 -0
- data/sig/aoc_cli/components/docs_component.rbs +15 -0
- data/sig/aoc_cli/components/errors_component.rbs +19 -0
- data/sig/aoc_cli/components/progress_table.rbs +28 -0
- data/sig/aoc_cli/components/puzzle_sync_component.rbs +19 -0
- data/sig/aoc_cli/configurators/session_configurator.rbs +7 -0
- data/sig/aoc_cli/controllers/application_controller.rbs +12 -0
- data/sig/aoc_cli/controllers/concerns/error_concern.rbs +9 -0
- data/sig/aoc_cli/controllers/concerns/location_concern.rbs +35 -0
- data/sig/aoc_cli/controllers/default_controller.rbs +7 -0
- data/sig/aoc_cli/controllers/event_controller.rbs +18 -0
- data/sig/aoc_cli/controllers/help/event_controller.rbs +11 -0
- data/sig/aoc_cli/controllers/help/puzzle_controller.rbs +13 -0
- data/sig/aoc_cli/controllers/puzzle_controller.rbs +15 -0
- data/sig/aoc_cli/core/attempt_parser.rbs +41 -0
- data/sig/aoc_cli/core/processor.rbs +25 -0
- data/sig/aoc_cli/core/repository.rbs +25 -0
- data/sig/aoc_cli/core/request.rbs +29 -0
- data/sig/aoc_cli/core/resource.rbs +25 -0
- data/sig/aoc_cli/core/stats_parser.rbs +23 -0
- data/sig/aoc_cli/helpers/table_generator.rbs +36 -0
- data/sig/aoc_cli/helpers/view_helper.rbs +15 -0
- data/sig/aoc_cli/models/attempt.rbs +35 -0
- data/sig/aoc_cli/models/event.rbs +17 -0
- data/sig/aoc_cli/models/location.rbs +19 -0
- data/sig/aoc_cli/models/puzzle.rbs +28 -0
- data/sig/aoc_cli/models/puzzle_dir_sync_log.rbs +11 -0
- data/sig/aoc_cli/models/stats.rbs +53 -0
- data/sig/aoc_cli/presenters/attempt_presenter.rbs +13 -0
- data/sig/aoc_cli/presenters/puzzle_presenter.rbs +19 -0
- data/sig/aoc_cli/presenters/stats_presenter.rbs +19 -0
- data/sig/aoc_cli/processors/event_initialiser.rbs +26 -0
- data/sig/aoc_cli/processors/puzzle_dir_synchroniser.rbs +40 -0
- data/sig/aoc_cli/processors/puzzle_initialiser.rbs +37 -0
- data/sig/aoc_cli/processors/puzzle_refresher.rbs +21 -0
- data/sig/aoc_cli/processors/resource_attacher.rbs +16 -0
- data/sig/aoc_cli/processors/solution_poster.rbs +34 -0
- data/sig/aoc_cli/processors/stats_initialiser.rbs +15 -0
- data/sig/aoc_cli/processors/stats_refresher.rbs +19 -0
- data/sig/aoc_cli/validators/collection_type_validator.rbs +24 -0
- data/sig/aoc_cli/validators/event_year_validator.rbs +19 -0
- data/sig/aoc_cli/validators/included_validator.rbs +11 -0
- data/sig/aoc_cli/validators/integer_validator.rbs +15 -0
- data/sig/aoc_cli/validators/path_validator.rbs +17 -0
- data/sig/aoc_cli/validators/type_validator.rbs +22 -0
- data/sig/aoc_cli.rbs +6 -0
- data/sig/http.rbs +3 -0
- data/sig/kangaru.rbs +5 -0
- data/sig/nokogiri.rbs +3 -0
- data/sig/reverse_markdown.rbs +3 -0
- metadata +142 -34
- data/.gitignore +0 -5
- data/bin/aoc +0 -4
- data/bin/console +0 -15
- data/bin/setup +0 -7
- data/lib/aoc_cli/commands.rb +0 -232
- data/lib/aoc_cli/database.rb +0 -224
- data/lib/aoc_cli/day.rb +0 -124
- data/lib/aoc_cli/db/reddit.db +0 -0
- data/lib/aoc_cli/errors.rb +0 -275
- data/lib/aoc_cli/files.rb +0 -163
- data/lib/aoc_cli/help.rb +0 -77
- data/lib/aoc_cli/interface.rb +0 -81
- data/lib/aoc_cli/paths.rb +0 -101
- data/lib/aoc_cli/solve.rb +0 -104
- data/lib/aoc_cli/tables.rb +0 -138
- data/lib/aoc_cli/tools.rb +0 -120
- data/lib/aoc_cli/year.rb +0 -116
- data/sample/aoc.rc +0 -21
@@ -0,0 +1,80 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Processors
|
3
|
+
class PuzzleDirSynchroniser < Core::Processor
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
attr_accessor :puzzle, :location, :skip_cache
|
7
|
+
|
8
|
+
set_default skip_cache: false
|
9
|
+
|
10
|
+
validates :puzzle, required: true
|
11
|
+
|
12
|
+
validates :location, required: true
|
13
|
+
|
14
|
+
# TODO: replace with conditional validation
|
15
|
+
def validate
|
16
|
+
super
|
17
|
+
validate_puzzle_dir_exists! if errors.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
refresh_puzzle! if skip_cache
|
22
|
+
|
23
|
+
create_puzzle_dir_sync_log!.tap do
|
24
|
+
puzzle_file.write(puzzle.content)
|
25
|
+
input_file.write(puzzle.input)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def_delegators :puzzle, :year, :day, :presenter
|
32
|
+
|
33
|
+
def_delegators :presenter, :puzzle_filename, :input_filename
|
34
|
+
|
35
|
+
def puzzle_dir
|
36
|
+
@puzzle_dir ||= location.to_pathname
|
37
|
+
end
|
38
|
+
|
39
|
+
def puzzle_file
|
40
|
+
@puzzle_file ||= puzzle_dir.join(puzzle_filename)
|
41
|
+
end
|
42
|
+
|
43
|
+
def input_file
|
44
|
+
@input_file ||= puzzle_dir.join(input_filename)
|
45
|
+
end
|
46
|
+
|
47
|
+
def refresh_puzzle!
|
48
|
+
PuzzleRefresher.run!(puzzle:)
|
49
|
+
|
50
|
+
puzzle.reload
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_puzzle_dir_sync_log!
|
54
|
+
PuzzleDirSyncLog.create(
|
55
|
+
puzzle:, location:, puzzle_status:, input_status:
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def puzzle_status
|
60
|
+
return :new unless puzzle_file.exist?
|
61
|
+
|
62
|
+
puzzle_file.read == puzzle.content ? :unmodified : :modified
|
63
|
+
end
|
64
|
+
|
65
|
+
def input_status
|
66
|
+
return :new unless input_file.exist?
|
67
|
+
|
68
|
+
input_file.read == puzzle.input ? :unmodified : :modified
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate_puzzle_dir_exists!
|
72
|
+
return if puzzle_dir.exist?
|
73
|
+
|
74
|
+
errors << Kangaru::Validation::Error.new(
|
75
|
+
attribute: :puzzle_dir, message: "does not exist"
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Processors
|
3
|
+
class PuzzleInitialiser < Core::Processor
|
4
|
+
attr_accessor :event, :day
|
5
|
+
|
6
|
+
validates :event, required: true
|
7
|
+
validates :day, integer: { between: 1..25 }
|
8
|
+
|
9
|
+
def validate
|
10
|
+
super
|
11
|
+
validate_event_location_set! if errors.empty?
|
12
|
+
validate_event_dir_exists! if errors.empty?
|
13
|
+
validate_puzzle_dir_does_not_exist! if errors.empty?
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
create_or_update_puzzle!(fetch_content!, fetch_input!).tap do |puzzle|
|
18
|
+
attach_puzzle_dir!(puzzle).tap do |location|
|
19
|
+
write_puzzle_files!(puzzle, location)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def_delegators :event, :year
|
27
|
+
|
28
|
+
def event_dir
|
29
|
+
@event_dir ||= event.location.to_pathname
|
30
|
+
end
|
31
|
+
|
32
|
+
def puzzle_dir
|
33
|
+
@puzzle_dir ||= event_dir.join(day.to_s)
|
34
|
+
end
|
35
|
+
|
36
|
+
def existing_puzzle
|
37
|
+
@existing_puzzle ||= Puzzle.first(event:, day:)
|
38
|
+
end
|
39
|
+
|
40
|
+
def fetch_content!
|
41
|
+
Core::Repository.get_puzzle(year:, day:)
|
42
|
+
end
|
43
|
+
|
44
|
+
def fetch_input!
|
45
|
+
Core::Repository.get_input(year:, day:)
|
46
|
+
end
|
47
|
+
|
48
|
+
def create_or_update_puzzle!(content, input)
|
49
|
+
if existing_puzzle.nil?
|
50
|
+
Puzzle.create(event:, day:, content:, input:)
|
51
|
+
else
|
52
|
+
existing_puzzle.update(content:, input:) || existing_puzzle
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def attach_puzzle_dir!(puzzle)
|
57
|
+
puzzle_dir.mkdir
|
58
|
+
|
59
|
+
ResourceAttacher.run!(resource: puzzle, path: puzzle_dir.to_s)
|
60
|
+
end
|
61
|
+
|
62
|
+
def write_puzzle_files!(puzzle, location)
|
63
|
+
PuzzleDirSynchroniser.run!(puzzle:, location:)
|
64
|
+
end
|
65
|
+
|
66
|
+
def validate_event_location_set!
|
67
|
+
return unless event.location.nil?
|
68
|
+
|
69
|
+
errors << Kangaru::Validation::Error.new(
|
70
|
+
attribute: :event_location, message: "can't be blank"
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate_event_dir_exists!
|
75
|
+
return if event_dir.exist?
|
76
|
+
|
77
|
+
errors << Kangaru::Validation::Error.new(
|
78
|
+
attribute: :event_dir, message: "does not exist"
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
def validate_puzzle_dir_does_not_exist!
|
83
|
+
return unless puzzle_dir.exist?
|
84
|
+
|
85
|
+
errors << Kangaru::Validation::Error.new(
|
86
|
+
attribute: :puzzle_dir, message: "already exists"
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Processors
|
3
|
+
class PuzzleRefresher < Core::Processor
|
4
|
+
attr_accessor :puzzle
|
5
|
+
|
6
|
+
validates :puzzle, type: { equals: Puzzle }
|
7
|
+
|
8
|
+
def run
|
9
|
+
puzzle.update(content:, input:) || puzzle
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
extend Forwardable
|
15
|
+
|
16
|
+
def_delegators :puzzle, :year, :day
|
17
|
+
|
18
|
+
def content
|
19
|
+
Core::Repository.get_puzzle(year:, day:)
|
20
|
+
end
|
21
|
+
|
22
|
+
def input
|
23
|
+
Core::Repository.get_input(year:, day:)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Processors
|
3
|
+
class ResourceAttacher < Core::Processor
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
attr_accessor :resource, :path
|
7
|
+
|
8
|
+
validates :resource, type: { one_of: [Event, Puzzle] }
|
9
|
+
validates :path, path: { exists: true }
|
10
|
+
|
11
|
+
def run
|
12
|
+
if location.nil?
|
13
|
+
Location.create(resource:, path:)
|
14
|
+
else
|
15
|
+
location&.update(path:) || location
|
16
|
+
end.tap { resource.reload }
|
17
|
+
end
|
18
|
+
|
19
|
+
def_delegators :resource, :location
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Processors
|
3
|
+
class SolutionPoster < Core::Processor
|
4
|
+
attr_accessor :puzzle, :answer
|
5
|
+
|
6
|
+
validates :puzzle, type: { equals: Puzzle }
|
7
|
+
|
8
|
+
validates :answer, required: true
|
9
|
+
|
10
|
+
# TODO: replace with conditional validation
|
11
|
+
def validate
|
12
|
+
super
|
13
|
+
validate_puzzle_location_set! if errors.empty?
|
14
|
+
validate_stats_associated! if errors.empty?
|
15
|
+
validate_puzzle_not_complete! if errors.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
create_attempt!(post_solution!).tap do |attempt|
|
20
|
+
advance_puzzle! if attempt.correct?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def_delegators :puzzle, :event, :location, :year, :day
|
27
|
+
|
28
|
+
def level
|
29
|
+
@level ||= event.stats.current_level(day) || raise
|
30
|
+
end
|
31
|
+
|
32
|
+
def post_solution!
|
33
|
+
Core::Repository.post_solution(year:, day:, level:, answer:)
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_attempt!(response)
|
37
|
+
Attempt.create(puzzle:, level:, answer:, **response)
|
38
|
+
end
|
39
|
+
|
40
|
+
def advance_puzzle!
|
41
|
+
event.stats.advance_progress!(day)
|
42
|
+
puzzle.mark_complete!(level)
|
43
|
+
|
44
|
+
PuzzleDirSynchroniser.run!(puzzle:, location:, skip_cache: true)
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate_puzzle_location_set!
|
48
|
+
return unless location.nil?
|
49
|
+
|
50
|
+
errors << Kangaru::Validation::Error.new(
|
51
|
+
attribute: :location, message: "can't be blank"
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate_stats_associated!
|
56
|
+
return unless puzzle.event.stats.nil?
|
57
|
+
|
58
|
+
errors << Kangaru::Validation::Error.new(
|
59
|
+
attribute: :stats, message: "can't be blank"
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def validate_puzzle_not_complete!
|
64
|
+
return unless event.stats.complete?(day)
|
65
|
+
|
66
|
+
errors << Kangaru::Validation::Error.new(
|
67
|
+
attribute: :puzzle, message: "is already complete"
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Processors
|
3
|
+
class StatsInitialiser < Core::Processor
|
4
|
+
attr_accessor :event
|
5
|
+
|
6
|
+
validates :event, type: { equals: Event }
|
7
|
+
|
8
|
+
def validate
|
9
|
+
super
|
10
|
+
validate_stats_do_not_exist! if errors.empty?
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
stats = BLANK_STATS.merge(fetch_stats!)
|
15
|
+
|
16
|
+
Stats.create(event:, **stats)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
BLANK_STATS = 1.upto(25).to_h { |day| [:"day_#{day}", 0] }
|
22
|
+
|
23
|
+
def fetch_stats!
|
24
|
+
Core::Repository.get_stats(year: event.year)
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate_stats_do_not_exist!
|
28
|
+
return if event.stats.nil?
|
29
|
+
|
30
|
+
errors << Kangaru::Validation::Error.new(
|
31
|
+
attribute: :stats, message: "already exist"
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Processors
|
3
|
+
class StatsRefresher < Core::Processor
|
4
|
+
attr_accessor :stats
|
5
|
+
|
6
|
+
validates :stats, type: { equals: Stats }
|
7
|
+
|
8
|
+
def run
|
9
|
+
stats.update(**updated_stats) || stats
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
extend Forwardable
|
15
|
+
|
16
|
+
def_delegators :stats, :year
|
17
|
+
|
18
|
+
def updated_stats
|
19
|
+
Core::Repository.get_stats(year:)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Validators
|
3
|
+
class CollectionTypeValidator < Kangaru::Validator
|
4
|
+
def validate
|
5
|
+
validate_preconditions!
|
6
|
+
|
7
|
+
return add_error!("can't be blank") if value.nil?
|
8
|
+
return add_error!("is not an array") unless value.is_a?(Array)
|
9
|
+
|
10
|
+
case validation_rule
|
11
|
+
when :all then validate_all_elements!
|
12
|
+
when :any then validate_any_elements!
|
13
|
+
when :none then validate_none_elements!
|
14
|
+
else raise
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
VALIDATION_RULES = %i[all any none].freeze
|
21
|
+
|
22
|
+
ERROR = "elements have incompatible types".freeze
|
23
|
+
|
24
|
+
def validation_rule
|
25
|
+
@validation_rule ||= params.slice(*VALIDATION_RULES).keys.first || raise
|
26
|
+
end
|
27
|
+
|
28
|
+
def target_type
|
29
|
+
@target_type ||= params[validation_rule]
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate_preconditions!
|
33
|
+
return if params.slice(*VALIDATION_RULES).count == 1
|
34
|
+
|
35
|
+
raise "Collection type rule must be one of #{VALIDATION_RULES.inspect}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate_all_elements!
|
39
|
+
return if value.all? { |element| element.is_a?(target_type) }
|
40
|
+
|
41
|
+
add_error!(ERROR)
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate_any_elements!
|
45
|
+
return if value.any? { |element| element.is_a?(target_type) }
|
46
|
+
|
47
|
+
add_error!(ERROR)
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate_none_elements!
|
51
|
+
return if value.none? { |element| element.is_a?(target_type) }
|
52
|
+
|
53
|
+
add_error!(ERROR)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Validators
|
3
|
+
class EventYearValidator < Kangaru::Validator
|
4
|
+
def validate
|
5
|
+
case value
|
6
|
+
when Integer then validate_year!
|
7
|
+
when NilClass then add_error!(ERRORS[:blank])
|
8
|
+
else add_error!(ERRORS[:invalid])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
ERRORS = {
|
15
|
+
blank: "can't be blank",
|
16
|
+
invalid: "is not an integer",
|
17
|
+
too_low: "is before first Advent of Code event (2015)",
|
18
|
+
too_high: "is in the future"
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
MIN_YEAR = 2015
|
22
|
+
|
23
|
+
def max_year
|
24
|
+
return Date.today.year if in_december?
|
25
|
+
|
26
|
+
Date.today.year - 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def in_december?
|
30
|
+
Date.today.month == 12
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_year!
|
34
|
+
if value < MIN_YEAR
|
35
|
+
add_error!(ERRORS[:too_low])
|
36
|
+
elsif value > max_year
|
37
|
+
add_error!(ERRORS[:too_high])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Validators
|
3
|
+
class IncludedValidator < Kangaru::Validator
|
4
|
+
def validate
|
5
|
+
return if allowed_values.include?(value)
|
6
|
+
|
7
|
+
add_error!(ERRORS[:not_included])
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
ERRORS = {
|
13
|
+
not_included: "is not a valid option"
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
def allowed_values
|
17
|
+
params[:in]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Validators
|
3
|
+
class IntegerValidator < Kangaru::Validator
|
4
|
+
def validate
|
5
|
+
return add_error!(ERRORS[:blank]) if value.nil?
|
6
|
+
return add_error!(ERRORS[:type]) unless value.is_a?(Integer)
|
7
|
+
|
8
|
+
validate_range! if params.keys.include?(:between)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
ERRORS = {
|
14
|
+
blank: "can't be blank",
|
15
|
+
type: "is not an integer",
|
16
|
+
too_small: "is too small",
|
17
|
+
too_large: "is too large"
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
def range
|
21
|
+
params[:between]
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_range!
|
25
|
+
return if range.include?(value)
|
26
|
+
|
27
|
+
add_error!(ERRORS[:too_small]) if value < range.first
|
28
|
+
add_error!(ERRORS[:too_large]) if value > params[:between].last
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Validators
|
3
|
+
class PathValidator < Kangaru::Validator
|
4
|
+
def validate
|
5
|
+
return add_error!(ERRORS[:blank]) if value.nil?
|
6
|
+
|
7
|
+
if params[:exists] == false
|
8
|
+
validate_path_does_not_exist!
|
9
|
+
else
|
10
|
+
validate_path_exists!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
ERRORS = {
|
17
|
+
blank: "can't be blank",
|
18
|
+
exists: "already exists",
|
19
|
+
does_not_exist: "does not exist"
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
def path_exists?
|
23
|
+
File.exist?(value)
|
24
|
+
end
|
25
|
+
|
26
|
+
def validate_path_does_not_exist!
|
27
|
+
return unless path_exists?
|
28
|
+
|
29
|
+
add_error!(ERRORS[:exists])
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate_path_exists!
|
33
|
+
return if path_exists?
|
34
|
+
|
35
|
+
add_error!(ERRORS[:does_not_exist])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Validators
|
3
|
+
class TypeValidator < Kangaru::Validator
|
4
|
+
def validate
|
5
|
+
validate_target_type_set!
|
6
|
+
|
7
|
+
if params[:equals]
|
8
|
+
validate_type!
|
9
|
+
elsif params[:one_of]
|
10
|
+
validate_type_from_list!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
ERRORS = {
|
17
|
+
blank: "can't be blank",
|
18
|
+
type: "has incompatible type"
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
def valid_type
|
22
|
+
params[:equals]
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid_types
|
26
|
+
params[:one_of]
|
27
|
+
end
|
28
|
+
|
29
|
+
def error
|
30
|
+
return ERRORS[:blank] if value.nil?
|
31
|
+
|
32
|
+
ERRORS[:type]
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate_target_type_set!
|
36
|
+
return if valid_type || valid_types
|
37
|
+
|
38
|
+
raise "type must be specified via :equals or :one_of options"
|
39
|
+
end
|
40
|
+
|
41
|
+
def validate_type!
|
42
|
+
return if value.is_a?(valid_type)
|
43
|
+
|
44
|
+
add_error!(error)
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate_type_from_list!
|
48
|
+
return if valid_types&.include?(value.class)
|
49
|
+
|
50
|
+
add_error!(error)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/aoc_cli/version.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
aoc event attach
|
2
|
+
|
3
|
+
Description:
|
4
|
+
|
5
|
+
Attaches an existing event to the specified directory (defaults to current
|
6
|
+
directory). This may be useful if the original directories have been moved
|
7
|
+
or deleted.
|
8
|
+
|
9
|
+
This command cannot be executed from, or target, a directory that is already
|
10
|
+
managed by aoc (i.e., existing event and puzzle directories).
|
11
|
+
|
12
|
+
The command will fail if the event has not already been initialized. To
|
13
|
+
create a new event, see the event init command.
|
14
|
+
|
15
|
+
Usage:
|
16
|
+
|
17
|
+
aoc event attach <year> [--dir=<dir>]
|
18
|
+
|
19
|
+
* <year>: Year of the event to relocate.
|
20
|
+
* <dir>: The new event directory (default ".").
|
21
|
+
|
22
|
+
Examples:
|
23
|
+
|
24
|
+
aoc event attach 2023 (in a non-AoC directory)
|
25
|
+
|
26
|
+
Relocates the 2023 event directory to the current directory
|
27
|
+
|
28
|
+
|
29
|
+
aoc event attach 2023 --dir /foo/bar/aoc
|
30
|
+
|
31
|
+
Relocates the 2023 event directory to the specified directory.
|
32
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
aoc event init
|
2
|
+
|
3
|
+
Description:
|
4
|
+
|
5
|
+
Initializes the event for the specified year by fetching event stats and
|
6
|
+
creating an event directory in the target directory. The target directory
|
7
|
+
defaults to the current directory but can be explicitly specified with the
|
8
|
+
optional --dir String argument.
|
9
|
+
|
10
|
+
The event directory serves as the location of event puzzle directories. For
|
11
|
+
more information, refer to puzzle init. The name of the created event
|
12
|
+
directory defaults to the event year.
|
13
|
+
|
14
|
+
This command cannot be executed from, or target, a directory that is already
|
15
|
+
managed by aoc (i.e., existing event and puzzle directories).
|
16
|
+
|
17
|
+
The command will fail if the event has already been initialized elsewhere. To
|
18
|
+
move an existing event's location, see the event attach command.
|
19
|
+
|
20
|
+
Usage:
|
21
|
+
|
22
|
+
aoc event init <year> [--dir=<dir>]
|
23
|
+
|
24
|
+
* <year>: Year of the event to initialize.
|
25
|
+
* <dir>: Base directory to initialize the event within (default ".").
|
26
|
+
|
27
|
+
Examples:
|
28
|
+
|
29
|
+
aoc event init 2023 (in a non-AoC directory)
|
30
|
+
|
31
|
+
Initializes the 2023 event and creates the 2023 event directory inside the
|
32
|
+
current directory.
|
33
|
+
|
34
|
+
aoc event init 2023 --dir /foo/bar/aoc
|
35
|
+
|
36
|
+
Initializes the 2023 event and creates the 2023 event directory in the
|
37
|
+
specified directory.
|
38
|
+
|