feat: download puzzle descriptions (#21)

* add support for downloading puzzle descriptions in `cargo download`
* add `cargo read` command to read puzzles in terminal
* extract `aoc_cli` module
* use `aoc-cli`'s new overwrite option to eliminate temp files
This commit is contained in:
Felix Spöttel
2022-12-06 18:22:40 +01:00
committed by GitHub
parent 7ef01ab32f
commit 4d10812fd3
5 changed files with 193 additions and 78 deletions

View File

@@ -2,14 +2,12 @@
* This file contains template code.
* There is no need to edit this file unless you want to change template functionality.
*/
use std::io::Write;
use std::path::PathBuf;
use std::{env::temp_dir, io, process::Command};
use std::{fs, process};
use advent_of_code::aoc_cli;
use std::process;
struct Args {
day: u8,
year: Option<i16>,
year: Option<u16>,
}
fn parse_args() -> Result<Args, pico_args::Error> {
@@ -20,86 +18,29 @@ fn parse_args() -> Result<Args, pico_args::Error> {
})
}
fn remove_file(path: &PathBuf) {
#[allow(unused_must_use)]
{
fs::remove_file(path);
}
}
fn exit_with_status(status: i32, path: &PathBuf) -> ! {
remove_file(path);
process::exit(status);
}
fn main() {
// acquire a temp file path to write aoc-cli output to.
// aoc-cli expects this file not to be present - delete just in case.
let mut tmp_file_path = temp_dir();
tmp_file_path.push("aoc_input_tmp");
remove_file(&tmp_file_path);
let args = match parse_args() {
Ok(args) => args,
Err(e) => {
eprintln!("Failed to process arguments: {}", e);
exit_with_status(1, &tmp_file_path);
process::exit(1);
}
};
let day_padded = format!("{:02}", args.day);
let input_path = format!("src/inputs/{}.txt", day_padded);
// check if aoc binary exists and is callable.
if Command::new("aoc").arg("-V").output().is_err() {
if aoc_cli::check().is_err() {
eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it.");
exit_with_status(1, &tmp_file_path);
process::exit(1);
}
let mut cmd_args = vec![];
if let Some(year) = args.year {
cmd_args.push("--year".into());
cmd_args.push(year.to_string());
}
cmd_args.append(&mut vec![
"--input-file".into(),
tmp_file_path.to_string_lossy().to_string(),
"--day".into(),
args.day.to_string(),
"download".into(),
]);
println!("Downloading input with >aoc {}", cmd_args.join(" "));
match Command::new("aoc").args(cmd_args).output() {
match aoc_cli::download(args.day, args.year) {
Ok(cmd_output) => {
io::stdout()
.write_all(&cmd_output.stdout)
.expect("could not write cmd stdout to pipe.");
io::stderr()
.write_all(&cmd_output.stderr)
.expect("could not write cmd stderr to pipe.");
if !cmd_output.status.success() {
exit_with_status(1, &tmp_file_path);
process::exit(1);
}
}
Err(e) => {
eprintln!("failed to spawn aoc-cli: {}", e);
exit_with_status(1, &tmp_file_path);
}
}
match fs::copy(&tmp_file_path, &input_path) {
Ok(_) => {
println!("---");
println!("🎄 Successfully wrote input to \"{}\".", &input_path);
exit_with_status(0, &tmp_file_path);
}
Err(e) => {
eprintln!("could not copy downloaded input to input file: {}", e);
exit_with_status(1, &tmp_file_path);
process::exit(1);
}
}
}

46
src/bin/read.rs Normal file
View File

@@ -0,0 +1,46 @@
/*
* This file contains template code.
* There is no need to edit this file unless you want to change template functionality.
*/
use advent_of_code::aoc_cli;
use std::process;
struct Args {
day: u8,
year: Option<u16>,
}
fn parse_args() -> Result<Args, pico_args::Error> {
let mut args = pico_args::Arguments::from_env();
Ok(Args {
day: args.free_from_str()?,
year: args.opt_value_from_str(["-y", "--year"])?,
})
}
fn main() {
let args = match parse_args() {
Ok(args) => args,
Err(e) => {
eprintln!("Failed to process arguments: {}", e);
process::exit(1);
}
};
if aoc_cli::check().is_err() {
eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it.");
process::exit(1);
}
match aoc_cli::read(args.day, args.year) {
Ok(cmd_output) => {
if !cmd_output.status.success() {
process::exit(1);
}
}
Err(e) => {
eprintln!("failed to spawn aoc-cli: {}", e);
process::exit(1);
}
}
}

View File

@@ -123,3 +123,111 @@ mod tests {
);
}
}
pub mod aoc_cli {
use std::{
fmt::Display,
fs::create_dir_all,
process::{Command, Output, Stdio},
};
pub enum AocCliError {
CommandNotFound,
CommandNotCallable,
BadExitStatus(Output),
IoError,
}
impl Display for AocCliError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AocCliError::CommandNotFound => write!(f, "aoc-cli is not present in environment."),
AocCliError::CommandNotCallable => write!(f, "aoc-cli could not be called."),
AocCliError::BadExitStatus(_) => write!(f, "aoc-cli exited with a non-zero status."),
AocCliError::IoError => write!(f, "could not write output files to file system."),
}
}
}
pub fn check() -> Result<(), AocCliError> {
Command::new("aoc")
.arg("-V")
.output()
.map_err(|_| AocCliError::CommandNotFound)?;
Ok(())
}
pub fn read(day: u8, year: Option<u16>) -> Result<Output, AocCliError> {
// TODO: output local puzzle if present.
let args = build_args("read", &[], day, year);
call_aoc_cli(&args)
}
pub fn download(day: u8, year: Option<u16>) -> Result<Output, AocCliError> {
let input_path = get_input_path(day);
let puzzle_path = get_puzzle_path(day);
create_dir_all("src/puzzles").map_err(|_| AocCliError::IoError)?;
let args = build_args(
"download",
&[
"--overwrite".into(),
"--input-file".into(),
input_path.to_string(),
"--puzzle-file".into(),
puzzle_path.to_string(),
],
day,
year,
);
let output = call_aoc_cli(&args)?;
if output.status.success() {
println!("---");
println!("🎄 Successfully wrote input to \"{}\".", &input_path);
println!("🎄 Successfully wrote puzzle to \"{}\".", &puzzle_path);
Ok(output)
} else {
Err(AocCliError::BadExitStatus(output))
}
}
fn get_input_path(day: u8) -> String {
let day_padded = format!("{:02}", day);
format!("src/inputs/{}.txt", day_padded)
}
fn get_puzzle_path(day: u8) -> String {
let day_padded = format!("{:02}", day);
format!("src/puzzles/{}.md", day_padded)
}
fn build_args(command: &str, args: &[String], day: u8, year: Option<u16>) -> Vec<String> {
let mut cmd_args = args.to_vec();
if let Some(year) = year {
cmd_args.push("--year".into());
cmd_args.push(year.to_string());
}
cmd_args.append(&mut vec!["--day".into(), day.to_string(), command.into()]);
cmd_args
}
fn call_aoc_cli(args: &[String]) -> Result<Output, AocCliError> {
if cfg!(debug_assertions) {
println!("Calling >aoc with: {}", args.join(" "));
}
Command::new("aoc")
.args(args)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.output()
.map_err(|_| AocCliError::CommandNotCallable)
}
}