Skip to content

Commit 49123f7

Browse files
committed
feat: implement bash scripts in rust
enables cross-platform support, closes #1. scaffold scripts adapted from code on @steventhorne's fork, thx!
1 parent 7b0b9f1 commit 49123f7

File tree

10 files changed

+220
-127
lines changed

10 files changed

+220
-127
lines changed

.cargo/config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
[alias]
22
rr = "run --release"
3+
scaffold = "run --bin scaffold -- "
4+
download = "run --bin download -- "

.github/workflows/readme-stars.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Update readme progress tracker
1+
name: Update readme ⭐️ progress
22

33
on:
44
schedule:

Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ default-run = "aoc"
77
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
88

99
[dependencies]
10+
pico-args = "0.5.0"

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
## Setup
1111

12-
### Create your _advent of code_ repository
12+
### Create your repository
1313

1414
1. Open [the template repository](https://github.com/fspoettel/advent-of-code-rust) on Github.
1515
2. Click `Use this template` and create your repository.
@@ -30,8 +30,8 @@
3030
### Setup new day
3131

3232
```sh
33-
# example: `./bin/scaffold 1`
34-
./bin/scaffold <day>
33+
# example: `cargo scaffold 1`
34+
cargo scaffold <day>
3535

3636
# output:
3737
# Created module "src/bin/01.rs"
@@ -51,10 +51,11 @@ Every [solution](https://github.com/fspoettel/advent-of-code-rust/blob/master/bi
5151
> This command requires configuring the optional [automatic input downloads](#automatic-input-downloads) feature.
5252
5353
```sh
54-
# example: `./bin/download 1`
55-
./bin/download <day>
54+
# example: `cargo download 1`
55+
cargo download <day>
5656

5757
# output:
58+
# Downloading input with aoc-cli...
5859
# Loaded session cookie from "/home/felix/.adventofcode.session".
5960
# Downloading input for day 1, 2021...
6061
# 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
6364
# 🎄 Successfully wrote input to "src/inputs/01.txt"!
6465
```
6566

66-
To download inputs for previous years, append the `--year` flag. _(example: `./bin/download 1 --year 2020`)_
67+
To download inputs for previous years, append the `--year` flag. _(example: `cargo download 1 --year 2020`)_
6768

6869
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.
6970

bin/download

Lines changed: 0 additions & 56 deletions
This file was deleted.

bin/scaffold

Lines changed: 0 additions & 64 deletions
This file was deleted.

src/bin/.keep

Whitespace-only changes.

src/bin/download.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use std::io::Write;
2+
use std::path::PathBuf;
3+
use std::{env::temp_dir, io, process::Command};
4+
use std::{fs, process};
5+
6+
struct Args {
7+
day: u8,
8+
year: Option<u32>,
9+
}
10+
11+
fn parse_args() -> Result<Args, pico_args::Error> {
12+
let mut args = pico_args::Arguments::from_env();
13+
Ok(Args {
14+
day: args.free_from_str()?,
15+
year: args.opt_value_from_str("--year")?,
16+
})
17+
}
18+
19+
fn remove_file(path: &PathBuf) {
20+
#[allow(unused_must_use)]
21+
{
22+
fs::remove_file(path);
23+
}
24+
}
25+
26+
fn exit_with_status(status: i32, path: &PathBuf) -> ! {
27+
remove_file(path);
28+
process::exit(status);
29+
}
30+
31+
fn main() {
32+
// acquire a temp file path to write aoc-cli output to.
33+
// aoc-cli expects this file not to be present - delete just in case.
34+
let mut tmp_file_path = temp_dir();
35+
tmp_file_path.push("aoc_input_tmp");
36+
remove_file(&tmp_file_path);
37+
38+
let args = match parse_args() {
39+
Ok(args) => args,
40+
Err(e) => {
41+
eprintln!("Failed to process arguments: {}", e);
42+
exit_with_status(1, &tmp_file_path);
43+
}
44+
};
45+
46+
let day_padded = format!("{:02}", args.day);
47+
let input_path = format!("src/inputs/{}.txt", day_padded);
48+
49+
// check if aoc binary exists and is callable.
50+
if Command::new("aoc").arg("-V").output().is_err() {
51+
eprintln!("command \"aoc\" not found or not callable. Try running \"cargo install aoc-cli\" to install it.");
52+
exit_with_status(1, &tmp_file_path);
53+
}
54+
55+
println!("Downloading input via aoc-cli...");
56+
57+
let mut cmd_args = vec![
58+
"download".into(),
59+
"--file".into(),
60+
tmp_file_path.to_string_lossy().to_string(),
61+
"--day".into(),
62+
args.day.to_string(),
63+
];
64+
65+
if let Some(year) = args.year {
66+
cmd_args.push("--year".into());
67+
cmd_args.push(year.to_string());
68+
}
69+
70+
match Command::new("aoc").args(cmd_args).output() {
71+
Ok(cmd_output) => {
72+
io::stdout()
73+
.write_all(&cmd_output.stdout)
74+
.expect("could not cmd stdout to pipe.");
75+
io::stderr()
76+
.write_all(&cmd_output.stderr)
77+
.expect("could not cmd stderr to pipe.");
78+
if !cmd_output.status.success() {
79+
exit_with_status(1, &tmp_file_path);
80+
}
81+
}
82+
Err(e) => {
83+
eprintln!("failed to spawn aoc-cli: {}", e);
84+
exit_with_status(1, &tmp_file_path);
85+
}
86+
}
87+
88+
match fs::copy(&tmp_file_path, &input_path) {
89+
Ok(_) => {
90+
println!("---");
91+
println!("🎄 Successfully wrote input to \"{}\".", &input_path);
92+
exit_with_status(0, &tmp_file_path);
93+
}
94+
Err(e) => {
95+
eprintln!("could not copy to input file: {}", e);
96+
exit_with_status(1, &tmp_file_path);
97+
}
98+
}
99+
}

src/bin/scaffold.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use std::{
2+
fs::{File, OpenOptions},
3+
io::Write,
4+
process,
5+
};
6+
7+
const MODULE_TEMPLATE: &str = r###"pub fn part_one(input: &str) -> u32 {
8+
0
9+
}
10+
11+
pub fn part_two(input: &str) -> u32 {
12+
0
13+
}
14+
15+
fn main() {
16+
aoc::solve!(&aoc::read_file("inputs", DAY), part_one, part_two)
17+
}
18+
19+
#[cfg(test)]
20+
mod tests {
21+
use super::*;
22+
#[test]
23+
fn test_part_one() {
24+
use aoc::read_file;
25+
let input = read_file("examples", DAY);
26+
assert_eq!(part_one(&input), 0);
27+
}
28+
#[test]
29+
fn test_part_two() {
30+
use aoc::read_file;
31+
let input = read_file("examples", DAY);
32+
assert_eq!(part_two(&input), 0);
33+
}
34+
}
35+
"###;
36+
37+
fn parse_args() -> Result<u8, pico_args::Error> {
38+
let mut args = pico_args::Arguments::from_env();
39+
args.free_from_str()
40+
}
41+
42+
fn safe_create_file(path: &str) -> Result<File, std::io::Error> {
43+
OpenOptions::new().write(true).create_new(true).open(path)
44+
}
45+
46+
fn main() {
47+
let day = match parse_args() {
48+
Ok(day) => day,
49+
Err(_) => {
50+
eprintln!("Need to specify a day (as integer). example: `cargo scaffold 7`");
51+
process::exit(1);
52+
}
53+
};
54+
55+
let day_padded = format!("{:02}", day);
56+
57+
let input_path = format!("src/inputs/{}.txt", day);
58+
let example_path = format!("src/examples/{}.txt", day);
59+
let module_path = format!("src/bin/{}.rs", day);
60+
61+
let mut file = match safe_create_file(&module_path) {
62+
Ok(file) => file,
63+
Err(e) => {
64+
eprintln!("Failed to create module file: {}", e);
65+
process::exit(1);
66+
}
67+
};
68+
69+
match file.write_all(MODULE_TEMPLATE.replace("DAY", &day_padded).as_bytes()) {
70+
Ok(_) => {
71+
println!("Created module file \"{}\"", &module_path);
72+
}
73+
Err(e) => {
74+
eprintln!("Failed to write module contents: {}", e);
75+
process::exit(1);
76+
}
77+
}
78+
79+
match safe_create_file(&input_path) {
80+
Ok(_) => {
81+
println!("Created empty input file \"{}\"", &input_path);
82+
}
83+
Err(e) => {
84+
eprintln!("Failed to create input file: {}", e);
85+
process::exit(1);
86+
}
87+
}
88+
89+
match safe_create_file(&example_path) {
90+
Ok(_) => {
91+
println!("Created empty example file \"{}\"", &example_path);
92+
}
93+
Err(e) => {
94+
eprintln!("Failed to create example file: {}", e);
95+
process::exit(1);
96+
}
97+
}
98+
99+
println!("---");
100+
println!("🎄 Type `cargo run --bin {}` to run your solution.", &day);
101+
}

0 commit comments

Comments
 (0)