aoc_cli 0.2.3 → 1.0.1
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 +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/db/migrate/7_create_progresses.rb +17 -0
- data/db/migrate/8_remove_legacy_timestamps.rb +8 -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/progress.rb +33 -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/progress_syncer.rb +48 -0
- data/lib/aoc_cli/processors/puzzle_dir_synchroniser.rb +80 -0
- data/lib/aoc_cli/processors/puzzle_initialiser.rb +106 -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 +36 -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/progress.rbs +21 -0
- data/sig/aoc_cli/models/puzzle.rbs +27 -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/progress_syncer.rbs +29 -0
- data/sig/aoc_cli/processors/puzzle_dir_synchroniser.rbs +40 -0
- data/sig/aoc_cli/processors/puzzle_initialiser.rbs +41 -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 +149 -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,48 @@
|
|
|
1
|
+
module AocCli
|
|
2
|
+
module Processors
|
|
3
|
+
class ProgressSyncer < Core::Processor
|
|
4
|
+
attr_accessor :puzzle, :stats
|
|
5
|
+
|
|
6
|
+
validates :puzzle, required: true
|
|
7
|
+
validates :stats, required: true
|
|
8
|
+
|
|
9
|
+
def run
|
|
10
|
+
case current_progress
|
|
11
|
+
when 0 then handle_not_complete!
|
|
12
|
+
when 1 then handle_partially_complete!
|
|
13
|
+
when 2 then handle_fully_complete!
|
|
14
|
+
else raise
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def_delegators :puzzle, :part_one_progress, :part_two_progress
|
|
21
|
+
|
|
22
|
+
def current_progress
|
|
23
|
+
stats.progress(puzzle.day)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def create_part_one_progress!
|
|
27
|
+
Progress.create(puzzle:, level: 1, started_at: Time.now)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create_part_two_progress!
|
|
31
|
+
Progress.create(puzzle:, level: 2, started_at: Time.now)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def handle_not_complete!
|
|
35
|
+
create_part_one_progress! if part_one_progress.nil?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def handle_partially_complete!
|
|
39
|
+
part_one_progress&.complete! if part_one_progress&.incomplete?
|
|
40
|
+
create_part_two_progress! if part_two_progress.nil?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def handle_fully_complete!
|
|
44
|
+
part_two_progress&.complete! if part_two_progress&.incomplete?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -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,106 @@
|
|
|
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_stats_set! if errors.empty?
|
|
12
|
+
validate_event_location_set! if errors.empty?
|
|
13
|
+
validate_event_dir_exists! if errors.empty?
|
|
14
|
+
validate_puzzle_dir_does_not_exist! if errors.empty?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run
|
|
18
|
+
create_or_update_puzzle!(fetch_content!, fetch_input!).tap do |puzzle|
|
|
19
|
+
sync_puzzle_progress!(puzzle)
|
|
20
|
+
|
|
21
|
+
attach_puzzle_dir!(puzzle).tap do |location|
|
|
22
|
+
write_puzzle_files!(puzzle, location)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def_delegators :event, :year
|
|
30
|
+
|
|
31
|
+
def event_dir
|
|
32
|
+
@event_dir ||= event.location.to_pathname
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def puzzle_dir
|
|
36
|
+
@puzzle_dir ||= event_dir.join(day.to_s)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def existing_puzzle
|
|
40
|
+
@existing_puzzle ||= Puzzle.first(event:, day:)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def fetch_content!
|
|
44
|
+
Core::Repository.get_puzzle(year:, day:)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def fetch_input!
|
|
48
|
+
Core::Repository.get_input(year:, day:)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def create_or_update_puzzle!(content, input)
|
|
52
|
+
if existing_puzzle.nil?
|
|
53
|
+
Puzzle.create(event:, day:, content:, input:)
|
|
54
|
+
else
|
|
55
|
+
existing_puzzle.update(content:, input:) || existing_puzzle
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def sync_puzzle_progress!(puzzle)
|
|
60
|
+
ProgressSyncer.run!(puzzle:, stats: event.stats)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def attach_puzzle_dir!(puzzle)
|
|
64
|
+
puzzle_dir.mkdir
|
|
65
|
+
|
|
66
|
+
ResourceAttacher.run!(resource: puzzle, path: puzzle_dir.to_s)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def write_puzzle_files!(puzzle, location)
|
|
70
|
+
PuzzleDirSynchroniser.run!(puzzle:, location:)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def validate_event_stats_set!
|
|
74
|
+
return unless event.stats.nil?
|
|
75
|
+
|
|
76
|
+
errors << Kangaru::Validation::Error.new(
|
|
77
|
+
attribute: :event_stats, message: "can't be blank"
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def validate_event_location_set!
|
|
82
|
+
return unless event.location.nil?
|
|
83
|
+
|
|
84
|
+
errors << Kangaru::Validation::Error.new(
|
|
85
|
+
attribute: :event_location, message: "can't be blank"
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def validate_event_dir_exists!
|
|
90
|
+
return if event_dir.exist?
|
|
91
|
+
|
|
92
|
+
errors << Kangaru::Validation::Error.new(
|
|
93
|
+
attribute: :event_dir, message: "does not exist"
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def validate_puzzle_dir_does_not_exist!
|
|
98
|
+
return unless puzzle_dir.exist?
|
|
99
|
+
|
|
100
|
+
errors << Kangaru::Validation::Error.new(
|
|
101
|
+
attribute: :puzzle_dir, message: "already exists"
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
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
|
+
|
|
43
|
+
ProgressSyncer.run!(puzzle:, stats: event.stats.reload)
|
|
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