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 AppError => e
  puts Pastel.new.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
# 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 { _1.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|
    if year.to_i == Date.today.year
      year_count = this_year_count
    else
      year_count = 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
# 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::SolutionNotFoundError
    puts PASTEL.red("Solution class not found. Make sure this class exists: #{PASTEL.bold("Year#{year}::Day#{day}")}")
  rescue Runner::SolutionArgumentError
    puts PASTEL.red("ArgumentError when running your solution. Make sure every method has a one parameter (the input file).")
  rescue Runner::SolutionArgumentError
  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 = options[:one] ? "1" : (options[:two] ? "2" : nil)
    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
      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 AppError => e
  puts PASTEL.red(e.message)
end