From fbb638abe7bebf6b03751514d3260b544d9689b1 Mon Sep 17 00:00:00 2001 From: Emily Samp Date: Fri, 2 Dec 2022 21:56:36 -0600 Subject: [PATCH] Introduce commands to create and run examples (#5) --- README.md | 55 ++++++++++++++++ lib/advent_of_code_cli.rb | 5 ++ lib/advent_of_code_cli/commands.rb | 1 + lib/advent_of_code_cli/commands/command.rb | 8 +++ lib/advent_of_code_cli/commands/example.rb | 38 ++++++++++++ .../commands/example/new.rb | 48 ++++++++++++++ .../commands/example/solve.rb | 62 +++++++++++++++++++ 7 files changed, 217 insertions(+) create mode 100644 lib/advent_of_code_cli/commands/example.rb create mode 100644 lib/advent_of_code_cli/commands/example/new.rb create mode 100644 lib/advent_of_code_cli/commands/example/solve.rb diff --git a/README.md b/README.md index d67bf3b..ebd4f3d 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,61 @@ Done! This command expects files to be in the format provided by the `scaffold` command. Once again, I would love to make this configurable but haven't gotten around to it yet. +### Examples + +It is often helpful to run our solutions against example input. The `example` command can help you create and run examples of your own invention. + +#### Create a new example + +You can create a new file for example input by running the following command: + +``` +bundle exec aoc_cli example new 1 A +``` + +The first argument specifies the day, and the second argument is the name of the example. You may choose whatever name you'd like. + +This will generate the following output: + +``` +Creating examples/01/A.txt... +Creating examples/01/A_expected.yml... +Done! +``` + +- `examples/01/A.txt` is a blank text file where you can enter your own input for the problem. +- `examples/01/A_expected.yml` is a YAML file with the following content: + +``` +part_one: ~ +part_two: ~ +``` + +Replace the two tildes (`~`) with the expected result of running your solution against the example input provided. + +#### Running examples + +You can check your solution against an example with the following command: + +``` +bundle exec aoc_cli example solve 1 A +``` + +This will output the following: + +``` +Reading input... +Loading solution... + +Running part one with example A... +Part one result: 1034 ✅ +Took 0.000259 seconds to solve + +Running part two with example A... +Part two result: 7934 ✅ +Took 0.000253 seconds to solve +``` + ## Contributing Issues and code contributions are welcome! Happy Advent of Code to all who celebrate! 🎁 diff --git a/lib/advent_of_code_cli.rb b/lib/advent_of_code_cli.rb index 9be0f49..3e00ae7 100644 --- a/lib/advent_of_code_cli.rb +++ b/lib/advent_of_code_cli.rb @@ -7,8 +7,10 @@ module AdventOfCode class Error < StandardError; end + class ExampleAlreadyExistsError < Error; end class InvalidDayError < Error; end class MissingCookieError < Error; end + class MissingExampleError < Error; end class MissingInputError < Error; end class MissingSolutionError < Error; end @@ -41,6 +43,9 @@ def solve(day) say "Error: Cannot find solution file.", :red end + desc "example", "create and run example files" + subcommand "example", Commands::Example::CLI + private def rescue_invalid_day_error diff --git a/lib/advent_of_code_cli/commands.rb b/lib/advent_of_code_cli/commands.rb index b69d84d..4cf5bc2 100644 --- a/lib/advent_of_code_cli/commands.rb +++ b/lib/advent_of_code_cli/commands.rb @@ -2,5 +2,6 @@ require_relative "commands/command" require_relative "commands/download" +require_relative "commands/example" require_relative "commands/scaffold" require_relative "commands/solve" diff --git a/lib/advent_of_code_cli/commands/command.rb b/lib/advent_of_code_cli/commands/command.rb index 43eb4c0..b624f94 100644 --- a/lib/advent_of_code_cli/commands/command.rb +++ b/lib/advent_of_code_cli/commands/command.rb @@ -25,6 +25,14 @@ def input_file_name "inputs/#{day_string}.txt" end + def example_file_name + "examples/#{day_string}/#{@name}.txt" + end + + def example_expected_file_name + "examples/#{day_string}/#{@name}_expected.yml" + end + def create_file(file_name, contents = nil) File.open(file_name, "w") do |file| file.puts contents if contents diff --git a/lib/advent_of_code_cli/commands/example.rb b/lib/advent_of_code_cli/commands/example.rb new file mode 100644 index 0000000..71a7714 --- /dev/null +++ b/lib/advent_of_code_cli/commands/example.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require_relative "example/new" +require_relative "example/solve" + +module AdventOfCode + module Commands + module Example + class CLI < Thor + desc "new DAY NAME", "creates an example file with name NAME for day DAY" + def new(day, name) + New.new(day: day.to_i, name: name).execute + rescue ExampleAlreadyExistsError => e + say(e.message, :red) + rescue InvalidDayError + rescue_invalid_day_error + end + + desc "solve DAY NAME", "runs the example with name NAME for day DAY" + def solve(day, name) + Solve.new(day: day.to_i, name: name).execute + rescue InvalidDayError + rescue_invalid_day_error + rescue MissingExampleError + say "Error: Cannot find example file.", :red + rescue AdventOfCode::MissingSolutionError + say "Error: Cannot find solution file.", :red + end + + private + + def rescue_invalid_day_error + say "Error: The day argument must be an integer between 1 and 25.", :red + end + end + end + end +end diff --git a/lib/advent_of_code_cli/commands/example/new.rb b/lib/advent_of_code_cli/commands/example/new.rb new file mode 100644 index 0000000..33445fd --- /dev/null +++ b/lib/advent_of_code_cli/commands/example/new.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module AdventOfCode + module Commands + module Example + class New < Command + def initialize(day:, name:) + @name = name + super(day: day) + end + + def execute + unless Dir.exist?("examples") + say("Creating examples directory...") + Dir.mkdir("examples") + end + + unless Dir.exist?("examples/#{day_string}") + say("Creating examples/#{day_string} directory...") + Dir.mkdir("examples/#{day_string}") + end + + if File.exist?(example_file_name) + raise ExampleAlreadyExistsError, + "could not create example file because file #{example_file_name} already exists" + end + + say("Creating #{example_file_name}...") + create_file(example_file_name) + + say("Creating #{example_expected_file_name}...") + create_file(example_expected_file_name, expected_file_contents) + + say "Done!", :green + end + + private + + def expected_file_contents + <<~RUBY + part_one: ~ + part_two: ~ + RUBY + end + end + end + end +end diff --git a/lib/advent_of_code_cli/commands/example/solve.rb b/lib/advent_of_code_cli/commands/example/solve.rb new file mode 100644 index 0000000..de3326f --- /dev/null +++ b/lib/advent_of_code_cli/commands/example/solve.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "yaml" + +module AdventOfCode + module Commands + module Example + class Solve < Command + def initialize(day:, name:) + @name = name + super(day: day) + end + + def execute + raise MissingExampleError unless File.exist?(example_file_name) + raise MissingExampleError unless File.exist?(example_expected_file_name) + raise MissingSolutionError unless File.exist?(solution_file_name) + + say "Reading input..." + input = File.readlines(example_file_name, chomp: true) + + say "Loading solution..." + load(solution_file_name) + + module_name = "Day#{day_string}" + + say "\nRunning part one with example #{@name}..." + solution(module_name, "one", input) + + say "\nRunning part two with example #{@name}..." + solution(module_name, "two", input) + + say "\nDone!", :green + end + + private + + def solution(module_name, part, input) + start_time = Time.now + result = Object.const_get(module_name).send("part_#{part}", input) + end_time = Time.now + + expected_result = expected_answers["part_#{part}"] + + if expected_result.nil? + say "Part #{part} result: #{result} ⚠️ (no expectation provided)" + elsif result == expected_result + say "Part #{part} result: #{result} ✅" + else + say "Part #{part} result: #{result} ❌ (expected #{expected_result})" + end + + say "Took #{end_time - start_time} seconds to solve" + end + + def expected_answers + @expected_answers ||= YAML.safe_load(File.read(example_expected_file_name)) + end + end + end + end +end