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,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