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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +88 -0
- data/.ruby-version +1 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +187 -0
- data/README.md +88 -282
- data/Rakefile +9 -2
- data/Steepfile +13 -0
- data/aoc_cli.gemspec +36 -26
- data/db/migrate/1_create_events.rb +14 -0
- data/db/migrate/2_create_puzzles.rb +21 -0
- data/db/migrate/3_create_stats.rb +19 -0
- data/db/migrate/4_create_attempts.rb +19 -0
- data/db/migrate/5_create_locations.rb +17 -0
- data/db/migrate/6_create_puzzle_dir_sync_logs.rb +18 -0
- data/exe/aoc +5 -0
- data/lib/aoc_cli/components/attempts_table.erb +5 -0
- data/lib/aoc_cli/components/attempts_table.rb +58 -0
- data/lib/aoc_cli/components/docs_component.erb +19 -0
- data/lib/aoc_cli/components/docs_component.rb +41 -0
- data/lib/aoc_cli/components/errors_component.erb +8 -0
- data/lib/aoc_cli/components/errors_component.rb +36 -0
- data/lib/aoc_cli/components/progress_table.erb +1 -0
- data/lib/aoc_cli/components/progress_table.rb +43 -0
- data/lib/aoc_cli/components/puzzle_sync_component.erb +2 -0
- data/lib/aoc_cli/components/puzzle_sync_component.rb +26 -0
- data/lib/aoc_cli/configurators/session_configurator.rb +7 -0
- data/lib/aoc_cli/controllers/application_controller.rb +27 -0
- data/lib/aoc_cli/controllers/concerns/error_concern.rb +17 -0
- data/lib/aoc_cli/controllers/concerns/location_concern.rb +79 -0
- data/lib/aoc_cli/controllers/default_controller.rb +9 -0
- data/lib/aoc_cli/controllers/event_controller.rb +35 -0
- data/lib/aoc_cli/controllers/help/event_controller.rb +11 -0
- data/lib/aoc_cli/controllers/help/puzzle_controller.rb +13 -0
- data/lib/aoc_cli/controllers/puzzle_controller.rb +39 -0
- data/lib/aoc_cli/core/attempt_parser.rb +69 -0
- data/lib/aoc_cli/core/processor.rb +32 -0
- data/lib/aoc_cli/core/repository.rb +74 -0
- data/lib/aoc_cli/core/request.rb +37 -0
- data/lib/aoc_cli/core/resource.rb +39 -0
- data/lib/aoc_cli/core/stats_parser.rb +43 -0
- data/lib/aoc_cli/helpers/table_generator.rb +64 -0
- data/lib/aoc_cli/helpers/view_helper.rb +35 -0
- data/lib/aoc_cli/models/attempt.rb +67 -0
- data/lib/aoc_cli/models/event.rb +9 -0
- data/lib/aoc_cli/models/location.rb +24 -0
- data/lib/aoc_cli/models/puzzle.rb +28 -0
- data/lib/aoc_cli/models/puzzle_dir_sync_log.rb +14 -0
- data/lib/aoc_cli/models/stats.rb +43 -0
- data/lib/aoc_cli/presenters/attempt_presenter.rb +29 -0
- data/lib/aoc_cli/presenters/puzzle_presenter.rb +29 -0
- data/lib/aoc_cli/presenters/stats_presenter.rb +32 -0
- data/lib/aoc_cli/processors/event_initialiser.rb +64 -0
- data/lib/aoc_cli/processors/puzzle_dir_synchroniser.rb +80 -0
- data/lib/aoc_cli/processors/puzzle_initialiser.rb +91 -0
- data/lib/aoc_cli/processors/puzzle_refresher.rb +27 -0
- data/lib/aoc_cli/processors/resource_attacher.rb +22 -0
- data/lib/aoc_cli/processors/solution_poster.rb +72 -0
- data/lib/aoc_cli/processors/stats_initialiser.rb +36 -0
- data/lib/aoc_cli/processors/stats_refresher.rb +23 -0
- data/lib/aoc_cli/validators/collection_type_validator.rb +57 -0
- data/lib/aoc_cli/validators/event_year_validator.rb +42 -0
- data/lib/aoc_cli/validators/included_validator.rb +21 -0
- data/lib/aoc_cli/validators/integer_validator.rb +32 -0
- data/lib/aoc_cli/validators/path_validator.rb +39 -0
- data/lib/aoc_cli/validators/type_validator.rb +54 -0
- data/lib/aoc_cli/version.rb +1 -1
- data/lib/aoc_cli/views/event/attach.erb +3 -0
- data/lib/aoc_cli/views/event/init.erb +3 -0
- data/lib/aoc_cli/views/help/event/attach.erb +32 -0
- data/lib/aoc_cli/views/help/event/init.erb +38 -0
- data/lib/aoc_cli/views/help/event/progress.erb +12 -0
- data/lib/aoc_cli/views/help/puzzle/attempts.erb +12 -0
- data/lib/aoc_cli/views/help/puzzle/init.erb +31 -0
- data/lib/aoc_cli/views/help/puzzle/solve.erb +33 -0
- data/lib/aoc_cli/views/help/puzzle/sync.erb +32 -0
- data/lib/aoc_cli/views/puzzle/init.erb +3 -0
- data/lib/aoc_cli/views/puzzle/solve.erb +10 -0
- data/lib/aoc_cli.rb +32 -16
- data/rbs_collection.lock.yaml +168 -0
- data/rbs_collection.yaml +28 -0
- data/sig/aoc_cli/components/attempts_table.rbs +29 -0
- data/sig/aoc_cli/components/docs_component.rbs +15 -0
- data/sig/aoc_cli/components/errors_component.rbs +19 -0
- data/sig/aoc_cli/components/progress_table.rbs +28 -0
- data/sig/aoc_cli/components/puzzle_sync_component.rbs +19 -0
- data/sig/aoc_cli/configurators/session_configurator.rbs +7 -0
- data/sig/aoc_cli/controllers/application_controller.rbs +12 -0
- data/sig/aoc_cli/controllers/concerns/error_concern.rbs +9 -0
- data/sig/aoc_cli/controllers/concerns/location_concern.rbs +35 -0
- data/sig/aoc_cli/controllers/default_controller.rbs +7 -0
- data/sig/aoc_cli/controllers/event_controller.rbs +18 -0
- data/sig/aoc_cli/controllers/help/event_controller.rbs +11 -0
- data/sig/aoc_cli/controllers/help/puzzle_controller.rbs +13 -0
- data/sig/aoc_cli/controllers/puzzle_controller.rbs +15 -0
- data/sig/aoc_cli/core/attempt_parser.rbs +41 -0
- data/sig/aoc_cli/core/processor.rbs +25 -0
- data/sig/aoc_cli/core/repository.rbs +25 -0
- data/sig/aoc_cli/core/request.rbs +29 -0
- data/sig/aoc_cli/core/resource.rbs +25 -0
- data/sig/aoc_cli/core/stats_parser.rbs +23 -0
- data/sig/aoc_cli/helpers/table_generator.rbs +36 -0
- data/sig/aoc_cli/helpers/view_helper.rbs +15 -0
- data/sig/aoc_cli/models/attempt.rbs +35 -0
- data/sig/aoc_cli/models/event.rbs +17 -0
- data/sig/aoc_cli/models/location.rbs +19 -0
- data/sig/aoc_cli/models/puzzle.rbs +28 -0
- data/sig/aoc_cli/models/puzzle_dir_sync_log.rbs +11 -0
- data/sig/aoc_cli/models/stats.rbs +53 -0
- data/sig/aoc_cli/presenters/attempt_presenter.rbs +13 -0
- data/sig/aoc_cli/presenters/puzzle_presenter.rbs +19 -0
- data/sig/aoc_cli/presenters/stats_presenter.rbs +19 -0
- data/sig/aoc_cli/processors/event_initialiser.rbs +26 -0
- data/sig/aoc_cli/processors/puzzle_dir_synchroniser.rbs +40 -0
- data/sig/aoc_cli/processors/puzzle_initialiser.rbs +37 -0
- data/sig/aoc_cli/processors/puzzle_refresher.rbs +21 -0
- data/sig/aoc_cli/processors/resource_attacher.rbs +16 -0
- data/sig/aoc_cli/processors/solution_poster.rbs +34 -0
- data/sig/aoc_cli/processors/stats_initialiser.rbs +15 -0
- data/sig/aoc_cli/processors/stats_refresher.rbs +19 -0
- data/sig/aoc_cli/validators/collection_type_validator.rbs +24 -0
- data/sig/aoc_cli/validators/event_year_validator.rbs +19 -0
- data/sig/aoc_cli/validators/included_validator.rbs +11 -0
- data/sig/aoc_cli/validators/integer_validator.rbs +15 -0
- data/sig/aoc_cli/validators/path_validator.rbs +17 -0
- data/sig/aoc_cli/validators/type_validator.rbs +22 -0
- data/sig/aoc_cli.rbs +6 -0
- data/sig/http.rbs +3 -0
- data/sig/kangaru.rbs +5 -0
- data/sig/nokogiri.rbs +3 -0
- data/sig/reverse_markdown.rbs +3 -0
- metadata +142 -34
- data/.gitignore +0 -5
- data/bin/aoc +0 -4
- data/bin/console +0 -15
- data/bin/setup +0 -7
- data/lib/aoc_cli/commands.rb +0 -232
- data/lib/aoc_cli/database.rb +0 -224
- data/lib/aoc_cli/day.rb +0 -124
- data/lib/aoc_cli/db/reddit.db +0 -0
- data/lib/aoc_cli/errors.rb +0 -275
- data/lib/aoc_cli/files.rb +0 -163
- data/lib/aoc_cli/help.rb +0 -77
- data/lib/aoc_cli/interface.rb +0 -81
- data/lib/aoc_cli/paths.rb +0 -101
- data/lib/aoc_cli/solve.rb +0 -104
- data/lib/aoc_cli/tables.rb +0 -138
- data/lib/aoc_cli/tools.rb +0 -120
- data/lib/aoc_cli/year.rb +0 -116
- 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,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,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,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,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,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,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
|