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.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +88 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +8 -0
  6. data/Gemfile.lock +187 -0
  7. data/README.md +88 -282
  8. data/Rakefile +9 -2
  9. data/Steepfile +13 -0
  10. data/aoc_cli.gemspec +36 -26
  11. data/db/migrate/1_create_events.rb +14 -0
  12. data/db/migrate/2_create_puzzles.rb +21 -0
  13. data/db/migrate/3_create_stats.rb +19 -0
  14. data/db/migrate/4_create_attempts.rb +19 -0
  15. data/db/migrate/5_create_locations.rb +17 -0
  16. data/db/migrate/6_create_puzzle_dir_sync_logs.rb +18 -0
  17. data/exe/aoc +5 -0
  18. data/lib/aoc_cli/components/attempts_table.erb +5 -0
  19. data/lib/aoc_cli/components/attempts_table.rb +58 -0
  20. data/lib/aoc_cli/components/docs_component.erb +19 -0
  21. data/lib/aoc_cli/components/docs_component.rb +41 -0
  22. data/lib/aoc_cli/components/errors_component.erb +8 -0
  23. data/lib/aoc_cli/components/errors_component.rb +36 -0
  24. data/lib/aoc_cli/components/progress_table.erb +1 -0
  25. data/lib/aoc_cli/components/progress_table.rb +43 -0
  26. data/lib/aoc_cli/components/puzzle_sync_component.erb +2 -0
  27. data/lib/aoc_cli/components/puzzle_sync_component.rb +26 -0
  28. data/lib/aoc_cli/configurators/session_configurator.rb +7 -0
  29. data/lib/aoc_cli/controllers/application_controller.rb +27 -0
  30. data/lib/aoc_cli/controllers/concerns/error_concern.rb +17 -0
  31. data/lib/aoc_cli/controllers/concerns/location_concern.rb +79 -0
  32. data/lib/aoc_cli/controllers/default_controller.rb +9 -0
  33. data/lib/aoc_cli/controllers/event_controller.rb +35 -0
  34. data/lib/aoc_cli/controllers/help/event_controller.rb +11 -0
  35. data/lib/aoc_cli/controllers/help/puzzle_controller.rb +13 -0
  36. data/lib/aoc_cli/controllers/puzzle_controller.rb +39 -0
  37. data/lib/aoc_cli/core/attempt_parser.rb +69 -0
  38. data/lib/aoc_cli/core/processor.rb +32 -0
  39. data/lib/aoc_cli/core/repository.rb +74 -0
  40. data/lib/aoc_cli/core/request.rb +37 -0
  41. data/lib/aoc_cli/core/resource.rb +39 -0
  42. data/lib/aoc_cli/core/stats_parser.rb +43 -0
  43. data/lib/aoc_cli/helpers/table_generator.rb +64 -0
  44. data/lib/aoc_cli/helpers/view_helper.rb +35 -0
  45. data/lib/aoc_cli/models/attempt.rb +67 -0
  46. data/lib/aoc_cli/models/event.rb +9 -0
  47. data/lib/aoc_cli/models/location.rb +24 -0
  48. data/lib/aoc_cli/models/puzzle.rb +28 -0
  49. data/lib/aoc_cli/models/puzzle_dir_sync_log.rb +14 -0
  50. data/lib/aoc_cli/models/stats.rb +43 -0
  51. data/lib/aoc_cli/presenters/attempt_presenter.rb +29 -0
  52. data/lib/aoc_cli/presenters/puzzle_presenter.rb +29 -0
  53. data/lib/aoc_cli/presenters/stats_presenter.rb +32 -0
  54. data/lib/aoc_cli/processors/event_initialiser.rb +64 -0
  55. data/lib/aoc_cli/processors/puzzle_dir_synchroniser.rb +80 -0
  56. data/lib/aoc_cli/processors/puzzle_initialiser.rb +91 -0
  57. data/lib/aoc_cli/processors/puzzle_refresher.rb +27 -0
  58. data/lib/aoc_cli/processors/resource_attacher.rb +22 -0
  59. data/lib/aoc_cli/processors/solution_poster.rb +72 -0
  60. data/lib/aoc_cli/processors/stats_initialiser.rb +36 -0
  61. data/lib/aoc_cli/processors/stats_refresher.rb +23 -0
  62. data/lib/aoc_cli/validators/collection_type_validator.rb +57 -0
  63. data/lib/aoc_cli/validators/event_year_validator.rb +42 -0
  64. data/lib/aoc_cli/validators/included_validator.rb +21 -0
  65. data/lib/aoc_cli/validators/integer_validator.rb +32 -0
  66. data/lib/aoc_cli/validators/path_validator.rb +39 -0
  67. data/lib/aoc_cli/validators/type_validator.rb +54 -0
  68. data/lib/aoc_cli/version.rb +1 -1
  69. data/lib/aoc_cli/views/event/attach.erb +3 -0
  70. data/lib/aoc_cli/views/event/init.erb +3 -0
  71. data/lib/aoc_cli/views/help/event/attach.erb +32 -0
  72. data/lib/aoc_cli/views/help/event/init.erb +38 -0
  73. data/lib/aoc_cli/views/help/event/progress.erb +12 -0
  74. data/lib/aoc_cli/views/help/puzzle/attempts.erb +12 -0
  75. data/lib/aoc_cli/views/help/puzzle/init.erb +31 -0
  76. data/lib/aoc_cli/views/help/puzzle/solve.erb +33 -0
  77. data/lib/aoc_cli/views/help/puzzle/sync.erb +32 -0
  78. data/lib/aoc_cli/views/puzzle/init.erb +3 -0
  79. data/lib/aoc_cli/views/puzzle/solve.erb +10 -0
  80. data/lib/aoc_cli.rb +32 -16
  81. data/rbs_collection.lock.yaml +168 -0
  82. data/rbs_collection.yaml +28 -0
  83. data/sig/aoc_cli/components/attempts_table.rbs +29 -0
  84. data/sig/aoc_cli/components/docs_component.rbs +15 -0
  85. data/sig/aoc_cli/components/errors_component.rbs +19 -0
  86. data/sig/aoc_cli/components/progress_table.rbs +28 -0
  87. data/sig/aoc_cli/components/puzzle_sync_component.rbs +19 -0
  88. data/sig/aoc_cli/configurators/session_configurator.rbs +7 -0
  89. data/sig/aoc_cli/controllers/application_controller.rbs +12 -0
  90. data/sig/aoc_cli/controllers/concerns/error_concern.rbs +9 -0
  91. data/sig/aoc_cli/controllers/concerns/location_concern.rbs +35 -0
  92. data/sig/aoc_cli/controllers/default_controller.rbs +7 -0
  93. data/sig/aoc_cli/controllers/event_controller.rbs +18 -0
  94. data/sig/aoc_cli/controllers/help/event_controller.rbs +11 -0
  95. data/sig/aoc_cli/controllers/help/puzzle_controller.rbs +13 -0
  96. data/sig/aoc_cli/controllers/puzzle_controller.rbs +15 -0
  97. data/sig/aoc_cli/core/attempt_parser.rbs +41 -0
  98. data/sig/aoc_cli/core/processor.rbs +25 -0
  99. data/sig/aoc_cli/core/repository.rbs +25 -0
  100. data/sig/aoc_cli/core/request.rbs +29 -0
  101. data/sig/aoc_cli/core/resource.rbs +25 -0
  102. data/sig/aoc_cli/core/stats_parser.rbs +23 -0
  103. data/sig/aoc_cli/helpers/table_generator.rbs +36 -0
  104. data/sig/aoc_cli/helpers/view_helper.rbs +15 -0
  105. data/sig/aoc_cli/models/attempt.rbs +35 -0
  106. data/sig/aoc_cli/models/event.rbs +17 -0
  107. data/sig/aoc_cli/models/location.rbs +19 -0
  108. data/sig/aoc_cli/models/puzzle.rbs +28 -0
  109. data/sig/aoc_cli/models/puzzle_dir_sync_log.rbs +11 -0
  110. data/sig/aoc_cli/models/stats.rbs +53 -0
  111. data/sig/aoc_cli/presenters/attempt_presenter.rbs +13 -0
  112. data/sig/aoc_cli/presenters/puzzle_presenter.rbs +19 -0
  113. data/sig/aoc_cli/presenters/stats_presenter.rbs +19 -0
  114. data/sig/aoc_cli/processors/event_initialiser.rbs +26 -0
  115. data/sig/aoc_cli/processors/puzzle_dir_synchroniser.rbs +40 -0
  116. data/sig/aoc_cli/processors/puzzle_initialiser.rbs +37 -0
  117. data/sig/aoc_cli/processors/puzzle_refresher.rbs +21 -0
  118. data/sig/aoc_cli/processors/resource_attacher.rbs +16 -0
  119. data/sig/aoc_cli/processors/solution_poster.rbs +34 -0
  120. data/sig/aoc_cli/processors/stats_initialiser.rbs +15 -0
  121. data/sig/aoc_cli/processors/stats_refresher.rbs +19 -0
  122. data/sig/aoc_cli/validators/collection_type_validator.rbs +24 -0
  123. data/sig/aoc_cli/validators/event_year_validator.rbs +19 -0
  124. data/sig/aoc_cli/validators/included_validator.rbs +11 -0
  125. data/sig/aoc_cli/validators/integer_validator.rbs +15 -0
  126. data/sig/aoc_cli/validators/path_validator.rbs +17 -0
  127. data/sig/aoc_cli/validators/type_validator.rbs +22 -0
  128. data/sig/aoc_cli.rbs +6 -0
  129. data/sig/http.rbs +3 -0
  130. data/sig/kangaru.rbs +5 -0
  131. data/sig/nokogiri.rbs +3 -0
  132. data/sig/reverse_markdown.rbs +3 -0
  133. metadata +142 -34
  134. data/.gitignore +0 -5
  135. data/bin/aoc +0 -4
  136. data/bin/console +0 -15
  137. data/bin/setup +0 -7
  138. data/lib/aoc_cli/commands.rb +0 -232
  139. data/lib/aoc_cli/database.rb +0 -224
  140. data/lib/aoc_cli/day.rb +0 -124
  141. data/lib/aoc_cli/db/reddit.db +0 -0
  142. data/lib/aoc_cli/errors.rb +0 -275
  143. data/lib/aoc_cli/files.rb +0 -163
  144. data/lib/aoc_cli/help.rb +0 -77
  145. data/lib/aoc_cli/interface.rb +0 -81
  146. data/lib/aoc_cli/paths.rb +0 -101
  147. data/lib/aoc_cli/solve.rb +0 -104
  148. data/lib/aoc_cli/tables.rb +0 -138
  149. data/lib/aoc_cli/tools.rb +0 -120
  150. data/lib/aoc_cli/year.rb +0 -116
  151. 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
@@ -1,3 +1,3 @@
1
1
  module AocCli
2
- VERSION = "0.2.3"
2
+ VERSION = "1.0.0".freeze
3
3
  end
@@ -0,0 +1,3 @@
1
+ <%= success_tag %>: <%= queried_event.year %> location updated
2
+ from <%= @source %>
3
+ to <%= @target %>
@@ -0,0 +1,3 @@
1
+ <%= success_tag %>: event initialised
2
+ year <%= @event.year %>
3
+ path <%= @event.location.path %>
@@ -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
+