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,18 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table :puzzle_dir_sync_logs do
4
+ primary_key :id
5
+
6
+ foreign_key :puzzle_id, :puzzles, null: false
7
+ foreign_key :location_id, :locations, null: false
8
+
9
+ integer :puzzle_status, null: false
10
+ integer :input_status, null: false
11
+
12
+ datetime :created_at
13
+ datetime :updated_at
14
+
15
+ index %i[puzzle_id location_id]
16
+ end
17
+ end
18
+ end
data/exe/aoc ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "aoc_cli"
4
+
5
+ AocCli.run!(*ARGV)
@@ -0,0 +1,5 @@
1
+ <% if puzzle.attempts.empty? -%>
2
+ No attempts have been made for this puzzle.
3
+ <% else -%>
4
+ <%= Terminal::Table.new(title:, headings:, rows:) %>
5
+ <% end -%>
@@ -0,0 +1,58 @@
1
+ module AocCli
2
+ module Components
3
+ class AttemptsTable < Kangaru::Component
4
+ attr_reader :puzzle
5
+
6
+ def initialize(puzzle:)
7
+ @puzzle = puzzle
8
+ end
9
+
10
+ def title
11
+ "Advent of Code: #{puzzle.presenter.date}"
12
+ end
13
+
14
+ def headings
15
+ %w[Answer Status Time Hint]
16
+ end
17
+
18
+ def rows
19
+ [*level_one_rows, separator, *level_two_rows].compact
20
+ end
21
+
22
+ private
23
+
24
+ def level_one_rows
25
+ @level_one_rows ||= rows_for(level_one_attempts)
26
+ end
27
+
28
+ def separator
29
+ return if level_one_rows.empty? || level_two_rows.empty?
30
+
31
+ :separator
32
+ end
33
+
34
+ def level_two_rows
35
+ @level_two_rows ||= rows_for(level_two_attempts)
36
+ end
37
+
38
+ def level_one_attempts
39
+ puzzle.attempts_dataset.where(level: 1).order(:created_at).to_a
40
+ end
41
+
42
+ def level_two_attempts
43
+ puzzle.attempts_dataset.where(level: 2).order(:created_at).to_a
44
+ end
45
+
46
+ def rows_for(attempts)
47
+ attempts.map do |attempt|
48
+ [
49
+ attempt.answer,
50
+ attempt.presenter.status,
51
+ attempt.created_at,
52
+ attempt.presenter.hint
53
+ ]
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,19 @@
1
+
2
+ <%= heading(title) %> <<%= AocCli::VERSION.blue %>>
3
+
4
+ A command-line interface for the Advent of Code puzzles.
5
+
6
+ Features include downloading puzzles and inputs, solving puzzles and
7
+ tracking year progress from within the terminal.
8
+
9
+ This is an unofficial project with no affiliation to Advent of Code.
10
+
11
+ <% endpoints.each do |controller, data| %>
12
+ <%= heading(controller) %>
13
+
14
+ <%= data[:description] %>
15
+
16
+ Usage: <%= "aoc #{controller}".blue %> <%= "<command>".cyan %>
17
+
18
+ <%= commands_table(data[:commands], indent: 4) %>
19
+ <% end -%>
@@ -0,0 +1,41 @@
1
+ module AocCli
2
+ module Components
3
+ class DocsComponent < Kangaru::Component
4
+ include Helpers::ViewHelper
5
+
6
+ ENDPOINTS = {
7
+ event: {
8
+ description: "Handle event directories",
9
+ commands: {
10
+ init: "Create and initialise an event directory",
11
+ progress: "Check your progress for the current event"
12
+ }
13
+ },
14
+
15
+ puzzle: {
16
+ description: "Handle puzzle directories",
17
+ commands: {
18
+ init: "Fetch and initialise puzzles for the current event",
19
+ solve: "Submit and evaluate a puzzle solution",
20
+ sync: "Ensure puzzle files are up to date",
21
+ attempts: "Review previous attempts for the current puzzle"
22
+ }
23
+ }
24
+ }.freeze
25
+
26
+ def endpoints
27
+ ENDPOINTS
28
+ end
29
+
30
+ def title
31
+ "Advent of Code CLI"
32
+ end
33
+
34
+ def commands_table(commands, indent: 0)
35
+ rows = commands.transform_keys(&:to_s).to_a
36
+
37
+ table_for(*rows, gap: 4, indent:)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,8 @@
1
+ <% if messages.count == 1 -%>
2
+ <%= title.red %>: <%= messages.first %>
3
+ <% else -%>
4
+ <%= title.red %>:
5
+ <% messages.each do |message| -%>
6
+ <%= "\u2022 #{message}" %>
7
+ <% end -%>
8
+ <% end -%>
@@ -0,0 +1,36 @@
1
+ module AocCli
2
+ module Components
3
+ class ErrorsComponent < Kangaru::Component
4
+ attr_reader :messages
5
+
6
+ def initialize(*messages)
7
+ @messages = messages
8
+ end
9
+
10
+ # TODO: remove once Kangaru has native conditional rendering
11
+ def render
12
+ return unless render?
13
+
14
+ super
15
+ end
16
+
17
+ def self.from_model(model)
18
+ errors = model.errors.map(&:full_message)
19
+
20
+ new(*errors)
21
+ end
22
+
23
+ private
24
+
25
+ def render?
26
+ !messages.empty?
27
+ end
28
+
29
+ # TODO: move this to a const when Kangaru allows binding-agnostic
30
+ # component constants (like controllers).
31
+ def title
32
+ "Error"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1 @@
1
+ <%= Terminal::Table.new(headings:, rows:, title:) %>
@@ -0,0 +1,43 @@
1
+ module AocCli
2
+ module Components
3
+ class ProgressTable < Kangaru::Component
4
+ attr_reader :event
5
+
6
+ def initialize(event:)
7
+ @event = event
8
+ end
9
+
10
+ private
11
+
12
+ TITLE = "Advent of Code: %{year}".freeze
13
+ HEADINGS = %w[Day Progress].freeze
14
+ PROGRESS = "Day %{day}".freeze
15
+ TOTAL = "Total".freeze
16
+
17
+ def title
18
+ format(TITLE, year: event.year)
19
+ end
20
+
21
+ def headings
22
+ HEADINGS
23
+ end
24
+
25
+ def rows
26
+ [*progress_rows, :separator, total_row]
27
+ end
28
+
29
+ def progress_rows
30
+ 1.upto(25).map do |day|
31
+ [
32
+ format(PROGRESS, day:),
33
+ event.stats.presenter.progress_icons(day)
34
+ ]
35
+ end
36
+ end
37
+
38
+ def total_row
39
+ [TOTAL, event.stats.presenter.total_progress]
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,2 @@
1
+ Puzzle <%= puzzle.presenter.date %> dir synchronised
2
+ <%= table_for(*status_rows, indent: 2) -%>
@@ -0,0 +1,26 @@
1
+ module AocCli
2
+ module Components
3
+ class PuzzleSyncComponent < Kangaru::Component
4
+ extend Forwardable
5
+
6
+ include Helpers::ViewHelper
7
+
8
+ attr_reader :log
9
+
10
+ def_delegators :log, :puzzle
11
+
12
+ def initialize(log:)
13
+ @log = log
14
+ end
15
+
16
+ private
17
+
18
+ def status_rows
19
+ [
20
+ [puzzle.presenter.puzzle_filename, log.puzzle_status],
21
+ [puzzle.presenter.input_filename, log.input_status]
22
+ ]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,7 @@
1
+ module AocCli
2
+ module Configurators
3
+ class SessionConfigurator < Kangaru::Configurator
4
+ attr_accessor :token
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,27 @@
1
+ module AocCli
2
+ class ApplicationController < Kangaru::Controller
3
+ include Concerns::ErrorConcern
4
+ include Concerns::LocationConcern
5
+
6
+ include Helpers::ViewHelper
7
+
8
+ def execute
9
+ return handle_help_param! if params[:help]
10
+
11
+ super
12
+ rescue Core::Processor::Error => e
13
+ render_model_errors!(e.processor)
14
+ end
15
+
16
+ private
17
+
18
+ def handle_help_param!
19
+ params.delete(:help)
20
+
21
+ path = File.join("/help", request.path)
22
+ request = Kangaru::Request.new(path:, params:)
23
+
24
+ Kangaru.application!.router.resolve(request)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ module AocCli
2
+ module Concerns
3
+ module ErrorConcern
4
+ def render_error!(error)
5
+ Components::ErrorsComponent.new(error).render
6
+
7
+ false
8
+ end
9
+
10
+ def render_model_errors!(model)
11
+ Components::ErrorsComponent.from_model(model).render
12
+
13
+ false
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,79 @@
1
+ module AocCli
2
+ module Concerns
3
+ module LocationConcern
4
+ def current_path
5
+ File.expand_path(".")
6
+ end
7
+
8
+ def current_location
9
+ Location.first(path: current_path)
10
+ end
11
+
12
+ def current_event
13
+ case current_resource
14
+ when Event then current_resource
15
+ when Puzzle then current_resource.event
16
+ end
17
+ end
18
+
19
+ def current_puzzle
20
+ case current_resource
21
+ when Puzzle then current_resource
22
+ end
23
+ end
24
+
25
+ def ensure_in_event_dir!
26
+ return true if in_event_dir?
27
+
28
+ render_error!(ERRORS[:not_in_event])
29
+ end
30
+
31
+ def ensure_in_puzzle_dir!
32
+ return true if in_puzzle_dir?
33
+
34
+ render_error!(ERRORS[:not_in_puzzle])
35
+ end
36
+
37
+ def ensure_in_aoc_dir!
38
+ return true if in_aoc_dir?
39
+
40
+ render_error!(ERRORS[:not_in_aoc])
41
+ end
42
+
43
+ def ensure_not_in_aoc_dir!
44
+ return true unless in_aoc_dir?
45
+
46
+ render_error!(ERRORS[:in_aoc])
47
+ end
48
+
49
+ private
50
+
51
+ ERRORS = {
52
+ not_in_event:
53
+ "Action can't be performed outside event directory",
54
+ not_in_puzzle:
55
+ "Action can't be performed outside puzzle directory",
56
+ not_in_aoc:
57
+ "Action can't be performed outside Advent of Code directory",
58
+ in_aoc:
59
+ "Action can't be performed from Advent of Code directory"
60
+ }.freeze
61
+
62
+ def current_resource
63
+ current_location&.resource
64
+ end
65
+
66
+ def in_event_dir?
67
+ current_location&.event_dir? || false
68
+ end
69
+
70
+ def in_puzzle_dir?
71
+ current_location&.puzzle_dir? || false
72
+ end
73
+
74
+ def in_aoc_dir?
75
+ in_event_dir? || in_puzzle_dir?
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,9 @@
1
+ module AocCli
2
+ class DefaultController < ApplicationController
3
+ def default
4
+ Components::DocsComponent.new.render
5
+ end
6
+
7
+ alias help default
8
+ end
9
+ end
@@ -0,0 +1,35 @@
1
+ module AocCli
2
+ class EventController < ApplicationController
3
+ def init
4
+ return unless ensure_not_in_aoc_dir!
5
+
6
+ @event = Processors::EventInitialiser.run!(
7
+ year: target_id,
8
+ dir: params[:dir] || current_path
9
+ )
10
+ end
11
+
12
+ def attach
13
+ return render_error!("Event does not exist") if queried_event.nil?
14
+
15
+ @source = queried_event.location.path
16
+
17
+ @target = Processors::ResourceAttacher.run!(
18
+ resource: queried_event,
19
+ path: params[:dir] || current_path
20
+ ).path
21
+ end
22
+
23
+ def progress
24
+ return unless ensure_in_aoc_dir!
25
+
26
+ Components::ProgressTable.new(event: current_event || raise).render
27
+ end
28
+
29
+ private
30
+
31
+ def queried_event
32
+ @queried_event ||= Event.first(year: target_id)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ module AocCli
2
+ module Help
3
+ class EventController < ApplicationController
4
+ def init = true
5
+
6
+ def attach = true
7
+
8
+ def progress = true
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module AocCli
2
+ module Help
3
+ class PuzzleController < ApplicationController
4
+ def init = true
5
+
6
+ def solve = true
7
+
8
+ def sync = true
9
+
10
+ def attempts = true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ module AocCli
2
+ class PuzzleController < ApplicationController
3
+ def init
4
+ return unless ensure_in_event_dir!
5
+
6
+ @puzzle = Processors::PuzzleInitialiser.run!(
7
+ event: current_event,
8
+ day: target_id
9
+ )
10
+ end
11
+
12
+ def solve
13
+ return unless ensure_in_puzzle_dir!
14
+
15
+ @attempt = Processors::SolutionPoster.run!(
16
+ puzzle: current_puzzle,
17
+ answer: params[:answer]
18
+ )
19
+ end
20
+
21
+ def sync
22
+ return unless ensure_in_puzzle_dir!
23
+
24
+ @sync_log = Processors::PuzzleDirSynchroniser.run!(
25
+ puzzle: current_puzzle,
26
+ location: current_location,
27
+ skip_cache: params[:skip_cache] || false
28
+ )
29
+
30
+ Components::PuzzleSyncComponent.new(log: @sync_log).render
31
+ end
32
+
33
+ def attempts
34
+ return unless ensure_in_puzzle_dir!
35
+
36
+ Components::AttemptsTable.new(puzzle: current_puzzle || raise).render
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,69 @@
1
+ module AocCli
2
+ module Core
3
+ class AttemptParser
4
+ attr_reader :response
5
+
6
+ def initialize(response)
7
+ @response = response
8
+ end
9
+
10
+ def to_h
11
+ { status:, hint:, wait_time: }.compact
12
+ end
13
+
14
+ private
15
+
16
+ module Prefixes
17
+ CORRECT = /^That's the right answer/
18
+ INCORRECT = /^That's not the right answer/
19
+ RATE_LIMITED = /^You gave an answer too recently/
20
+ WRONG_LEVEL = /^You don't seem to be solving the right level/
21
+ end
22
+
23
+ module Hints
24
+ TOO_LOW = /your answer is too low/
25
+ TOO_HIGH = /your answer is too high/
26
+ end
27
+
28
+ module WaitTimes
29
+ ONE_MINUTE = /one minute/
30
+ INCORRECT_FORMAT = /(\d+) minutes/
31
+ RATE_LIMITED_FORMAT = /(?:(\d+)m)/
32
+ end
33
+
34
+ def status
35
+ case response
36
+ when Prefixes::CORRECT then :correct
37
+ when Prefixes::INCORRECT then :incorrect
38
+ when Prefixes::RATE_LIMITED then :rate_limited
39
+ when Prefixes::WRONG_LEVEL then :wrong_level
40
+ else raise "unexpected response"
41
+ end
42
+ end
43
+
44
+ def hint
45
+ case response
46
+ when Hints::TOO_LOW then :too_low
47
+ when Hints::TOO_HIGH then :too_high
48
+ end
49
+ end
50
+
51
+ def wait_time
52
+ case status
53
+ when :incorrect then scan_incorrect_wait_time!
54
+ when :rate_limited then scan_rated_limited_wait_time!
55
+ end
56
+ end
57
+
58
+ def scan_incorrect_wait_time!
59
+ return 1 if response.match?(WaitTimes::ONE_MINUTE)
60
+
61
+ response.scan(WaitTimes::INCORRECT_FORMAT).flatten.first.to_i
62
+ end
63
+
64
+ def scan_rated_limited_wait_time!
65
+ response.scan(WaitTimes::RATE_LIMITED_FORMAT).flatten.first.to_i
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,32 @@
1
+ module AocCli
2
+ module Core
3
+ class Processor
4
+ extend Forwardable
5
+
6
+ include Kangaru::Attributable
7
+ include Kangaru::Validatable
8
+
9
+ class Error < StandardError
10
+ attr_reader :processor
11
+
12
+ def initialize(processor)
13
+ @processor = processor
14
+ end
15
+ end
16
+
17
+ def run
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def run!
22
+ raise(Error, self) unless valid?
23
+
24
+ run
25
+ end
26
+
27
+ def self.run!(...)
28
+ new(...).run!
29
+ end
30
+ end
31
+ end
32
+ end