Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configuration #12

Merged
merged 15 commits into from
Nov 29, 2022
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ GEM

PLATFORMS
arm64-darwin-21
ruby

DEPENDENCIES
advent!
Expand Down
43 changes: 40 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,26 @@ If bundler is not being used to manage dependencies, install the gem by executin

## Usage

Initialise a new project somewhere:

```bash
mkdir advent_of_code && cd advent_of_code

# create a blank advent.yml config file
advent init
```

Configuration values and format are explained in the [Config](#config) section.

Advent expects you to have a working directory resembling something like:

$ tree
.
├── 2015
└── 2016
├── 2016
└── advent.yml

Some commands can be run from within a directory for a specific year, but it's
better to run from the parent directory where possible.
You can run commands from anywhere under this directory.

The typical flow for tackling a daily challenge would be:

Expand All @@ -40,6 +51,32 @@ A list of commands and help is available using `advent`:

$ advent help

## Config

The config file should be at the root of your working directory called
`advent.yml`. The default values if you don't provide an override are:

```yaml
download_when_generating: true
remember_session: true
```

### Config explained

<dl>
<dt>download_when_generating</dt>
<dd>
When you run `advent generate` it will automatically download the input file to
go with it
</dd>

<dt>remember_session</dt>
<dd>
Save your session cookie in `.advent_session` when prompted so you don't need to
find it again
</dd>
</dl>

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
Expand Down
8 changes: 8 additions & 0 deletions advent.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,12 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"]

spec.add_dependency "thor", "~> 1.2"

spec.post_install_message = "
advent v0.1.5 requires a config file in your working directory.

See #{spec.homepage}/blob/main/README.md#usage or if you're
brave run `advent init` in your current directory.

"
end
24 changes: 23 additions & 1 deletion lib/advent.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

require_relative "advent/configuration"
require_relative "advent/input"
require_relative "advent/session"
require_relative "advent/solution"
Expand All @@ -10,8 +11,29 @@ module Advent
class Error < StandardError; end

class << self
def config
@_config ||= Configuration.from_file(root.join(Configuration::FILE_NAME))
end

def root
if (location = find_config_location)
location
else
raise Error, "Cannot find advent.yml config file in current or parent directories."
end
end

def session
@_session = Session.new
@_session ||= Session.new
end

private

def find_config_location
Pathname.new(Dir.pwd).ascend do |path|
return path if File.exist? path.join(Configuration::FILE_NAME)
return nil if path.to_s == "/"
end
end
end
end
93 changes: 28 additions & 65 deletions lib/advent/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,89 +9,68 @@ module Advent
class CLI < Thor
include Thor::Actions

class_option :root_path, default: Dir.pwd, hide: true, check_default_type: false
class_option :http_module, default: Net::HTTP, check_default_type: false

def initialize(*args)
super

self.destination_root = root_path
source_paths << File.expand_path("templates", __dir__)

# Don't try to load Advent.root if we're running init
unless args.last[:current_command]&.name == "init"
self.destination_root = Advent.root
end
end

# @return [Boolean] defines whether an exit status is set if a command fails
def self.exit_on_failure?
true
end

no_commands do
# @return [Boolean] whether the current root_path option is in a
# directory that looks like a year (eg. 2015)
def in_year_directory?
dir = root_path.basename.to_s
dir =~ /^20[0-9]{2}/
end
end

desc "download YEAR DAY", "Download the input for YEAR and DAY"
def download(year_or_day, day = nil)
year, day = determine_year_and_day(year_or_day, day)

if (error_message = validate(year, day))
say_error error_message, :red
return
end
def download(year, day)
require "advent/cli/downloader"

subpath = if in_year_directory?
""
else
"#{year}/"
end

unless Advent.session.exist?
session = ask "What is your Advent of Code session cookie value?", echo: false
Advent.session.value = session

say "\n\nThanks. Psst, we're going to save this for next time. It's in .advent_session if you need to update or delete it.\n\n"
end

input = Advent::Input.new(root_path.join(subpath), day: day.to_i)

if input.download(Advent.session.value, options.http_module)
say "Input downloaded to #{input.file_path}.", :green
say "\nUsing #load_input in your daily solution will load the input file for you."
else
say_error "Something went wrong, maybe an old session cookie?", :red
Dir.chdir Advent.root do
Downloader.new(self, year, day).download
end
end

desc "generate YEAR DAY", "Generate a new solution for YEAR and DAY"
# Generates a new solution file. If within a year directory, only the day
# is used, otherwise both the year and day will be required to generate the
# output.
def generate(year_or_day, day = nil)
year, day = determine_year_and_day(year_or_day, day)
def generate(year, day)
year = parse_number year
day = parse_number day

if (error_message = validate(year, day))
say_error error_message, :red
if (message = validate(year, day))
say_error message, :red
return
end

subpath = if in_year_directory?
template "solution.rb.tt", "#{year}/day#{day}.rb", context: binding
template "solution_test.rb.tt", "#{year}/test/day#{day}_test.rb", context: binding

download year, day if Advent.config.download_when_generating
end

desc "init DIR", "Initialise a new advent project in DIR"
def init(dir = ".")
create_file Pathname.getwd.join(dir).join(Advent::Configuration::FILE_NAME) do
""
else
"#{year}/"
end

template "solution.rb.tt", "#{subpath}day#{day}.rb", context: binding
template "solution_test.rb.tt", "#{subpath}test/day#{day}_test.rb", context: binding
end

desc "solve FILE", "Solve your solution"
# Runs a solution file, outputting both :part1 and :part2 method return values.
def solve(path)
require "advent/cli/solver"
Solver.new(self, root_path.join(path)).solve
file_path = Pathname.getwd.join(path)

Dir.chdir Advent.root do
Solver.new(self, file_path.relative_path_from(Advent.root)).solve
end
end

desc "version", "Prints the current version of the gem"
Expand All @@ -102,22 +81,6 @@ def version

private

def determine_year_and_day(year_or_day, day)
if in_year_directory?
[root_path.basename.to_s, parse_number(year_or_day)]
else
[year_or_day, parse_number(day)]
end
end

def root_path
@_root_path ||= if options.root_path.is_a?(Pathname)
options.root_path
else
Pathname.new(options.root_path)
end
end

def parse_number(str)
if (m = str.match(/[0-9]+/))
m[0]
Expand Down
32 changes: 32 additions & 0 deletions lib/advent/cli/downloader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

class Advent::CLI::Downloader
def initialize(command, year, day)
@command = command
@year = year
@day = day
end

def download
ask_for_session_cookie_if_needed
input = Advent::Input.new(Advent.root.join(@year), day: @day.to_i)

if input.download(Advent.session.value, @command.options.http_module)
@command.say "Input downloaded to #{input.file_path}.", :green
@command.say "\nUsing #load_input in your daily solution will load the input file for you."
else
@command.say_error "Something went wrong, maybe an old session cookie?", :red
end
end

private

def ask_for_session_cookie_if_needed
return if Advent.session.exist?

session = @command.ask "What is your Advent of Code session cookie value?", echo: false
Advent.session.value = session

@command.say "\n\nThanks. Psst, we're going to save this for next time. It's in .advent_session if you need to update or delete it.\n\n"
end
end
14 changes: 5 additions & 9 deletions lib/advent/cli/solver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def solve
load @path, Solutions
solution = Solutions.const_get(solution_class_name).new
else
require @path
require @path.expand_path
solution = Object.const_get(solution_class_name).new
end

Expand All @@ -37,15 +37,11 @@ def solve

private

def day
@_day ||= @path.basename.to_s.match(/day([0-9]+)\.rb/)[1]
end

def solution_file_name
"day#{day}.rb"
end

def solution_class_name
"Day#{day}"
end

def day
@_day ||= @path.basename.to_s.match(/day([0-9]+)\.rb/)[1]
end
end
30 changes: 30 additions & 0 deletions lib/advent/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require "psych"

module Advent
class Configuration
DEFAULTS = {
"download_when_generating" => true,
"remember_session" => true
}
FILE_NAME = "advent.yml"

attr_reader :download_when_generating, :remember_session

class << self
def from_file(file = FILE_NAME)
if RUBY_VERSION >= "3.1"
new Psych.safe_load_file(file)
else
new Psych.safe_load(File.read(file))
end
end
end

def initialize(conf)
config = DEFAULTS.merge(conf || {})

@download_when_generating = config.dig("download_when_generating")
@remember_session = config.dig("remember_session")
end
end
end
15 changes: 13 additions & 2 deletions lib/advent/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,22 @@ def exist?
end

def value=(val)
File.write file_name, val
if save_to_disk?
File.write file_name, val
else
@_value = val
end
end

def value
File.read file_name if exist?
return @_value unless save_to_disk?
return File.read(file_name) if exist?
end

private

def save_to_disk?
Advent.config.remember_session
end
end
end
Loading