aoc_cli 0.2.2 → 1.0.0
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/CHANGELOG.md +4 -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
|
+
|