feat: implement bash scripts in rust
enables cross-platform support, closes #1. scaffold scripts adapted from code on @steventhorne's fork, thx!
This commit is contained in:
99
src/bin/download.rs
Normal file
99
src/bin/download.rs
Normal file
@@ -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<u32>,
|
||||
}
|
||||
|
||||
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("--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);
|
||||
}
|
||||
}
|
||||
}
|
||||
101
src/bin/scaffold.rs
Normal file
101
src/bin/scaffold.rs
Normal file
@@ -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<u8, pico_args::Error> {
|
||||
let mut args = pico_args::Arguments::from_env();
|
||||
args.free_from_str()
|
||||
}
|
||||
|
||||
fn safe_create_file(path: &str) -> Result<File, std::io::Error> {
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user