├── .gitignore ├── .rubocop.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── advent_of_code_cli.gemspec ├── bin ├── console └── setup ├── exe ├── advent_of_code_cli └── aoc_cli ├── lib ├── advent_of_code_cli.rb └── advent_of_code_cli │ ├── commands.rb │ ├── commands │ ├── command.rb │ ├── download.rb │ ├── example.rb │ ├── example │ │ ├── new.rb │ │ └── solve.rb │ ├── scaffold.rb │ └── solve.rb │ └── version.rb └── test └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.6 3 | NewCops: enable 4 | 5 | Style/Documentation: 6 | Enabled: false 7 | 8 | Style/StringLiterals: 9 | Enabled: true 10 | EnforcedStyle: double_quotes 11 | 12 | Style/StringLiteralsInInterpolation: 13 | Enabled: true 14 | EnforcedStyle: double_quotes 15 | 16 | Layout/LineLength: 17 | Max: 120 18 | 19 | Metrics/AbcSize: 20 | Enabled: false 21 | 22 | Metrics/MethodLength: 23 | Enabled: false 24 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in advent_of_code_cli.gemspec 6 | gemspec 7 | 8 | gem "rake", "~> 13.0" 9 | 10 | gem "minitest", "~> 5.0" 11 | 12 | gem "rubocop", "~> 1.21" 13 | 14 | gem "thor" 15 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | advent_of_code_cli (0.1.7) 5 | thor (>= 1.2.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | ast (2.4.2) 11 | json (2.6.2) 12 | minitest (5.16.3) 13 | parallel (1.22.1) 14 | parser (3.1.3.0) 15 | ast (~> 2.4.1) 16 | rainbow (3.1.1) 17 | rake (13.0.6) 18 | regexp_parser (2.6.1) 19 | rexml (3.2.5) 20 | rubocop (1.39.0) 21 | json (~> 2.3) 22 | parallel (~> 1.10) 23 | parser (>= 3.1.2.1) 24 | rainbow (>= 2.2.2, < 4.0) 25 | regexp_parser (>= 1.8, < 3.0) 26 | rexml (>= 3.2.5, < 4.0) 27 | rubocop-ast (>= 1.23.0, < 2.0) 28 | ruby-progressbar (~> 1.7) 29 | unicode-display_width (>= 1.4.0, < 3.0) 30 | rubocop-ast (1.24.0) 31 | parser (>= 3.1.1.0) 32 | ruby-progressbar (1.11.0) 33 | thor (1.2.1) 34 | unicode-display_width (2.3.0) 35 | 36 | PLATFORMS 37 | arm64-darwin-21 38 | 39 | DEPENDENCIES 40 | advent_of_code_cli! 41 | minitest (~> 5.0) 42 | rake (~> 13.0) 43 | rubocop (~> 1.21) 44 | thor 45 | 46 | BUNDLED WITH 47 | 2.3.24 48 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Emily Samp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🎄 Advent of Code CLI 2 | 3 | > ⚠️ **Note:** This tool is under active development. I built in in a couple hours with no automated tests. Things may change between versions! 4 | 5 | A little CLI tool that scaffolds and runs [Advent of Code](https://adventofcode.com) solutions in Ruby. 6 | 7 | This project is heavily based on [advent-of-code-rust](https://github.com/arturopala/advent-of-code-rust). Go check it out! 8 | 9 | ## Installation 10 | 11 | Add this line to your application's `Gemfile`: 12 | 13 | ```ruby 14 | gem "advent_of_code_cli" 15 | ``` 16 | 17 | Run `bundle install`. 18 | 19 | ## Usage 20 | 21 | ### Scaffold 22 | 23 | This command will set up the files for any day of Advent of Code. It takes a number between 1 and 25 as an argument. 24 | 25 | ```bash 26 | bundle exec aoc_cli scaffold 1 27 | ``` 28 | 29 | This will result in the following output: 30 | 31 | ``` 32 | Creating file: 01.rb... 33 | Creating inputs directory... 34 | Creating file: inputs/01.txt... 35 | Creating examples directory... 36 | Creating examples/01 directory... 37 | ``` 38 | 39 | The file `01.rb` will have the following structure: 40 | 41 | ```rb 42 | module Day01 43 | class << self 44 | def part_one(input) 45 | raise NotImplementedError 46 | end 47 | 48 | def part_two(input) 49 | raise NotImplementedError 50 | end 51 | end 52 | end 53 | ``` 54 | 55 | ...where `input` is an `Array[String]` of all the lines without newlines. 56 | 57 | I would love to make this structure configurable in the future. 58 | 59 | ### Download 60 | 61 | This command will download the input for a given day. 62 | 63 | In order for this to work, you must provide your Advent of Code session cookie to the program in an environment variable: 64 | 65 | ```bash 66 | export AOC_COOKIE=your-cookie 67 | ``` 68 | To obtain the cookie, sign into Advent of Code through your browser, then use the developer tools to examine the headers for a page request. The `cookie:` header should contain a value like `session=a1b2c3...`. The part from `a1b2c3...` onwards is what your need for `AOC_COOKIE`. 69 | 70 | Once the environment variable is set, you can request your personal input for any day. 71 | 72 | ```bash 73 | bundle exec aoc_cli download 1 74 | ``` 75 | 76 | This will create the following output: 77 | 78 | ``` 79 | Fetching input... 80 | Writing input to inputs/01.txt... 81 | Done! 82 | ``` 83 | 84 | By default, the CLI will request the input for this year, but you can request previous years' input by passing a `--year` flag. 85 | 86 | ```bash 87 | bundle exec aoc_cli download 1 --year 2021 88 | ``` 89 | 90 | ### Solve 91 | 92 | This command will run your solution to a certain day's puzzle. 93 | 94 | ``` 95 | bundle exec aoc_cli solve 1 96 | ``` 97 | 98 | This will create the following output: 99 | 100 | ``` 101 | Reading input... 102 | Loading solution... 103 | 104 | Running part one... 105 | Part one result: 10000 106 | Took 0.000259 seconds to solve 107 | 108 | Running part two... 109 | Part two result: 10000 110 | Took 0.00026 seconds to solve 111 | 112 | Done! 113 | ``` 114 | 115 | 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. 116 | 117 | ### Examples 118 | 119 | 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. 120 | 121 | #### Create a new example 122 | 123 | You can create a new file for example input by running the following command: 124 | 125 | ``` 126 | bundle exec aoc_cli example new 1 A 127 | ``` 128 | 129 | The first argument specifies the day, and the second argument is the name of the example. You may choose whatever name you'd like. 130 | 131 | This will generate the following output: 132 | 133 | ``` 134 | Creating examples/01/A.txt... 135 | Creating examples/01/A_expected.yml... 136 | Done! 137 | ``` 138 | 139 | - `examples/01/A.txt` is a blank text file where you can enter your own input for the problem. 140 | - `examples/01/A_expected.yml` is a YAML file with the following content: 141 | 142 | ``` 143 | part_one: ~ 144 | part_two: ~ 145 | ``` 146 | 147 | Replace the two tildes (`~`) with the expected result of running your solution against the example input provided. 148 | 149 | #### Running examples 150 | 151 | You can check your solution against an example with the following command: 152 | 153 | ``` 154 | bundle exec aoc_cli example solve 1 A 155 | ``` 156 | 157 | This will output the following: 158 | 159 | ``` 160 | Reading input... 161 | Loading solution... 162 | 163 | Running part one with example A... 164 | Part one result: 1034 ✅ 165 | Took 0.000259 seconds to solve 166 | 167 | Running part two with example A... 168 | Part two result: 7934 ✅ 169 | Took 0.000253 seconds to solve 170 | ``` 171 | 172 | ## Contributing 173 | 174 | Issues and code contributions are welcome! Happy Advent of Code to all who celebrate! 🎁 175 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rake/testtask" 5 | 6 | Rake::TestTask.new(:test) do |t| 7 | t.libs << "test" 8 | t.libs << "lib" 9 | t.test_files = FileList["test/**/test_*.rb"] 10 | end 11 | 12 | require "rubocop/rake_task" 13 | 14 | RuboCop::RakeTask.new 15 | 16 | task default: %i[test rubocop] 17 | -------------------------------------------------------------------------------- /advent_of_code_cli.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/advent_of_code_cli/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "advent_of_code_cli" 7 | spec.version = AdventOfCode::VERSION 8 | spec.authors = ["Emily Samp"] 9 | spec.email = ["emily.samp@icloud.com"] 10 | 11 | spec.summary = "A CLI tool to scaffold and run Advent of Code solutions." 12 | spec.homepage = "https://github.com/egiurleo/advent_of_code_cli" 13 | spec.license = "MIT" 14 | spec.required_ruby_version = ">= 2.6.0" 15 | 16 | spec.metadata["homepage_uri"] = spec.homepage 17 | spec.metadata["source_code_uri"] = spec.homepage 18 | 19 | # Specify which files should be added to the gem when it is released. 20 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 21 | spec.files = Dir.chdir(__dir__) do 22 | `git ls-files -z`.split("\x0").reject do |f| 23 | (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)}) 24 | end 25 | end 26 | spec.bindir = "exe" 27 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } 28 | spec.require_paths = ["lib"] 29 | spec.add_dependency("thor", ">= 1.2.0") 30 | spec.metadata["rubygems_mfa_required"] = "true" 31 | end 32 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "advent_of_code_cli" 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | # (If you use this, don't forget to add pry to your Gemfile!) 11 | # require "pry" 12 | # Pry.start 13 | 14 | require "irb" 15 | IRB.start(__FILE__) 16 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /exe/advent_of_code_cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "advent_of_code_cli" 5 | 6 | AdventOfCode::CLI.start(ARGV) 7 | -------------------------------------------------------------------------------- /exe/aoc_cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "advent_of_code_cli" 5 | 6 | AdventOfCode::CLI.start(ARGV) 7 | -------------------------------------------------------------------------------- /lib/advent_of_code_cli.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "thor" 4 | 5 | require_relative "advent_of_code_cli/version" 6 | require_relative "advent_of_code_cli/commands" 7 | 8 | module AdventOfCode 9 | class Error < StandardError; end 10 | class ExampleAlreadyExistsError < Error; end 11 | class InvalidDayError < Error; end 12 | class MissingCookieError < Error; end 13 | class MissingExampleError < Error; end 14 | class MissingInputError < Error; end 15 | class MissingSolutionError < Error; end 16 | 17 | class CLI < Thor 18 | desc "scaffold DAY", "generate files for day DAY" 19 | def scaffold(day) 20 | AdventOfCode::Commands::Scaffold.new(day: day.to_i).execute 21 | rescue AdventOfCode::InvalidDayError 22 | rescue_invalid_day_error 23 | end 24 | 25 | desc "download DAY", "download your input for day DAY" 26 | option :year, default: Time.now.year.to_s 27 | def download(day) 28 | AdventOfCode::Commands::Download.new(day: day.to_i, year: options[:year].to_i).execute 29 | rescue AdventOfCode::InvalidDayError 30 | rescue_invalid_day_error 31 | rescue AdventOfCode::MissingCookieError 32 | say "Error: Cannot find cookie in the AOC_COOKIE environment variable.", :red 33 | end 34 | 35 | desc "solve DAY", "run your solutions for day DAY" 36 | def solve(day) 37 | AdventOfCode::Commands::Solve.new(day: day.to_i).execute 38 | rescue AdventOfCode::InvalidDayError 39 | rescue_invalid_day_error 40 | rescue AdventOfCode::MissingInputError 41 | say "Error: Cannot find input file.", :red 42 | rescue AdventOfCode::MissingSolutionError 43 | say "Error: Cannot find solution file.", :red 44 | end 45 | 46 | desc "example", "create and run example files" 47 | subcommand "example", Commands::Example::CLI 48 | 49 | private 50 | 51 | def rescue_invalid_day_error 52 | say "Error: The day argument must be an integer between 1 and 25.", :red 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/advent_of_code_cli/commands.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "commands/command" 4 | require_relative "commands/download" 5 | require_relative "commands/example" 6 | require_relative "commands/scaffold" 7 | require_relative "commands/solve" 8 | -------------------------------------------------------------------------------- /lib/advent_of_code_cli/commands/command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AdventOfCode 4 | module Commands 5 | class Command 6 | include Thor::Base 7 | 8 | def initialize(day:) 9 | raise InvalidDayError unless day.is_a?(Integer) && day.positive? && day <= 25 10 | 11 | @day = day 12 | end 13 | 14 | private 15 | 16 | def day_string 17 | @day < 10 ? "0#{@day}" : @day.to_s 18 | end 19 | 20 | def solution_file_name 21 | "#{day_string}.rb" 22 | end 23 | 24 | def input_file_name 25 | "inputs/#{day_string}.txt" 26 | end 27 | 28 | def example_file_name 29 | "examples/#{day_string}/#{@name}.txt" 30 | end 31 | 32 | def example_expected_file_name 33 | "examples/#{day_string}/#{@name}_expected.yml" 34 | end 35 | 36 | def create_file(file_name, contents = nil) 37 | File.open(file_name, "w") do |file| 38 | file.puts contents if contents 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/advent_of_code_cli/commands/download.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "net/http" 4 | require "uri" 5 | 6 | module AdventOfCode 7 | module Commands 8 | class Download < Command 9 | def initialize(year:, day:) 10 | @year = year 11 | super(day: day) 12 | end 13 | 14 | def execute 15 | raise MissingCookieError unless cookie_present? && cookie 16 | 17 | say("Fetching input...") 18 | input = fetch_input 19 | 20 | unless Dir.exist?("inputs") 21 | say("Creating inputs directory...") 22 | Dir.mkdir("inputs") 23 | end 24 | 25 | say("Writing input to #{input_file_name}...") 26 | create_file(input_file_name, input) 27 | 28 | say("Done!", :green) 29 | end 30 | 31 | private 32 | 33 | def cookie 34 | @cookie ||= ENV.fetch("AOC_COOKIE", nil) 35 | end 36 | 37 | def cookie_present? 38 | ENV.key?("AOC_COOKIE") 39 | end 40 | 41 | def fetch_input 42 | url = URI("https://adventofcode.com/#{@year}/day/#{@day}/input") 43 | 44 | https = Net::HTTP.new(url.host, url.port) 45 | https.use_ssl = true 46 | 47 | request = Net::HTTP::Get.new(url) 48 | request["Cookie"] = "session=#{cookie}" 49 | # The creator of Advent of Code has requested that automated tools send identifying information 50 | # when making requests: https://www.reddit.com/r/adventofcode/comments/z9dhtd 51 | request["User-Agent"] = "github.com/egiurleo/advent_of_code_cli by emily.samp@icloud.com" 52 | 53 | response = https.request(request) 54 | response.read_body 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/advent_of_code_cli/commands/example.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "example/new" 4 | require_relative "example/solve" 5 | 6 | module AdventOfCode 7 | module Commands 8 | module Example 9 | class CLI < Thor 10 | desc "new DAY NAME", "creates an example file with name NAME for day DAY" 11 | def new(day, name) 12 | New.new(day: day.to_i, name: name).execute 13 | rescue ExampleAlreadyExistsError => e 14 | say(e.message, :red) 15 | rescue InvalidDayError 16 | rescue_invalid_day_error 17 | end 18 | 19 | desc "solve DAY NAME", "runs the example with name NAME for day DAY" 20 | def solve(day, name) 21 | Solve.new(day: day.to_i, name: name).execute 22 | rescue InvalidDayError 23 | rescue_invalid_day_error 24 | rescue MissingExampleError 25 | say "Error: Cannot find example file.", :red 26 | rescue AdventOfCode::MissingSolutionError 27 | say "Error: Cannot find solution file.", :red 28 | end 29 | 30 | private 31 | 32 | def rescue_invalid_day_error 33 | say "Error: The day argument must be an integer between 1 and 25.", :red 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/advent_of_code_cli/commands/example/new.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AdventOfCode 4 | module Commands 5 | module Example 6 | class New < Command 7 | def initialize(day:, name:) 8 | @name = name 9 | super(day: day) 10 | end 11 | 12 | def execute 13 | unless Dir.exist?("examples") 14 | say("Creating examples directory...") 15 | Dir.mkdir("examples") 16 | end 17 | 18 | unless Dir.exist?("examples/#{day_string}") 19 | say("Creating examples/#{day_string} directory...") 20 | Dir.mkdir("examples/#{day_string}") 21 | end 22 | 23 | if File.exist?(example_file_name) 24 | raise ExampleAlreadyExistsError, 25 | "could not create example file because file #{example_file_name} already exists" 26 | end 27 | 28 | say("Creating #{example_file_name}...") 29 | create_file(example_file_name) 30 | 31 | say("Creating #{example_expected_file_name}...") 32 | create_file(example_expected_file_name, expected_file_contents) 33 | 34 | say "Done!", :green 35 | end 36 | 37 | private 38 | 39 | def expected_file_contents 40 | <<~RUBY 41 | part_one: ~ 42 | part_two: ~ 43 | RUBY 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/advent_of_code_cli/commands/example/solve.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "yaml" 4 | 5 | module AdventOfCode 6 | module Commands 7 | module Example 8 | class Solve < Command 9 | def initialize(day:, name:) 10 | @name = name 11 | super(day: day) 12 | end 13 | 14 | def execute 15 | raise MissingExampleError unless File.exist?(example_file_name) 16 | raise MissingExampleError unless File.exist?(example_expected_file_name) 17 | raise MissingSolutionError unless File.exist?(solution_file_name) 18 | 19 | say "Reading input..." 20 | input = File.readlines(example_file_name, chomp: true) 21 | 22 | say "Loading solution..." 23 | load(solution_file_name) 24 | 25 | module_name = "Day#{day_string}" 26 | 27 | say "\nRunning part one with example #{@name}..." 28 | solution(module_name, "one", input) 29 | 30 | say "\nRunning part two with example #{@name}..." 31 | solution(module_name, "two", input) 32 | 33 | say "\nDone!", :green 34 | end 35 | 36 | private 37 | 38 | def solution(module_name, part, input) 39 | start_time = Time.now 40 | result = Object.const_get(module_name).send("part_#{part}", input) 41 | end_time = Time.now 42 | 43 | expected_result = expected_answers["part_#{part}"] 44 | 45 | if expected_result.nil? 46 | say "Part #{part} result: #{result} ⚠️ (no expectation provided)" 47 | elsif result == expected_result 48 | say "Part #{part} result: #{result} ✅" 49 | else 50 | say "Part #{part} result: #{result} ❌ (expected #{expected_result})" 51 | end 52 | 53 | say "Took #{end_time - start_time} seconds to solve" 54 | rescue NotImplementedError 55 | say "Part #{part} has not yet been implemented", :yellow 56 | end 57 | 58 | def expected_answers 59 | @expected_answers ||= YAML.safe_load(File.read(example_expected_file_name)) 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/advent_of_code_cli/commands/scaffold.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AdventOfCode 4 | module Commands 5 | class Scaffold < Command 6 | def execute 7 | 8 | unless File.exist?(solution_file_name) 9 | say("Creating file: #{solution_file_name}...") 10 | create_file(solution_file_name, solution_file_contents) 11 | end 12 | 13 | unless Dir.exist?("inputs") 14 | say("Creating inputs directory...") 15 | Dir.mkdir("inputs") 16 | end 17 | 18 | unless File.exist?(input_file_name) 19 | say("Creating file: #{input_file_name}...") 20 | create_file(input_file_name) 21 | end 22 | 23 | unless Dir.exist?("examples") 24 | say("Creating examples directory...") 25 | Dir.mkdir("examples") 26 | end 27 | 28 | unless Dir.exist?("examples/#{day_string}") 29 | say("Creating examples/#{day_string} directory...") 30 | Dir.mkdir("examples/#{day_string}") 31 | end 32 | 33 | say "Done!", :green 34 | end 35 | 36 | private 37 | 38 | def solution_file_contents 39 | <<~RUBY 40 | module Day#{day_string} 41 | class << self 42 | def part_one(input) 43 | raise NotImplementedError 44 | end 45 | 46 | def part_two(input) 47 | raise NotImplementedError 48 | end 49 | end 50 | end 51 | RUBY 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/advent_of_code_cli/commands/solve.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AdventOfCode 4 | module Commands 5 | class Solve < Command 6 | def execute 7 | raise MissingInputError unless File.exist?(input_file_name) 8 | raise MissingSolutionError unless File.exist?(solution_file_name) 9 | 10 | say "Reading input..." 11 | input = File.readlines(input_file_name, chomp: true) 12 | 13 | say "Loading solution..." 14 | load(solution_file_name) 15 | 16 | module_name = "Day#{day_string}" 17 | 18 | say "\nRunning part one..." 19 | solution(module_name, "one", input) 20 | 21 | say "\nRunning part two..." 22 | solution(module_name, "two", input) 23 | 24 | say "\nDone!", :green 25 | end 26 | 27 | private 28 | 29 | def solution(module_name, part, input) 30 | start_time = Time.now 31 | result = Object.const_get(module_name).send("part_#{part}", input) 32 | end_time = Time.now 33 | 34 | say "Part #{part} result: #{result}" 35 | say "Took #{end_time - start_time} seconds to solve" 36 | rescue NotImplementedError 37 | say "Part #{part} has not yet been implemented", :yellow 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/advent_of_code_cli/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AdventOfCode 4 | VERSION = "0.1.7" 5 | end 6 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path("../lib", __dir__) 4 | require "advent_of_code_cli" 5 | 6 | require "minitest/autorun" 7 | --------------------------------------------------------------------------------