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