advent_of_ruby 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a387ce043139b5090d50ac18e76a0687f248fa75dc7e4ba098780f9e739b8687
4
- data.tar.gz: e805613b4a188fafa0a0d17499893abb2edd26e5e33c2367fa717a9aa8a827c9
3
+ metadata.gz: ed16b9db7d2113dc310e48d6fc3792c673dfdfeab2259ad4c8af67797b4c7c6b
4
+ data.tar.gz: 6cde22c6e469c4c774d0ba336a6f37dfacedf2f98e421480153ddc9d875369b1
5
5
  SHA512:
6
- metadata.gz: a731b8c4449af8c8d89a542bc4979ed96edb8972802177709425e62ac8270c1c89fddf71f2c3239ebc430693fdc917b633f53f2006d59c5ce16e027530621203
7
- data.tar.gz: 1c87883119e89399b0c7dc6c41a7472142c863d605bca45eafeb2d8b32a51fae11f77474655bef263d1e0696d9ad49d1e8bb84cb406dfb8cf1554390e7b99641
6
+ metadata.gz: 77e2f6d78f40aec3b30f625fce9367af74612c015111cab5f11ee207ff150b4feadbe21cff287e8f372b68fa711c3d81f17ac5b923be8bef9b991bf0781514d8
7
+ data.tar.gz: 1274475154d99500503228440120a0322d83832f0c30f39ffdbbc56bcc29d54d258ef0c6e4f8914617818183682b4a2f1c5525581c1803d3f611b90a35f00f11
data/bin/arb CHANGED
@@ -11,19 +11,13 @@ class ArbApp < Thor
11
11
  Arb::Cli.bootstrap(year:, day:)
12
12
  end
13
13
 
14
- # TODO add a [VARIANT] arg, so that additional versions of the same solution
15
- # can co-exist as additional methods, e.g.:
16
- #
17
- # #part_1_first # for my initial attempt
18
- # #part_1_concise # for a code-golf type solution
19
- # #part_1_alt # for an alternative approach
20
14
  desc "run [YEAR] [DAY]", "Runs the puzzle that's untracked in Git, or the " \
21
15
  "puzzle of the given day and year. `-s` runs only specs, `-o` part 1, `-t` part 2."
22
16
  method_option :spec, type: :boolean, aliases: "-s"
23
- method_option :real_part_1, type: :boolean, aliases: "-o"
24
- method_option :real_part_2, type: :boolean, aliases: "-t"
25
- def run_day(year = nil, day = nil)
26
- Arb::Cli.run_day(year:, day:, options:)
17
+ method_option :one, type: :boolean, aliases: "-o"
18
+ method_option :two, type: :boolean, aliases: "-t"
19
+ def run_day(year = nil, day = nil) # can't be named `run` due to Thor conflict
20
+ Arb::Cli.run(year:, day:, options:)
27
21
  end
28
22
 
29
23
  desc "progress", "Shows progress based on the number of your solutions " \
data/lib/arb/api/aoc.rb CHANGED
@@ -3,7 +3,7 @@ module Arb
3
3
  class Aoc
4
4
  private attr_reader :connection
5
5
 
6
- def initialize(cookie)
6
+ def initialize(cookie:)
7
7
  @connection = Faraday.new(
8
8
  url: "https://adventofcode.com",
9
9
  headers: {
@@ -13,22 +13,22 @@ module Arb
13
13
  )
14
14
  end
15
15
 
16
- def input(year, day)
16
+ def input(year:, day:)
17
17
  logged_in {
18
- connection.get("/#{year}/day/#{day}/input")
18
+ connection.get("/#{year}/day/#{day.delete_prefix("0")}/input")
19
19
  }
20
20
  end
21
21
 
22
- def instructions(year, day)
22
+ def instructions(year:, day:)
23
23
  logged_in {
24
- connection.get("/#{year}/day/#{day}")
24
+ connection.get("/#{year}/day/#{day.delete_prefix("0")}")
25
25
  }
26
26
  end
27
27
 
28
- def submit(year, day, part, answer)
28
+ def submit(year:, day:, part:, answer:)
29
29
  logged_in {
30
30
  connection.post(
31
- "/#{year}/day/#{day}/answer",
31
+ "/#{year}/day/#{day.delete_prefix("0")}/answer",
32
32
  "level=#{part}&answer=#{answer}",
33
33
  )
34
34
  }
@@ -6,37 +6,37 @@ module Arb
6
6
  private attr_reader :connection
7
7
 
8
8
  PATHS = {
9
- "eregon" => ->(year, day, part) {
9
+ "eregon" => ->(year:, day:, part:) {
10
10
  if part == "1"
11
11
  [
12
- "adventofcode/tree/master/#{year}/#{day}.rb",
13
- "adventofcode/tree/master/#{year}/#{day}a.rb",
12
+ "adventofcode/tree/master/#{year}/#{day.delete_prefix("0")}.rb",
13
+ "adventofcode/tree/master/#{year}/#{day.delete_prefix("0")}a.rb",
14
14
  ]
15
15
  elsif part == "2"
16
- ["adventofcode/tree/master/#{year}/#{day}b.rb"]
16
+ ["adventofcode/tree/master/#{year}/#{day.delete_prefix("0")}b.rb"]
17
17
  end
18
18
  },
19
- "gchan" => ->(year, day, part) {
20
- ["advent-of-code-ruby/tree/main/#{year}/day-#{day.rjust(2, "0")}/day-#{day.rjust(2, "0")}-part-#{part}.rb"]
19
+ "gchan" => ->(year:, day:, part:) {
20
+ ["advent-of-code-ruby/tree/main/#{year}/day-#{day}/day-#{day}-part-#{part}.rb"]
21
21
  },
22
- "ahorner" => ->(year, day, part) {
22
+ "ahorner" => ->(year:, day:, part:) {
23
23
  return [] if part == "1"
24
- ["advent-of-code/tree/main/lib/#{year}/#{day.rjust(2, "0")}.rb"]
24
+ ["advent-of-code/tree/main/lib/#{year}/#{day}.rb"]
25
25
  },
26
- "ZogStriP" => ->(year, day, part) {
26
+ "ZogStriP" => ->(year:, day:, part:) {
27
27
  return [] if part == "1"
28
28
 
29
- puzzle_name = File.read(Files::Instructions.path(year, day))
29
+ puzzle_name = File.read(Files::Instructions.path(year:, day:))
30
30
  .match(/## --- Day \d\d?: (.+)(?= ---)/)
31
31
  .captures
32
32
  .first
33
33
  .downcase
34
34
  .gsub(" ", "_")
35
35
 
36
- ["adventofcode/tree/master/#{year}/#{day.rjust(2, "0")}_#{puzzle_name}.rb"]
36
+ ["adventofcode/tree/master/#{year}/#{day}_#{puzzle_name}.rb"]
37
37
  },
38
- "erikw" => ->(year, day, part) {
39
- ["advent-of-code-solutions/tree/main/#{year}/#{day.rjust(2, "0")}/part#{part}.rb"]
38
+ "erikw" => ->(year:, day:, part:) {
39
+ ["advent-of-code-solutions/tree/main/#{year}/#{day}/part#{part}.rb"]
40
40
  },
41
41
  }
42
42
 
@@ -64,13 +64,13 @@ module Arb
64
64
  )
65
65
  end
66
66
 
67
- def other_solutions(year, day, part)
67
+ def other_solutions(year:, day:, part:)
68
68
  "# #{year} Day #{day} Part #{part}\n\n" +
69
69
  PATHS
70
70
  .map { |username, path_builder|
71
71
  actual_path = nil
72
72
  solution = nil
73
- paths = path_builder.call(year, day, part)
73
+ paths = path_builder.call(year:, day:, part:)
74
74
 
75
75
  paths.each do |path|
76
76
  next if solution
@@ -82,14 +82,14 @@ module Arb
82
82
  end
83
83
 
84
84
  if solution
85
- <<~TPL
85
+ <<~SOLUTION
86
86
  # ------------------------------------------------------------------------------
87
87
  # #{username}: #{UI_URI}/#{username}/#{actual_path}
88
88
  # ------------------------------------------------------------------------------
89
89
 
90
90
  #{solution}
91
91
 
92
- TPL
92
+ SOLUTION
93
93
  end
94
94
  }
95
95
  .compact
data/lib/arb/arb.rb CHANGED
@@ -3,6 +3,7 @@ require "date"
3
3
  require "debug"
4
4
  require "dotenv"
5
5
  require "faraday"
6
+ require "open3"
6
7
  require "pastel"
7
8
  require "reverse_markdown"
8
9
  require "rspec/core"
@@ -16,8 +17,3 @@ PASTEL = Pastel.new
16
17
  Dir[File.join(__dir__, "**", "*.rb")].each do |file|
17
18
  require file
18
19
  end
19
-
20
- solution_files = File.join(Dir.pwd, "src", "**", "*.rb")
21
- Dir[solution_files].each do |file|
22
- require file
23
- end
@@ -7,19 +7,20 @@ module Arb
7
7
 
8
8
  year, day = YearDayValidator.validate_year_and_day(year:, day:)
9
9
 
10
- instructions_path = Files::Instructions.download(year, day)
11
- others_1_path, others_2_path = Files::OtherSolutions.download(year, day)
12
- input_path = Files::Input.download(year, day)
13
- source_path = Files::Source.create(year, day)
14
- spec_path = Files::Spec.create(year, day)
10
+ instructions_path = Files::Instructions.download(year:, day:)
11
+ others_1_path, others_2_path = Files::OtherSolutions.download(year:, day:)
12
+ input_path = Files::Input.download(year:, day:)
13
+ solution_path = Files::Solution.create(year:, day:)
14
+ spec_path = Files::Spec.create(year:, day:)
15
15
 
16
- puts "🤘 Bootstrapped #{year}##{day}\n\n"
16
+ puts "🤘 Bootstrapped #{year}##{day}"
17
+ puts
17
18
 
18
19
  # Open the new files.
19
20
  `#{ENV["EDITOR_COMMAND"]} #{others_1_path}`
20
21
  `#{ENV["EDITOR_COMMAND"]} #{others_2_path}`
21
22
  `#{ENV["EDITOR_COMMAND"]} #{input_path}`
22
- `#{ENV["EDITOR_COMMAND"]} #{source_path}`
23
+ `#{ENV["EDITOR_COMMAND"]} #{solution_path}`
23
24
  `#{ENV["EDITOR_COMMAND"]} #{spec_path}`
24
25
  `#{ENV["EDITOR_COMMAND"]} #{instructions_path}`
25
26
 
@@ -27,7 +28,7 @@ module Arb
27
28
  puts "Now fill in the spec for Part One with an example from the instructions, " \
28
29
  "then run it with `#{PASTEL.blue.bold("arb run")}` (or just `arb`) as " \
29
30
  "you implement the solution. When the spec passes, your solution will " \
30
- "be run with the real input and you'll be prompted to submit your solution.\n"
31
+ "be run with the real input and you'll be prompted to submit your solution."
31
32
  end
32
33
  rescue AppError => e
33
34
  puts Pastel.new.red(e.message)
@@ -3,32 +3,41 @@ module Arb
3
3
  def self.commit
4
4
  WorkingDirectory.prepare!
5
5
 
6
- change_year, change_day = Git.new_solutions.first
7
- unless change_year
6
+ puzzles = Git.modified_puzzles
7
+ if puzzles.any?
8
8
  files_modified = true
9
- change_year, change_day = Git.modified_solutions.first
9
+ else
10
+ puzzles = Git.uncommitted_puzzles
10
11
  end
11
12
 
12
- unless change_year
13
+ if puzzles.empty?
13
14
  puts "Nothing to commit."
14
15
 
15
16
  if Git.commit_count <= 2
16
- puts "\nRun `#{PASTEL.blue.bold("arb bootstrap")}` (or `arb b`) to start the next puzzle."
17
+ puts
18
+ puts "Run `#{PASTEL.blue.bold("arb bootstrap")}` (or `arb b`) to start the next puzzle."
17
19
  end
18
20
 
19
21
  return
20
22
  end
21
23
 
22
- message = "#{files_modified ? "Improve" : "Solve"} #{change_year}##{change_day}"
23
- Git.commit_all!(message:)
24
+ puzzles.each do |(year, day), filenames|
25
+ message = "#{files_modified ? "Improve" : "Solve"} #{year}##{day}"
26
+ Git.commit!(filenames:, message:)
24
27
 
25
- # TODO less naive check: ensure prev. days are finished too
26
- if !files_modified && change_day == "25"
27
- puts "\n🎉 You've finished #{change_year}!\n\n"
28
+ # TODO less naive check: ensure prev. days are finished too
29
+ if !files_modified && day == "25"
30
+ puts
31
+ puts "🎉 You've finished #{year}!"
32
+ puts
33
+ end
34
+
35
+ puts "Puzzle #{year}##{day}#{" (modified)" if files_modified} committed 🎉"
28
36
  end
29
37
 
30
38
  if Git.commit_count <= 1
31
- puts "Solution committed! When you're ready to start the next puzzle, run " \
39
+ puts
40
+ puts "When you're ready to start the next puzzle, run " \
32
41
  "`#{PASTEL.blue.bold("arb bootstrap")}` (or `arb b`)."
33
42
  end
34
43
  end
@@ -14,8 +14,10 @@ module Arb
14
14
  total_percent = (my_total_count.to_f / total_count * 100).round(1)
15
15
  total_percent = total_percent == total_percent.to_i ? total_percent.to_i : total_percent
16
16
 
17
- puts "You have completed:\n\n"
18
- puts PASTEL.bold "#{PASTEL.blue("All:")}\t#{total_percent}% \t#{my_total_count}/#{total_count} puzzles\n"
17
+ puts "You have completed:"
18
+ puts
19
+ puts PASTEL.bold "#{PASTEL.blue("All:")}\t#{total_percent}% \t#{my_total_count}/#{total_count} puzzles"
20
+ puts
19
21
 
20
22
  my_counts_by_year.each do |year, my_count|
21
23
  if year.to_i == Date.today.year
@@ -0,0 +1,171 @@
1
+ module Arb
2
+ module Cli
3
+ # @param year [String, Integer]
4
+ # @param day [String, Integer]
5
+ # @param options [Hash] see `method_option`s above ArbApp#run_day.
6
+ def self.run(year:, day:, options:)
7
+ WorkingDirectory.prepare!
8
+
9
+ if options[:spec] && (options[:one] || options[:two])
10
+ raise InputError, "Don't use --spec (-s) with --one (-o) or --two (-t)"
11
+ end
12
+
13
+ year, day = YearDayValidator.validate_year_and_day(year:, day:, default_to_untracked_or_last_committed: true)
14
+
15
+ if Git.uncommitted_puzzles.empty? && !Git.last_committed_puzzle(year:)
16
+ bootstrap(year:, day:)
17
+ return
18
+ end
19
+
20
+ instructions_path = Files::Instructions.download(year:, day:, notify_exists: false, overwrite: false)
21
+ instructions = File.read(instructions_path)
22
+ correct_answer_1, correct_answer_2 = instructions.scan(/Your puzzle answer was `([^`]+)`./).flatten
23
+ skip_count = 0
24
+
25
+ if options[:spec]
26
+ run_specs_only(year:, day:)
27
+ return
28
+ elsif !(options[:one] || options[:two])
29
+ specs_passed, skip_count = run_specs_before_real(year:, day:)
30
+ return unless specs_passed
31
+ puts "👍 Specs passed!"
32
+ if skip_count > 1 || (skip_count == 1 && correct_answer_1)
33
+ puts PASTEL.yellow.bold("🤐 #{skip_count} skipped, however")
34
+ end
35
+ puts
36
+ end
37
+
38
+ Files::Input.download(year:, day:, notify_exists: false)
39
+ answers_1, answers_2 = [], []
40
+
41
+ begin
42
+ if options[:one] || (!options[:two] && ((correct_answer_1.nil? && skip_count <= 1) || correct_answer_2))
43
+ answers_1 = Runner.run_part(year:, day:, part: "1", correct_answer: correct_answer_1)
44
+ end
45
+ if options[:two] || (!options[:one] && ((correct_answer_1 && !correct_answer_2 && skip_count.zero?) || correct_answer_2))
46
+ if answers_1.count > 1
47
+ puts "------------"
48
+ puts
49
+ end
50
+
51
+ answers_2 = Runner.run_part(year:, day:, part: "2",correct_answer: correct_answer_2)
52
+ end
53
+ rescue Runner::SolutionNotFoundError
54
+ puts PASTEL.red("Solution class not found. Make sure this class exists: #{PASTEL.bold("Year#{year}::Day#{day}")}")
55
+ rescue Runner::SolutionArgumentError
56
+ puts PASTEL.red("ArgumentError when running your solution. Make sure every method has a one parameter (the input file).")
57
+ rescue Runner::SolutionArgumentError
58
+ end
59
+
60
+ answer_1, answer_2 = answers_1.compact.first, answers_2.compact.first
61
+
62
+ return unless answer_1 || answer_2
63
+
64
+ if correct_answer_2
65
+ puts "🙌 You've already submitted the answers to both parts.\n"
66
+
67
+ if Git.commit_count <= 1
68
+ puts "When you're done with this puzzle, run " \
69
+ "`#{PASTEL.blue.bold("arb commit")}` (or `arb c`) commit your solution to Git.\n"
70
+ end
71
+
72
+ return
73
+ elsif options[:one] && correct_answer_1
74
+ puts "🙌 You've already submitted the answer to this part.\n\n"
75
+ return
76
+ end
77
+
78
+ puts "Submit solution? (Y/n)"
79
+ print PASTEL.green("> ")
80
+ submit = STDIN.gets.strip.downcase
81
+ puts
82
+
83
+ if submit == "y" || submit == ""
84
+ options_part = options[:one] ? "1" : (options[:two] ? "2" : nil)
85
+ inferred_part = correct_answer_1.nil? ? "1" : "2"
86
+ aoc_api = Api::Aoc.new(cookie: ENV["AOC_COOKIE"])
87
+
88
+ response = aoc_api.submit(year:, day:, part: options_part || inferred_part, answer: answer_2 || answer_1)
89
+ message = response.match(/(?<=<article>).+(?=<\/article>)/m).to_s.strip
90
+ markdown_message = ReverseMarkdown.convert(message)
91
+ short_message = markdown_message
92
+ .sub(/\n\n.+/m, "")
93
+ .sub(/ \[\[.+/, "")
94
+
95
+ if short_message.start_with?("That's the right answer!")
96
+ puts
97
+ puts "⭐ #{short_message}"
98
+
99
+ # TODO don't re-download if the instructions file already contains the next part
100
+ instructions_path = Files::Instructions.download(year:, day:, overwrite: true)
101
+
102
+ if (options_part || inferred_part) == "1"
103
+ puts "Downloaded instructions for Part Two."
104
+ `#{ENV["EDITOR_COMMAND"]} #{instructions_path}`
105
+
106
+ spec_path = Files::Spec.create(year:, day:, notify_exists: false)
107
+ spec = File.read(spec_path)
108
+ spec_without_skips = spec.gsub(" xit ", " it ")
109
+ File.write(spec_path, spec_without_skips)
110
+ end
111
+
112
+ if Git.commit_count <= 1
113
+ puts
114
+ puts "Now it's time to improve your solution! Be sure to look " \
115
+ "at other people's solutions (in the \"others\" directory). When " \
116
+ "you're done, run `#{PASTEL.blue.bold("arb commit")}` (or `arb c`) " \
117
+ "to commit your solution to Git.\n"
118
+ end
119
+ else
120
+ puts "❌ #{short_message}"
121
+ end
122
+ end
123
+ rescue AppError => e
124
+ puts PASTEL.red(e.message)
125
+ end
126
+
127
+ private_class_method def self.run_specs_only(year:, day:)
128
+ spec_filename = File.join("spec", year, "#{day}_spec.rb")
129
+
130
+ # Running RSpec from within RSpec causes problems, so in the test environment
131
+ # run RSpec in a subprocess.
132
+ if ENV["TEST_ENV"]
133
+ system "rspec #{spec_filename}"
134
+ else
135
+ RSpec::Core::Runner.run([spec_filename])
136
+ end
137
+ end
138
+
139
+ private_class_method def self.run_specs_before_real(year:, day:)
140
+ spec_filename = File.join("spec", year, "#{day}_spec.rb")
141
+
142
+ # Running RSpec from within RSpec causes problems, so in the test environment
143
+ # run RSpec in a subprocess.
144
+ if ENV["TEST_ENV"]
145
+ stdout, _status = Open3.capture2("rspec #{spec_filename} --color --tty")
146
+ else
147
+ stdout = StringIO.new
148
+
149
+ RSpec.configure do |config|
150
+ config.color = true
151
+ config.tty = true
152
+ config.output_stream = stdout
153
+ end
154
+
155
+ RSpec::Core::Runner.run([spec_filename])
156
+ stdout = stdout.string
157
+ end
158
+
159
+ if stdout.include? "Failures:"
160
+ puts stdout
161
+
162
+ [false, nil]
163
+ elsif stdout.include?("Finished in ") && stdout.include?("0 failures")
164
+ [true, stdout.scan("skipped with xit").count]
165
+ else
166
+ uncolorized_stdout = stdout.gsub(/\e\[\d+m/, "")
167
+ raise uncolorized_stdout
168
+ end
169
+ end
170
+ end
171
+ end
@@ -1,23 +1,36 @@
1
1
  module Arb
2
2
  module Cli
3
3
  class Git
4
- # Years and days of uncommitted new solutions, or an empty array.
4
+ # A hash whose keys are ["<year>", "<day>"] of new solutions or specs, and
5
+ # and whose values are the names of the modified files (solution, spec, or both).
5
6
  # @return [Array<Array(String, String)>]
6
- def self.new_solutions
7
+ def self.uncommitted_puzzles
7
8
  output = `git status -su | grep -e "^?? src" -e "^?? spec" -e "^A src" -e "^A spec"`
8
- output.scan(/(?<year>\d{4})\/(?<day>\d\d)(?:_spec)?.rb$/).uniq
9
+ filenames = output.scan(/(?:src|spec)\/\d{4}\/\d\d(?:_spec)?.rb/).uniq
10
+ filenames.group_by { |filename|
11
+ filename
12
+ .match(/(?<year>\d{4})\/(?<day>\d\d)(?:_spec)?.rb$/)
13
+ .captures
14
+ }
9
15
  end
10
16
 
11
- # Years and days of modified existing solutions, or an empty array.
12
- # @return [Array<Array(String, String)>]
13
- def self.modified_solutions
17
+ # A hash whose keys are ["<year>", "<day>"] of modified existing solutions
18
+ # or specs, and whose values are the names of the modified files
19
+ # (solution, spec, or both).
20
+ # @return [Hash{Array(String, String), Array<String>}]
21
+ def self.modified_puzzles
14
22
  output = `git status -su | grep -e "^ M src" -e "^ M spec" -e "^M src" -e "^M spec"`
15
- output.scan(/(?<year>\d{4})\/(?<day>\d\d)(?:_spec)?.rb$/).uniq
23
+ filenames = output.scan(/(?:src|spec)\/\d{4}\/\d\d(?:_spec)?.rb/).uniq
24
+ filenames.group_by { |filename|
25
+ filename
26
+ .match(/(?<year>\d{4})\/(?<day>\d\d)(?:_spec)?.rb$/)
27
+ .captures
28
+ }
16
29
  end
17
30
 
18
31
  # Year and day of the latest-date solution in the most recent commit, or nil.
19
32
  # @return [Array(String, String), nil]
20
- def self.last_committed_solution(year: nil, exclude_year: nil)
33
+ def self.last_committed_puzzle(year: nil, exclude_year: nil)
21
34
  if exclude_year
22
35
  output = `git log -n 1 --diff-filter=A --name-only --pretty=format: -- src spec ':!src/#{exclude_year}' ':!spec/#{exclude_year}'`
23
36
  else
@@ -57,10 +70,13 @@ module Arb
57
70
 
58
71
  previous_days.map { |year, days|
59
72
  days_hash = days.map { |day|
60
- [day, committed_days.has_key?(year) && committed_days[year].include?(day)]
73
+ [
74
+ day.to_s.rjust(2, "0"),
75
+ committed_days.has_key?(year) && committed_days[year].include?(day),
76
+ ]
61
77
  }.to_h
62
78
 
63
- [year, days_hash]
79
+ [year.to_s, days_hash]
64
80
  }.to_h
65
81
  end
66
82
 
@@ -73,6 +89,11 @@ module Arb
73
89
  `git init`
74
90
  end
75
91
 
92
+ def self.commit!(filenames:, message:)
93
+ `git add #{filenames.join(" ")}`
94
+ `git commit -m "#{message}"`
95
+ end
96
+
76
97
  def self.commit_all!(message:)
77
98
  `git add -A`
78
99
  `git commit -m "#{message}"`
@@ -1,17 +1,41 @@
1
1
  module Arb
2
2
  module Cli
3
3
  class Runner
4
- def self.load_solution(year, day)
5
- padded_day = day.rjust(2, "0")
6
- Module.const_get("Year#{year}").const_get("Day#{padded_day}").new
7
- rescue NameError
8
- puts "There is no solution for this puzzle"
9
- end
4
+ SolutionNotFoundError = Class.new(StandardError)
5
+ SolutionArgumentError = Class.new(StandardError)
6
+
7
+ def self.run_part(year:, day:, part:, correct_answer:)
8
+ solution_class = solution_class(year:, day:)
9
+ base_method_name = "part_#{part}"
10
+ variant_method_names = solution_class
11
+ .instance_methods(false)
12
+ .filter { _1.to_s.start_with?(base_method_name)}
13
+ .sort
14
+
15
+ if variant_method_names.empty?
16
+ puts PASTEL.red("🤔 Couldn't find the method #{PASTEL.bold("Year#{year}::Day#{day}##{base_method_name}")}.")
17
+ return
18
+ end
19
+
20
+ answers = []
21
+
22
+ variant_method_names.each do |variant_method_name|
23
+ solution = solution_class.new
24
+ input_file = File.new(Files::Input.path(year:, day:))
25
+ answer = nil
26
+
27
+ begin
28
+ run_time = Benchmark.realtime do
29
+ answer = solution.send(variant_method_name, input_file)
30
+ end
31
+ variant = variant_method_name.to_s.delete_prefix(base_method_name).delete_prefix("_")
32
+ variant_note = " `#{PASTEL.blue.bold(variant)}`" unless variant.empty?
33
+ rescue ArgumentError
34
+ raise SolutionArgumentError
35
+ end
36
+
37
+ part_name = "#{year}##{day}.#{part}#{variant_note}"
10
38
 
11
- def self.run_part(part_name, correct_answer)
12
- answer = nil
13
- t = Benchmark.realtime do
14
- answer = yield
15
39
  if answer
16
40
  puts "Result for #{part_name}:"
17
41
 
@@ -27,15 +51,31 @@ module Arb
27
51
  else
28
52
  puts "❌ No result for #{part_name}"
29
53
  end
30
- end
31
54
 
32
- if answer
33
- puts "(obtained in #{t.round(10)} seconds)"
55
+ if answer && run_time
56
+ if variant_method_names.count > 1
57
+ puts "(obtained in #{PASTEL.cyan("%.6f" % run_time)} seconds)"
58
+ else
59
+ puts "(obtained in #{"%.6f" % run_time} seconds)"
60
+ end
61
+ end
62
+
63
+ answers << answer
64
+
65
+ puts
34
66
  end
35
67
 
36
- puts
68
+ answers
69
+ end
37
70
 
38
- answer
71
+ private_class_method def self.solution_class(year:, day:)
72
+ require "#{Dir.pwd}/src/#{year}/#{day}"
73
+
74
+ Module
75
+ .const_get("Year#{year}")
76
+ .const_get("Day#{day}")
77
+ rescue NameError
78
+ raise SolutionNotFoundError
39
79
  end
40
80
  end
41
81
  end
@@ -2,20 +2,18 @@ module Arb
2
2
  module Cli
3
3
  class WorkingDirectory
4
4
  FILES = {
5
- gitignore: <<~FILE,
5
+ ".gitignore" => <<~FILE,
6
6
  input/**/*
7
7
  instructions/**/*
8
8
  others/**/*
9
9
  .env
10
-
11
10
  FILE
12
- ruby_version: "3.3.0\n",
13
- gemfile: <<~FILE,
11
+ ".ruby-version" => "3.3.0\n",
12
+ "Gemfile" => <<~FILE,
14
13
  source "https://rubygems.org"
15
14
  ruby file: ".ruby-version"
16
-
17
15
  FILE
18
- spec_helper_addition: <<~FILE
16
+ File.join("spec", "spec_helper.rb") => <<~FILE
19
17
  require "debug"
20
18
 
21
19
  Dir[File.join(__dir__, "..", "src", "**", "*.rb")].each do |file|
@@ -43,7 +41,8 @@ module Arb
43
41
  files_created += create_other_files!
44
42
 
45
43
  if files_created.any?
46
- puts "✅ Initial files created and committed to a new Git repository.\n\n"
44
+ puts "✅ Initial files created and committed to a new Git repository."
45
+ puts
47
46
  end
48
47
  end
49
48
 
@@ -54,19 +53,19 @@ module Arb
54
53
  File.write(".env", generate_dotenv)
55
54
  end
56
55
 
57
- private
58
-
59
- def self.generate_dotenv(new_dotenv)
56
+ private_class_method def self.generate_dotenv(new_dotenv)
60
57
  new_dotenv.slice(*env_keys).map { |k, v|
61
58
  "#{k}=#{v}"
62
59
  }.join("\n")
63
60
  end
64
61
 
65
- def self.create_dotenv!(existing_dotenv)
62
+ private_class_method def self.create_dotenv!(existing_dotenv)
66
63
  new_dotenv = existing_dotenv.dup
67
64
 
68
- puts "🎄 Welcome to Advent of Code in Ruby! 🎄\n\n"
69
- puts "Let's start with some configuration.\n\n"
65
+ puts "🎄 Welcome to Advent of Code in Ruby! 🎄"
66
+ puts
67
+ puts "Let's start with some configuration."
68
+ puts
70
69
 
71
70
  unless existing_dotenv.has_key?("EDITOR_COMMAND")
72
71
  puts "What's the shell command to start your editor? (default: #{default_editor_command})"
@@ -76,6 +75,7 @@ module Arb
76
75
  new_dotenv["EDITOR_COMMAND"] = editor_command
77
76
  end
78
77
 
78
+ puts
79
79
  puts
80
80
 
81
81
  unless existing_dotenv.has_key?("AOC_COOKIE")
@@ -85,7 +85,7 @@ module Arb
85
85
  File.write(".env", generate_dotenv(new_dotenv))
86
86
  end
87
87
 
88
- def self.input_aoc_cookie
88
+ private_class_method def self.input_aoc_cookie
89
89
  aoc_cookie = nil
90
90
 
91
91
  loop do
@@ -97,6 +97,7 @@ module Arb
97
97
 
98
98
  aoc_cookie = STDIN.gets.strip
99
99
 
100
+ puts
100
101
  puts
101
102
 
102
103
  break unless aoc_cookie.strip.empty?
@@ -105,32 +106,24 @@ module Arb
105
106
  aoc_cookie
106
107
  end
107
108
 
108
- def self.create_other_files!
109
+ private_class_method def self.create_other_files!
109
110
  other_files_created = []
110
111
 
111
112
  unless Dir.exist?("src")
112
113
  Dir.mkdir("src")
113
- other_files_created << :src_dir
114
+ other_files_created << "src"
114
115
  end
115
116
 
116
117
  unless Dir.exist?("spec")
117
118
  Dir.mkdir("spec")
118
- other_files_created << :spec_dir
119
- end
120
-
121
- unless File.exist?(".gitignore")
122
- File.write(".gitignore", FILES.fetch(:gitignore))
123
- other_files_created << :gitignore
119
+ other_files_created << "spec"
124
120
  end
125
121
 
126
- unless File.exist?(".ruby-version")
127
- File.write(".ruby-version", FILES.fetch(:ruby_version))
128
- other_files_created << :ruby_version
129
- end
130
-
131
- unless File.exist?("Gemfile")
132
- File.write("Gemfile", FILES.fetch(:gemfile))
133
- other_files_created << :gemfile
122
+ FILES.slice(".gitignore", ".ruby-version", "Gemfile").each do |filename, contents|
123
+ unless File.exist?(filename)
124
+ File.write(filename, contents)
125
+ other_files_created << filename
126
+ end
134
127
  end
135
128
 
136
129
  spec_helper_path = File.join("spec", "spec_helper.rb")
@@ -138,16 +131,16 @@ module Arb
138
131
  rspec_init_output = `rspec --init`
139
132
  unless rspec_init_output.match?(/exist\s+spec.spec_helper.rb/)
140
133
  original_spec_helper = File.read(spec_helper_path)
141
- File.write(spec_helper_path, FILES.fetch(:spec_helper_addition) + original_spec_helper)
142
- other_files_created << :spec_helper
134
+ File.write(spec_helper_path, FILES.fetch(spec_helper_path) + original_spec_helper)
135
+ other_files_created << spec_helper_path
143
136
  end
144
- other_files_created << :rspec
137
+ other_files_created << ".rspec"
145
138
  end
146
139
 
147
140
  unless Git.repo_exists?
148
141
  Git.init!
149
142
  Git.commit_all!(message: "Initial commit")
150
- other_files_created << :git
143
+ other_files_created << ".git"
151
144
  end
152
145
 
153
146
  other_files_created
@@ -1,8 +1,8 @@
1
1
  module Arb
2
2
  module Cli
3
3
  class YearDayValidator
4
- def self.validate_year_and_day(year:, day:, default_untracked_or_done: false)
5
- year, day = year&.to_s, day&.to_s
4
+ def self.validate_year_and_day(year:, day:, default_to_untracked_or_last_committed: false)
5
+ year, day = year&.to_s, day&.to_s&.rjust(2, "0")
6
6
 
7
7
  # The first two digits of the year may be omitted.
8
8
  year = "20#{year}" if year && year.length == 2
@@ -10,18 +10,17 @@ module Arb
10
10
  if day && !year
11
11
  raise InputError, "If you specify the day, specify the year also."
12
12
  elsif !day
13
- if default_untracked_or_done
14
- year, day = Git.new_solutions.last
13
+ if !year && default_to_untracked_or_last_committed
14
+ year, day = Git.uncommitted_puzzles.keys.last
15
15
  end
16
16
 
17
17
  unless day
18
- if year && !Dir.exist?(File.join("src", year))
19
- Dir.mkdir(File.join("src", year))
20
- day = "1"
18
+ if year && Git.committed_by_year[year].values.none?
19
+ day = "01"
21
20
  else
22
- year, day = Git.last_committed_solution(year:)
21
+ year, day = Git.last_committed_puzzle(year:)
23
22
 
24
- if day && !default_untracked_or_done
23
+ if day && !default_to_untracked_or_last_committed
25
24
  if day == "25"
26
25
  day = :end
27
26
  else
@@ -31,7 +30,7 @@ module Arb
31
30
 
32
31
  if !day || day == :end
33
32
  default_year = "2015"
34
- default_day = "1"
33
+ default_day = "01"
35
34
  bootstrap_year_prompt = nil
36
35
 
37
36
  committed = Git.committed_by_year
@@ -46,7 +45,7 @@ module Arb
46
45
  default_day = committed[default_year].values.index(false)
47
46
 
48
47
  puts "You've recently finished #{year}. Yay!"
49
- bootstrap_year_prompt = "What year do you want to bootstrap next? (default: #{default_year} [at Day #{default_day}])"
48
+ bootstrap_year_prompt = "What year do you want to bootstrap next? (default: #{default_year} [at Day #{default_day.to_s.delete_prefix("0")}])"
50
49
  end
51
50
 
52
51
  loop do
@@ -54,12 +53,14 @@ module Arb
54
53
  print PASTEL.green("> ")
55
54
  year_input = STDIN.gets.strip
56
55
  puts
56
+ puts
57
+
57
58
  if year_input.strip.empty?
58
59
  year = default_year
59
60
  day = default_day
60
61
  else
61
62
  year = year_input.strip.match(/\A\d{4}\z/)&.to_s
62
- day = "1"
63
+ day = "01"
63
64
  end
64
65
  break if year
65
66
  end
@@ -78,7 +79,7 @@ module Arb
78
79
  raise InputError, "Day must be between 1 and 25, and <= today."
79
80
  end
80
81
 
81
- [year.to_s, day.to_s]
82
+ [year.to_s, day.to_s.rjust(2, "0")]
82
83
  end
83
84
  end
84
85
  end
@@ -1,19 +1,22 @@
1
1
  module Arb
2
2
  module Files
3
3
  class Input
4
- def self.download(year, day, notify_exists: true)
4
+ def self.path(year:, day:)
5
5
  year_directory = File.join("input", year)
6
+ File.join(year_directory, "#{day}.txt")
7
+ end
8
+
9
+ def self.download(year:, day:, notify_exists: true)
6
10
  Dir.mkdir("input") unless Dir.exist?("input")
11
+ year_directory = File.join("input", year)
7
12
  Dir.mkdir(year_directory) unless Dir.exist?(year_directory)
8
-
9
- padded_day = day.rjust(2, "0")
10
- file_path = File.join(year_directory, "#{padded_day}.txt")
13
+ file_path = File.join(year_directory, "#{day}.txt")
11
14
 
12
15
  if File.exist?(file_path)
13
16
  puts "Already exists: #{file_path}" if notify_exists
14
17
  else
15
- aoc_api = Api::Aoc.new(ENV["AOC_COOKIE"])
16
- response = aoc_api.input(year, day)
18
+ aoc_api = Api::Aoc.new(cookie: ENV["AOC_COOKIE"])
19
+ response = aoc_api.input(year:, day:)
17
20
 
18
21
  File.write(file_path, response)
19
22
  end
@@ -1,26 +1,24 @@
1
1
  module Arb
2
2
  module Files
3
3
  class Instructions
4
- def self.path(year, day)
4
+ def self.path(year:, day:)
5
5
  year_directory = File.join("instructions", year)
6
- Dir.mkdir("instructions") unless Dir.exist?("instructions")
7
- Dir.mkdir(year_directory) unless Dir.exist?(year_directory)
8
-
9
- padded_day = day.rjust(2, "0")
10
-
11
- File.join(year_directory, "#{padded_day}.md")
6
+ File.join(year_directory, "#{day}.md")
12
7
  end
13
8
 
14
- def self.download(year, day, notify_exists: true, overwrite: false)
15
- file_path = path(year, day)
9
+ def self.download(year:, day:, notify_exists: true, overwrite: false)
10
+ Dir.mkdir("instructions") unless Dir.exist?("instructions")
11
+ year_directory = File.join("instructions", year)
12
+ Dir.mkdir(year_directory) unless Dir.exist?(year_directory)
13
+ file_path = File.join(year_directory, "#{day}.md")
16
14
 
17
15
  if File.exist?(file_path) && !overwrite
18
16
  puts "Already exists: #{file_path}" if notify_exists
19
17
  else
20
- url = "https://adventofcode.com/#{year}/day/#{day}"
18
+ url = "https://adventofcode.com/#{year}/day/#{day.delete_prefix("0")}"
21
19
 
22
- aoc_api = Api::Aoc.new(ENV["AOC_COOKIE"])
23
- response = aoc_api.instructions(year, day)
20
+ aoc_api = Api::Aoc.new(cookie: ENV["AOC_COOKIE"])
21
+ response = aoc_api.instructions(year:, day:)
24
22
  instructions = response.match(/(?<=<main>).+(?=<\/main>)/m).to_s
25
23
  markdown_instructions = ReverseMarkdown.convert(instructions).strip
26
24
  markdown_instructions = markdown_instructions
@@ -1,20 +1,18 @@
1
1
  module Arb
2
2
  module Files
3
3
  class OtherSolutions
4
- def self.download(year, day)
4
+ def self.download(year:, day:)
5
5
  year_directory = File.join("others", year)
6
6
  Dir.mkdir("others") unless Dir.exist?("others")
7
7
  Dir.mkdir(year_directory) unless Dir.exist?(year_directory)
8
8
 
9
- padded_day = day.rjust(2, "0")
10
-
11
9
  file_paths = %w[1 2].map do |part|
12
- file_path = File.join(year_directory, "#{padded_day}_#{part}.rb")
10
+ file_path = File.join(year_directory, "#{day}_#{part}.rb")
13
11
 
14
12
  if File.exist?(file_path)
15
13
  puts "Already exists: #{file_path}"
16
14
  else
17
- other_solutions = Api::OtherSolutions.new.other_solutions(year, day, part)
15
+ other_solutions = Api::OtherSolutions.new.other_solutions(year:, day:, part:)
18
16
  File.write(file_path, other_solutions)
19
17
  end
20
18
 
@@ -1,29 +1,26 @@
1
1
  module Arb
2
2
  module Files
3
- class Source
4
- def self.create(year, day)
3
+ class Solution
4
+ def self.create(year:, day:)
5
5
  year_directory = File.join("src", year)
6
6
  Dir.mkdir(year_directory) unless Dir.exist?(year_directory)
7
7
 
8
- padded_day = day.rjust(2, "0")
9
- file_path = File.join(year_directory, "#{padded_day}.rb")
8
+ file_path = File.join(year_directory, "#{day}.rb")
10
9
 
11
10
  if File.exist?(file_path)
12
11
  puts "Already exists: #{file_path}"
13
12
  else
14
- File.write(file_path, source(year, day))
13
+ File.write(file_path, template(year:, day:))
15
14
  end
16
15
 
17
16
  file_path
18
17
  end
19
18
 
20
- def self.source(year, day)
21
- padded_day = day.rjust(2, "0")
22
-
23
- <<~TPL
24
- # https://adventofcode.com/#{year}/day/#{day}
19
+ def self.template(year:, day:)
20
+ <<~SRC
21
+ # https://adventofcode.com/#{year}/day/#{day.delete_prefix("0")}
25
22
  module Year#{year}
26
- class Day#{padded_day}
23
+ class Day#{day}
27
24
  def part_1(input_file)
28
25
  lines = input_file.readlines(chomp: true)
29
26
 
@@ -37,7 +34,7 @@ module Arb
37
34
  end
38
35
  end
39
36
  end
40
- TPL
37
+ SRC
41
38
  end
42
39
  end
43
40
  end
@@ -1,27 +1,24 @@
1
1
  module Arb
2
2
  module Files
3
3
  class Spec
4
- def self.create(year, day, notify_exists: true)
4
+ def self.create(year:, day:, notify_exists: true)
5
5
  year_directory = File.join("spec", year)
6
6
  Dir.mkdir(year_directory) unless Dir.exist?(year_directory)
7
7
 
8
- padded_day = day.rjust(2, "0")
9
- file_path = File.join(year_directory, "#{padded_day}_spec.rb")
8
+ file_path = File.join(year_directory, "#{day}_spec.rb")
10
9
 
11
10
  if File.exist?(file_path)
12
11
  puts "Already exists: #{file_path}" if notify_exists
13
12
  else
14
- File.write(file_path, source(year, day))
13
+ File.write(file_path, template(year:, day:))
15
14
  end
16
15
 
17
16
  file_path
18
17
  end
19
18
 
20
- def self.source(year, day)
21
- padded_day = day.rjust(2, "0")
22
-
23
- <<~TPL
24
- RSpec.describe Year#{year}::Day#{padded_day} do
19
+ def self.template(year:, day:)
20
+ <<~SPECS
21
+ RSpec.describe Year#{year}::Day#{day} do
25
22
  let(:input) {
26
23
  StringIO.new(
27
24
  <<~IN
@@ -38,7 +35,7 @@ module Arb
38
35
  expect(subject.part_2(input)).to eq(:todo)
39
36
  end
40
37
  end
41
- TPL
38
+ SPECS
42
39
  end
43
40
  end
44
41
  end
data/lib/arb/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Arb
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: advent_of_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felipe Vogel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-21 00:00:00.000000000 Z
11
+ date: 2024-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: debug
@@ -137,7 +137,7 @@ files:
137
137
  - lib/arb/cli/bootstrap.rb
138
138
  - lib/arb/cli/commit.rb
139
139
  - lib/arb/cli/progress.rb
140
- - lib/arb/cli/run_day.rb
140
+ - lib/arb/cli/run.rb
141
141
  - lib/arb/cli/shared/git.rb
142
142
  - lib/arb/cli/shared/runner.rb
143
143
  - lib/arb/cli/shared/working_directory.rb
@@ -145,7 +145,7 @@ files:
145
145
  - lib/arb/files/input.rb
146
146
  - lib/arb/files/instructions.rb
147
147
  - lib/arb/files/other_solutions.rb
148
- - lib/arb/files/source.rb
148
+ - lib/arb/files/solution.rb
149
149
  - lib/arb/files/spec.rb
150
150
  - lib/arb/version.rb
151
151
  homepage: https://github.com/fpsvogel/advent_of_ruby
@@ -1,140 +0,0 @@
1
- module Arb
2
- module Cli
3
- # @param year [String, Integer]
4
- # @param day [String, Integer]
5
- # @param options [Thor::CoreExt::HashWithIndifferentAccess] see `method_option`s
6
- # above ArbApp#run_day.
7
- def self.run_day(year:, day:, options:)
8
- WorkingDirectory.prepare!
9
-
10
- if options.spec? && (options.real_part_1? || options.real_part_2?)
11
- raise InputError, "Don't use --spec (-s) with --real_part_1 (-o) or --real_part_2 (-t)"
12
- end
13
-
14
- year, day = YearDayValidator.validate_year_and_day(year:, day:, default_untracked_or_done: true)
15
-
16
- if Git.new_solutions.none? && !Git.last_committed_solution(year:)
17
- bootstrap(year:, day:)
18
- return
19
- end
20
-
21
- solution = Runner.load_solution(year, day)
22
- input_path = Files::Input.download(year, day, notify_exists: false)
23
- answer_1, answer_2 = nil, nil
24
-
25
- instructions_path = Files::Instructions.download(year, day, notify_exists: false, overwrite: false)
26
- instructions = File.read(instructions_path)
27
- correct_answer_1, correct_answer_2 = instructions.scan(/Your puzzle answer was `([^`]+)`./).flatten
28
- skip_count = 0
29
-
30
- if options.spec?
31
- run_specs_only(year, day)
32
- return
33
- elsif !(options.real_part_1? || options.real_part_2?)
34
- specs_passed, skip_count = run_specs_before_real(year, day)
35
- return unless specs_passed
36
- puts "👍 Specs passed!"
37
- if skip_count > 1 || (skip_count == 1 && correct_answer_1)
38
- puts PASTEL.yellow.bold("🤐 #{skip_count} skipped, however")
39
- end
40
- puts "\n"
41
- end
42
-
43
- if options.real_part_1? || (!options.real_part_2? && ((correct_answer_1.nil? && skip_count <= 1) || correct_answer_2))
44
- answer_1 = Runner.run_part("#{year}##{day}.1", correct_answer_1) do
45
- solution.part_1(File.new(input_path))
46
- end
47
- end
48
- if options.real_part_2? || (!options.real_part_1? && ((correct_answer_1 && !correct_answer_2 && skip_count.zero?) || correct_answer_2))
49
- answer_2 = Runner.run_part("#{year}##{day}.2", correct_answer_2) do
50
- solution.part_2(File.new(input_path))
51
- end
52
- end
53
-
54
- return unless answer_1 || answer_2
55
-
56
- if correct_answer_2
57
- puts "🙌 You've already submitted the answers to both parts.\n\n"
58
-
59
- if Git.commit_count <= 1
60
- puts "When you're done with this puzzle, run " \
61
- "`#{PASTEL.blue.bold("arb commit")}` (or `arb c`) commit your solution to Git.\n"
62
- end
63
-
64
- return
65
- elsif options.real_part_1? && correct_answer_1
66
- puts "🙌 You've already submitted the answer to this part.\n\n"
67
- return
68
- end
69
-
70
- puts "Submit solution? (Y/n)"
71
- print PASTEL.green("> ")
72
- submit = STDIN.gets.strip.downcase
73
-
74
- if submit == "y" || submit == ""
75
- options_part = options.real_part_1? ? "1" : (options.real_part_2? ? "2" : nil)
76
- inferred_part = correct_answer_1.nil? ? "1" : "2"
77
- aoc_api = Api::Aoc.new(ENV["AOC_COOKIE"])
78
-
79
- response = aoc_api.submit(year, day, options_part || inferred_part, answer_2 || answer_1)
80
- message = response.match(/(?<=<article>).+(?=<\/article>)/m).to_s.strip
81
- markdown_message = ReverseMarkdown.convert(message)
82
- short_message = markdown_message
83
- .sub(/\n\n.+/m, "")
84
- .sub(/ \[\[.+/, "")
85
-
86
- if short_message.start_with?("That's the right answer!")
87
- puts "⭐ #{short_message}\n"
88
-
89
- # TODO don't re-download if the instructions file already contains the next part
90
- instructions_path = Files::Instructions.download(year, day, overwrite: true)
91
-
92
- if (options_part || inferred_part) == "1"
93
- puts "\nDownloaded instructions for Part Two.\n\n"
94
- `#{ENV["EDITOR_COMMAND"]} #{instructions_path}`
95
-
96
- spec_path = Files::Spec.create(year, day, notify_exists: false)
97
- spec = File.read(spec_path)
98
- spec_without_skips = spec.gsub(" xit ", " it ")
99
- File.write(spec_path, spec_without_skips)
100
- end
101
-
102
- if Git.commit_count <= 1
103
- puts "\n\nNow it's time to improve your solution! Be sure to look " \
104
- "at other people's solutions (in the \"others\" directory). When " \
105
- "you're done, run `#{PASTEL.blue.bold("arb commit")}` (or `arb c`) " \
106
- "to commit your solution to Git.\n"
107
- end
108
- else
109
- puts "❌ #{short_message}"
110
- end
111
- end
112
- rescue AppError => e
113
- puts PASTEL.red(e.message)
114
- end
115
-
116
- private_class_method def self.run_specs_only(year, day)
117
- padded_day = day.rjust(2, "0")
118
- spec_filename = [File.join("spec", year, "#{padded_day}_spec.rb")]
119
-
120
- RSpec::Core::Runner.run(spec_filename)
121
- end
122
-
123
- private_class_method def self.run_specs_before_real(year, day)
124
- padded_day = day.rjust(2, "0")
125
- spec_filename = [File.join("spec", year, "#{padded_day}_spec.rb")]
126
-
127
- out = StringIO.new
128
- RSpec::Core::Runner.run(spec_filename, $stderr, out)
129
-
130
- if out.string.match?(/Failures:/)
131
- RSpec.reset
132
- RSpec::Core::Runner.run(spec_filename)
133
-
134
- [false, nil]
135
- else
136
- [true, out.string.scan("skipped with xit").count]
137
- end
138
- end
139
- end
140
- end