diff --git a/.cargo/config b/.cargo/config index 6fbe0f6..abaea5b 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,2 +1,4 @@ [alias] rr = "run --release" +scaffold = "run --bin scaffold -- " +download = "run --bin download -- " diff --git a/.github/workflows/readme-stars.yml b/.github/workflows/readme-stars.yml index 43ad024..e1b2b1e 100644 --- a/.github/workflows/readme-stars.yml +++ b/.github/workflows/readme-stars.yml @@ -1,4 +1,4 @@ -name: Update readme progress tracker +name: Update readme ⭐️ progress on: schedule: diff --git a/Cargo.lock b/Cargo.lock index 8e731db..2026bc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,3 +5,12 @@ version = 3 [[package]] name = "aoc" version = "0.2.0" +dependencies = [ + "pico-args", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" diff --git a/Cargo.toml b/Cargo.toml index abfde04..1618d73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ default-run = "aoc" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +pico-args = "0.5.0" diff --git a/README.md b/README.md index 1533d2b..6464e6a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ## Setup -### Create your _advent of code_ repository +### Create your repository 1. Open [the template repository](https://github.com/fspoettel/advent-of-code-rust) on Github. 2. Click `Use this template` and create your repository. @@ -30,8 +30,8 @@ ### Setup new day ```sh -# example: `./bin/scaffold 1` -./bin/scaffold +# example: `cargo scaffold 1` +cargo scaffold # output: # Created module "src/bin/01.rs" @@ -51,10 +51,11 @@ Every [solution](https://github.com/fspoettel/advent-of-code-rust/blob/master/bi > This command requires configuring the optional [automatic input downloads](#automatic-input-downloads) feature. ```sh -# example: `./bin/download 1` -./bin/download +# example: `cargo download 1` +cargo download # output: +# Downloading input with aoc-cli... # Loaded session cookie from "/home/felix/.adventofcode.session". # Downloading input for day 1, 2021... # Saving puzzle input to "/tmp/tmp.MBdcAdL9Iw/input"... @@ -63,7 +64,7 @@ Every [solution](https://github.com/fspoettel/advent-of-code-rust/blob/master/bi # 🎄 Successfully wrote input to "src/inputs/01.txt"! ``` -To download inputs for previous years, append the `--year` flag. _(example: `./bin/download 1 --year 2020`)_ +To download inputs for previous years, append the `--year` flag. _(example: `cargo download 1 --year 2020`)_ Puzzle inputs are not checked into git. [See here](https://old.reddit.com/r/adventofcode/comments/k99rod/sharing_input_data_were_we_requested_not_to/gf2ukkf/?context=3) why. diff --git a/bin/download b/bin/download deleted file mode 100755 index 9d32f8c..0000000 --- a/bin/download +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/bash - -set -e; - -if ! command -v 'aoc' &> /dev/null -then - echo "command \`aoc\` not found. Try running \`cargo install aoc-cli\` to install it." - exit 1 -fi - -POSITIONAL=() -while [[ $# -gt 0 ]]; do - key="$1" - - case $key in - -y|--year) - year="$2" - shift - shift - ;; - *) - POSITIONAL+=("$1") - shift - ;; - esac -done - -set -- "${POSITIONAL[@]}" - -if [ -z "$1" ]; then - >&2 echo "Argument is required for day." - exit 1 -fi - -day=$(echo "$1" | sed 's/^0*//'); -day_padded=$(printf %02d "$day"); - -filename="$day_padded"; -input_path="src/inputs/$filename.txt"; - -tmp_dir=$(mktemp -d); -tmp_file_path="$tmp_dir/input"; - -if [[ "$year" != "" ]] -then -aoc download --day "$day" --year "$year" --file "$tmp_file_path"; -else -aoc download --day "$day" --file "$tmp_file_path"; -fi - -cat "$tmp_file_path" > "$input_path"; -echo "---" -echo "🎄 Successfully wrote input to \"$input_path\"!" - -trap "exit 1" HUP INT PIPE QUIT TERM -trap 'rm -rf "$tmp_dir"' EXIT diff --git a/bin/scaffold b/bin/scaffold deleted file mode 100755 index 49a1e48..0000000 --- a/bin/scaffold +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash - -set -e; - -if [ -z "$1" ]; then - >&2 echo "Argument is required for day." - exit 1 -fi - -day=$(echo "$1" | sed 's/^0*//'); -day_padded=$(printf %02d "$day"); - -filename="$day_padded"; - -input_path="src/inputs/$filename.txt"; -example_path="src/examples/$filename.txt"; -module_path="src/bin/$filename.rs"; - -touch "$module_path"; - -cat > "$module_path" < u32 { - 0 -} - -pub fn part_two(input: &str) -> u32 { - 0 -} - -fn main() { - aoc::solve!(&aoc::read_file("inputs", DAYNUM), part_one, part_two) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_part_one() { - use aoc::read_file; - let input = read_file("examples", DAYNUM); - assert_eq!(part_one(&input), 0); - } - - #[test] - fn test_part_two() { - use aoc::read_file; - let input = read_file("examples", DAYNUM); - assert_eq!(part_two(&input), 0); - } -} -EOF - -perl -pi -e "s,DAYNUM,$day,g" "$module_path"; - -echo "Created module \"$module_path\""; - -touch "$input_path"; -echo "Created empty input file \"$input_path\""; - -touch "$example_path"; -echo "Created empty example file \"$example_path\""; -echo "---" -echo "🎄 Type \`cargo run --bin $day_padded\` to run your solution." diff --git a/src/bin/.keep b/src/bin/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/src/bin/download.rs b/src/bin/download.rs new file mode 100644 index 0000000..0525596 --- /dev/null +++ b/src/bin/download.rs @@ -0,0 +1,99 @@ +use std::io::Write; +use std::path::PathBuf; +use std::{env::temp_dir, io, process::Command}; +use std::{fs, process}; + +struct Args { + day: u8, + year: Option, +} + +fn parse_args() -> Result { + let mut args = pico_args::Arguments::from_env(); + Ok(Args { + day: args.free_from_str()?, + year: args.opt_value_from_str("--year")?, + }) +} + +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); + } + }; + + 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() { + eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it."); + exit_with_status(1, &tmp_file_path); + } + + println!("Downloading input via aoc-cli..."); + + let mut cmd_args = vec![ + "download".into(), + "--file".into(), + tmp_file_path.to_string_lossy().to_string(), + "--day".into(), + args.day.to_string(), + ]; + + if let Some(year) = args.year { + cmd_args.push("--year".into()); + cmd_args.push(year.to_string()); + } + + match Command::new("aoc").args(cmd_args).output() { + Ok(cmd_output) => { + io::stdout() + .write_all(&cmd_output.stdout) + .expect("could not cmd stdout to pipe."); + io::stderr() + .write_all(&cmd_output.stderr) + .expect("could not cmd stderr to pipe."); + if !cmd_output.status.success() { + exit_with_status(1, &tmp_file_path); + } + } + 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 to input file: {}", e); + exit_with_status(1, &tmp_file_path); + } + } +} diff --git a/src/bin/scaffold.rs b/src/bin/scaffold.rs new file mode 100644 index 0000000..b69440b --- /dev/null +++ b/src/bin/scaffold.rs @@ -0,0 +1,101 @@ +use std::{ + fs::{File, OpenOptions}, + io::Write, + process, +}; + +const MODULE_TEMPLATE: &str = r###"pub fn part_one(input: &str) -> u32 { + 0 +} + +pub fn part_two(input: &str) -> u32 { + 0 +} + +fn main() { + aoc::solve!(&aoc::read_file("inputs", DAY), part_one, part_two) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_part_one() { + use aoc::read_file; + let input = read_file("examples", DAY); + assert_eq!(part_one(&input), 0); + } + #[test] + fn test_part_two() { + use aoc::read_file; + let input = read_file("examples", DAY); + assert_eq!(part_two(&input), 0); + } +} +"###; + +fn parse_args() -> Result { + let mut args = pico_args::Arguments::from_env(); + args.free_from_str() +} + +fn safe_create_file(path: &str) -> Result { + OpenOptions::new().write(true).create_new(true).open(path) +} + +fn main() { + let day = match parse_args() { + Ok(day) => day, + Err(_) => { + eprintln!("Need to specify a day (as integer). example: `cargo scaffold 7`"); + process::exit(1); + } + }; + + let day_padded = format!("{:02}", day); + + let input_path = format!("src/inputs/{}.txt", day); + let example_path = format!("src/examples/{}.txt", day); + let module_path = format!("src/bin/{}.rs", day); + + let mut file = match safe_create_file(&module_path) { + Ok(file) => file, + Err(e) => { + eprintln!("Failed to create module file: {}", e); + process::exit(1); + } + }; + + match file.write_all(MODULE_TEMPLATE.replace("DAY", &day_padded).as_bytes()) { + Ok(_) => { + println!("Created module file \"{}\"", &module_path); + } + Err(e) => { + eprintln!("Failed to write module contents: {}", e); + process::exit(1); + } + } + + match safe_create_file(&input_path) { + Ok(_) => { + println!("Created empty input file \"{}\"", &input_path); + } + Err(e) => { + eprintln!("Failed to create input file: {}", e); + process::exit(1); + } + } + + match safe_create_file(&example_path) { + Ok(_) => { + println!("Created empty example file \"{}\"", &example_path); + } + Err(e) => { + eprintln!("Failed to create example file: {}", e); + process::exit(1); + } + } + + println!("---"); + println!("🎄 Type `cargo run --bin {}` to run your solution.", &day); +}