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:
parent
7b0b9f100c
commit
49123f790a
10 changed files with 220 additions and 127 deletions
|
@ -1,2 +1,4 @@
|
||||||
[alias]
|
[alias]
|
||||||
rr = "run --release"
|
rr = "run --release"
|
||||||
|
scaffold = "run --bin scaffold -- "
|
||||||
|
download = "run --bin download -- "
|
||||||
|
|
2
.github/workflows/readme-stars.yml
vendored
2
.github/workflows/readme-stars.yml
vendored
|
@ -1,4 +1,4 @@
|
||||||
name: Update readme progress tracker
|
name: Update readme ⭐️ progress
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
|
|
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -5,3 +5,12 @@ version = 3
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aoc"
|
name = "aoc"
|
||||||
version = "0.2.0"
|
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"
|
||||||
|
|
|
@ -7,3 +7,4 @@ default-run = "aoc"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
pico-args = "0.5.0"
|
||||||
|
|
13
README.md
13
README.md
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
## Setup
|
## 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.
|
1. Open [the template repository](https://github.com/fspoettel/advent-of-code-rust) on Github.
|
||||||
2. Click `Use this template` and create your repository.
|
2. Click `Use this template` and create your repository.
|
||||||
|
@ -30,8 +30,8 @@
|
||||||
### Setup new day
|
### Setup new day
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# example: `./bin/scaffold 1`
|
# example: `cargo scaffold 1`
|
||||||
./bin/scaffold <day>
|
cargo scaffold <day>
|
||||||
|
|
||||||
# output:
|
# output:
|
||||||
# Created module "src/bin/01.rs"
|
# 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.
|
> This command requires configuring the optional [automatic input downloads](#automatic-input-downloads) feature.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# example: `./bin/download 1`
|
# example: `cargo download 1`
|
||||||
./bin/download <day>
|
cargo download <day>
|
||||||
|
|
||||||
# output:
|
# output:
|
||||||
|
# Downloading input with aoc-cli...
|
||||||
# Loaded session cookie from "/home/felix/.adventofcode.session".
|
# Loaded session cookie from "/home/felix/.adventofcode.session".
|
||||||
# Downloading input for day 1, 2021...
|
# Downloading input for day 1, 2021...
|
||||||
# Saving puzzle input to "/tmp/tmp.MBdcAdL9Iw/input"...
|
# 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"!
|
# 🎄 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.
|
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.
|
||||||
|
|
||||||
|
|
56
bin/download
56
bin/download
|
@ -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
|
|
64
bin/scaffold
64
bin/scaffold
|
@ -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" <<EOF
|
|
||||||
pub fn part_one(input: &str) -> 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."
|
|
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);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue