advent_of_ruby 0.1.0 → 0.2.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 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