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