Module: Arb::Cli

Defined in:
lib/arb/cli/run.rb,
lib/arb/cli/commit.rb,
lib/arb/cli/progress.rb,
lib/arb/cli/bootstrap.rb,
lib/arb/cli/shared/git.rb,
lib/arb/cli/shared/runner.rb,
lib/arb/cli/shared/working_directory.rb,
lib/arb/cli/shared/year_day_validator.rb

Defined Under Namespace

Classes: Git, Runner, WorkingDirectory, YearDayValidator

Class Method Summary collapse

Class Method Details

.bootstrap(year: nil, day: nil) ⇒ Object

Parameters:

  • year (String, Integer) (defaults to: nil)
  • day (String, Integer) (defaults to: nil)


5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/arb/cli/bootstrap.rb', line 5

def self.bootstrap(year: nil, day: nil)
  WorkingDirectory.prepare!

  year, day = YearDayValidator.validate_year_and_day(year:, day:)

  instructions_path = Files::Instructions.download(year:, day:)
  others_1_path, others_2_path = Files::OtherSolutions.download(year:, day:)
  input_path = Files::Input.download(year:, day:)
  solution_path = Files::Solution.create(year:, day:)
  spec_path = Files::Spec.create(year:, day:)

  puts "🀘 Bootstrapped #{year}##{day}"
  puts

  # Open the new files.
  `#{ENV["EDITOR_COMMAND"]} #{others_1_path}`
  `#{ENV["EDITOR_COMMAND"]} #{others_2_path}`
  `#{ENV["EDITOR_COMMAND"]} #{input_path}`
  `#{ENV["EDITOR_COMMAND"]} #{solution_path}`
  `#{ENV["EDITOR_COMMAND"]} #{spec_path}`
  `#{ENV["EDITOR_COMMAND"]} #{instructions_path}`

  if Git.commit_count <= 1
    puts "Now fill in the spec for Part One with an example from the instructions, " \
      "then run it with `#{PASTEL.blue.bold("arb run")}` (or just `arb`) as " \
      "you implement the solution. When the spec passes, your solution will " \
      "be run with the real input and you'll be prompted to submit your solution."
  end
rescue InputError => e
  puts PASTEL.red(e.message)
end

.commitObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/arb/cli/commit.rb', line 3

def self.commit
  WorkingDirectory.prepare!

  puzzles = Git.modified_puzzles
  if puzzles.any?
    files_modified = true
  else
    puzzles = Git.uncommitted_puzzles
  end

  if puzzles.empty?
    puts "Nothing to commit."

    if Git.commit_count <= 2
      puts
      puts "Run `#{PASTEL.blue.bold("arb bootstrap")}` (or `arb b`) to start the next puzzle."
    end

    return
  end

  puzzles.each do |(year, day), filenames|
    message = "#{files_modified ? "Improve" : "Solve"} #{year}##{day}"
    Git.commit!(filenames:, message:)

    # TODO less naive check: ensure prev. days are finished too
    if !files_modified && day == "25"
      puts
      puts "πŸŽ‰ You've finished #{year}!"
      puts
    end

    puts "Puzzle #{year}##{day}#{" (modified)" if files_modified} committed πŸŽ‰"
  end

  if Git.commit_count <= 1
    puts
    puts "When you're ready to start the next puzzle, run " \
      "`#{PASTEL.blue.bold("arb bootstrap")}` (or `arb b`)."
  end
end

.progressObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/arb/cli/progress.rb', line 3

def self.progress
  WorkingDirectory.prepare!

  committed = Git.committed_by_year

  total_count = committed.values.sum(&:count)
  my_counts_by_year = committed
    .transform_values { it.values.count(&:itself) }
    .reject { |k, v| v.zero? }
  my_total_count = my_counts_by_year.values.sum

  total_percent = (my_total_count.to_f / total_count * 100).round(1)
  total_percent = (total_percent == total_percent.to_i) ? total_percent.to_i : total_percent

  puts "You have completed:"
  puts
  puts PASTEL.bold "#{PASTEL.blue("All:")}\t#{total_percent}% \t#{my_total_count}/#{total_count} puzzles"
  puts

  my_counts_by_year.each do |year, my_count|
    year_count =
      if year.to_i == Date.today.year
        this_year_count
      else
        25
      end

    year_percent = (my_count.to_f / year_count * 100).round

    puts "#{PASTEL.blue("#{year}:")}\t#{year_percent}%\t#{my_count}/#{year_count}"
  end
end

.run(year:, day:, options:) ⇒ Object

