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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +88 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +4 -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
|