Parameters:

  • year (String, Integer)
  • day (String, Integer)
  • options (Hash)

    see β€˜method_option`s above ArbApp#run_day.



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/arb/cli/run.rb', line 6

def self.run(year:, day:, options:)
  WorkingDirectory.prepare!

  if options[:spec] && (options[:one] || options[:two])
    raise InputError, "Don't use --spec (-s) with --one (-o) or --two (-t)"
  end

  year, day = YearDayValidator.validate_year_and_day(year:, day:, default_to_untracked_or_last_committed: true)

  if Git.uncommitted_puzzles.empty? && !Git.last_committed_puzzle(year:)
    bootstrap(year:, day:)
    return
  end

  instructions_path = Files::Instructions.download(year:, day:, notify_exists: false, overwrite: false)
  instructions = File.read(instructions_path)
  correct_answer_1, correct_answer_2 = instructions.scan(/Your puzzle answer was `([^`]+)`./).flatten
  skip_count = 0

  if options[:spec]
    run_specs_only(year:, day:)
    return
  elsif !(options[:one] || options[:two])
    specs_passed, skip_count = run_specs_before_real(year:, day:)
    return unless specs_passed
    puts "πŸ‘ Specs passed!"
    if skip_count > 1 || (skip_count == 1 && correct_answer_1)
      puts PASTEL.yellow.bold("🀐 #{skip_count} skipped, however")
    end
    puts
  end

  Files::Input.download(year:, day:, notify_exists: false)
  answers_1, answers_2 = [], []

  begin
    if options[:one] || (!options[:two] && ((correct_answer_1.nil? && skip_count <= 1) || correct_answer_2))
      answers_1 = Runner.run_part(year:, day:, part: "1", correct_answer: correct_answer_1)
    end
    if options[:two] || (!options[:one] && ((correct_answer_1 && !correct_answer_2 && skip_count.zero?) || correct_answer_2))
      if answers_1.count > 1
        puts "------------"
        puts
      end

      answers_2 = Runner.run_part(year:, day:, part: "2", correct_answer: correct_answer_2)
    end
  rescue Runner::SolutionFileNotFoundError
    puts PASTEL.red("Solution file not found. Make sure this file exists: #{PASTEL.bold("./src/#{year}/#{day}")}")
  rescue Runner::SolutionClassNotFoundError
    puts PASTEL.red("Solution class not found. Make sure the class #{PASTEL.bold("Year#{year}::Day#{day}")} exists in this file (which *does* exist): ./src/#{year}/#{day}")
  rescue Runner::SolutionMethodNotFoundError
    puts PASTEL.red("πŸ€” Couldn't find the method #{PASTEL.bold("##{base_method_name}")}, though the class Year#{year}::Day#{day} exists.")
  rescue Runner::SolutionArgumentError => e
    puts PASTEL.red("#{e.message.capitalize}. Make sure that method has one parameter, the input file.")
  end

  answer_1, answer_2 = answers_1.compact.first, answers_2.compact.first

  return unless answer_1 || answer_2

  if correct_answer_2
    puts "πŸ™Œ You've already submitted the answers to both parts.\n"

    if Git.commit_count <= 1
      puts "When you're done with this puzzle, run " \
        "`#{PASTEL.blue.bold("arb commit")}` (or `arb c`) commit your solution to Git.\n"
    end

    return
  elsif options[:one] && correct_answer_1
    puts "πŸ™Œ You've already submitted the answer to this part.\n\n"
    return
  end

  puts "Submit solution? (Y/n)"
  print PASTEL.green("> ")
  submit = $stdin.gets.strip.downcase
  puts

  if submit == "y" || submit == ""
    options_part = if options[:one]
      "1"
    else
      (options[:two] ? "2" : nil)
    end
    inferred_part = correct_answer_1.nil? ? "1" : "2"
    aoc_api = Api::Aoc.new(cookie: ENV["AOC_COOKIE"])

    response = aoc_api.submit(year:, day:, part: options_part || inferred_part, answer: answer_2 || answer_1)
    message = response.match(/(?<=<article>).+(?=<\/article>)/m).to_s.strip
    markdown_message = ReverseMarkdown.convert(message)
    short_message = markdown_message
      .sub(/\n\n.+/m, "")
      .sub(/ \[\[.+/, "")

    if short_message.start_with?("That's the right answer!")
      puts "⭐ #{short_message}"

      # TODO don't re-download if the instructions file already contains the next part
      instructions_path = Files::Instructions.download(year:, day:, overwrite: true)

      if (options_part || inferred_part) == "1"
        puts "Downloaded instructions for Part Two."
        `#{ENV["EDITOR_COMMAND"]} #{instructions_path}`

        spec_path = Files::Spec.create(year:, day:, notify_exists: false)
        spec = File.read(spec_path)
        spec_without_skips = spec.gsub("  xit ", "  it ")
        File.write(spec_path, spec_without_skips)
      end

      if Git.commit_count <= 1
        puts
        puts "Now it's time to improve your solution! Be sure to look " \
          "at other people's solutions (in the \"others\" directory). When " \
          "you're done, run `#{PASTEL.blue.bold("arb commit")}` (or `arb c`) " \
          "to commit your solution to Git.\n"
      end
    else
      puts "❌ #{short_message}"
    end
  end
rescue InputError => e
  puts PASTEL.red(e.message)
end