Initial commit
This commit is contained in:
61
src/bin/download.rs
Normal file
61
src/bin/download.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use dotenv::dotenv;
|
||||
use reqwest::{header, Client};
|
||||
use std::{env, fs::OpenOptions, io::Write, process};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let day: u8 = match aoc::parse_args() {
|
||||
Ok(day) => day,
|
||||
Err(_) => {
|
||||
eprintln!("Need to specify a day (as integer). example: `cargo download 7`");
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
dotenv().ok();
|
||||
|
||||
let day_padded = format!("{day:02}");
|
||||
let token = env::var("TOKEN").expect("$TOKEN is not set");
|
||||
let year = env::var("YEAR")
|
||||
.expect("$YEAR is not set")
|
||||
.parse::<u32>()
|
||||
.expect("$YEAR must be a number");
|
||||
|
||||
let mut headers = header::HeaderMap::new();
|
||||
let mut session_header = header::HeaderValue::from_str(format!("session={token}").as_str())
|
||||
.expect("Error building cookie header");
|
||||
session_header.set_sensitive(true);
|
||||
headers.insert(header::COOKIE, session_header);
|
||||
|
||||
let client = Client::builder().default_headers(headers).build().unwrap();
|
||||
let res = client
|
||||
.get(format!("https://adventofcode.com/{year}/day/{day}/input"))
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.text()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let input_path = format!("src/inputs/{day_padded}.txt");
|
||||
let mut file = match OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&input_path)
|
||||
{
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to create module file: {e}");
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
match file.write_all(res.as_bytes()) {
|
||||
Ok(_) => {
|
||||
println!("Downloaded input file \"{}\"", &input_path);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to write module contents: {e}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
src/bin/download_all.rs
Normal file
57
src/bin/download_all.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use dotenv::dotenv;
|
||||
use itertools::Itertools;
|
||||
use reqwest::{header, Client};
|
||||
use std::{env, fs::OpenOptions, io::Write, process};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenv().ok();
|
||||
let token = env::var("TOKEN").expect("$TOKEN is not set");
|
||||
let year = env::var("YEAR")
|
||||
.expect("$YEAR is not set")
|
||||
.parse::<u32>()
|
||||
.expect("$YEAR must be a number");
|
||||
|
||||
let mut headers = header::HeaderMap::new();
|
||||
let mut session_header = header::HeaderValue::from_str(format!("session={token}").as_str())
|
||||
.expect("Error building cookie header");
|
||||
session_header.set_sensitive(true);
|
||||
headers.insert(header::COOKIE, session_header);
|
||||
|
||||
let client = Client::builder().default_headers(headers).build().unwrap();
|
||||
let responses = (1..=25)
|
||||
.map(|d| {
|
||||
let endpoint = format!("https://adventofcode.com/{year}/day/{d}/input");
|
||||
println!("{endpoint}");
|
||||
client.get(endpoint).send()
|
||||
})
|
||||
.collect_vec()
|
||||
.into_iter()
|
||||
.map(|x| async { x.await.unwrap().text().await.unwrap() })
|
||||
.collect_vec();
|
||||
|
||||
for (day, res) in (1..=25).zip(responses) {
|
||||
let input_path = format!("src/inputs/{day:02}.txt");
|
||||
let mut file = match OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&input_path)
|
||||
{
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to create module file: {e}");
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
match file.write_all(res.await.as_bytes()) {
|
||||
Ok(_) => {
|
||||
println!("Downloaded input file \"{}\"", &input_path);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to write module contents: {e}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
104
src/bin/scaffold.rs
Normal file
104
src/bin/scaffold.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* This file contains template code.
|
||||
* There is no need to edit this file unless you want to change template functionality.
|
||||
*/
|
||||
use std::{
|
||||
fs::{File, OpenOptions},
|
||||
io::Write,
|
||||
process,
|
||||
};
|
||||
|
||||
const MODULE_TEMPLATE: &str = r###"pub fn part_one(input: &str) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
pub fn part_two(input: &str) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
fn main() {
|
||||
let input = &aoc::read_file("inputs", DAY);
|
||||
aoc::solve!(1, part_one, input);
|
||||
aoc::solve!(2, part_two, input);
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_part_one() {
|
||||
let input = aoc::read_file("examples", DAY).trim();
|
||||
assert_eq!(part_one(&input), None);
|
||||
}
|
||||
#[test]
|
||||
fn test_part_two() {
|
||||
let input = aoc::read_file("examples", DAY).trim();
|
||||
assert_eq!(part_two(&input), None);
|
||||
}
|
||||
}
|
||||
"###;
|
||||
|
||||
fn safe_create_file(path: &str) -> Result<File, std::io::Error> {
|
||||
OpenOptions::new().write(true).create_new(true).open(path)
|
||||
}
|
||||
|
||||
fn create_file(path: &str) -> Result<File, std::io::Error> {
|
||||
OpenOptions::new().write(true).create(true).open(path)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let day = match aoc::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!("{day:02}");
|
||||
|
||||
let input_path = format!("src/inputs/{day_padded}.txt");
|
||||
let example_path = format!("src/examples/{day_padded}.txt");
|
||||
let module_path = format!("src/bin/{day_padded}.rs");
|
||||
|
||||
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.to_string()).as_bytes()) {
|
||||
Ok(_) => {
|
||||
println!("Created module file \"{}\"", &module_path);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to write module contents: {e}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
match 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 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 solve {}` to run your solution.",
|
||||
&day_padded
|
||||
);
|
||||
}
|
||||
0
src/examples/.keep
Normal file
0
src/examples/.keep
Normal file
0
src/inputs/.keep
Normal file
0
src/inputs/.keep
Normal file
127
src/lib.rs
Normal file
127
src/lib.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* This file contains template code.
|
||||
* There is no need to edit this file unless you want to change template functionality.
|
||||
* Prefer `./helpers.rs` if you want to extract code from your solutions.
|
||||
*/
|
||||
use std::env;
|
||||
use std::fs;
|
||||
|
||||
pub const ANSI_ITALIC: &str = "\x1b[3m";
|
||||
pub const ANSI_BOLD: &str = "\x1b[1m";
|
||||
pub const ANSI_RESET: &str = "\x1b[0m";
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! solve {
|
||||
($part:expr, $solver:ident, $input:expr) => {{
|
||||
use aoc::{ANSI_BOLD, ANSI_ITALIC, ANSI_RESET};
|
||||
use std::fmt::Display;
|
||||
use std::time::Instant;
|
||||
|
||||
fn print_result<T: Display>(func: impl FnOnce(&str) -> Option<T>, input: &str) {
|
||||
let timer = Instant::now();
|
||||
let result = func(input);
|
||||
let elapsed = timer.elapsed();
|
||||
match result {
|
||||
Some(result) => {
|
||||
println!(
|
||||
"{} {}(elapsed: {:.2?}){}",
|
||||
result, ANSI_ITALIC, elapsed, ANSI_RESET
|
||||
);
|
||||
}
|
||||
None => {
|
||||
println!("not solved.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("🎄 {}Part {}{} 🎄", ANSI_BOLD, $part, ANSI_RESET);
|
||||
print_result($solver, $input);
|
||||
}};
|
||||
}
|
||||
|
||||
pub fn read_file(folder: &str, day: u8) -> String {
|
||||
let cwd = env::current_dir().unwrap();
|
||||
|
||||
let filepath = cwd.join("src").join(folder).join(format!("{day:02}.txt"));
|
||||
|
||||
let f = fs::read_to_string(filepath);
|
||||
f.expect("could not open input file")
|
||||
}
|
||||
|
||||
fn parse_time(val: &str, postfix: &str) -> f64 {
|
||||
val.split(postfix).next().unwrap().parse().unwrap()
|
||||
}
|
||||
|
||||
pub fn parse_args() -> Result<u8, pico_args::Error> {
|
||||
let mut args = pico_args::Arguments::from_env();
|
||||
args.free_from_str()
|
||||
}
|
||||
|
||||
pub fn parse_exec_time(output: &str) -> f64 {
|
||||
output.lines().fold(0_f64, |acc, l| {
|
||||
if !l.contains("elapsed:") {
|
||||
acc
|
||||
} else {
|
||||
let timing = l.split("(elapsed: ").last().unwrap();
|
||||
// use `contains` istd. of `ends_with`: string may contain ANSI escape sequences.
|
||||
// for possible time formats, see: https://github.com/rust-lang/rust/blob/1.64.0/library/core/src/time.rs#L1176-L1200
|
||||
if timing.contains("ns)") {
|
||||
acc // range below rounding precision.
|
||||
} else if timing.contains("µs)") {
|
||||
acc + parse_time(timing, "µs") / 1000_f64
|
||||
} else if timing.contains("ms)") {
|
||||
acc + parse_time(timing, "ms")
|
||||
} else if timing.contains("s)") {
|
||||
acc + parse_time(timing, "s") * 1000_f64
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// copied from: https://github.com/rust-lang/rust/blob/1.64.0/library/std/src/macros.rs#L328-L333
|
||||
#[cfg(test)]
|
||||
macro_rules! assert_approx_eq {
|
||||
($a:expr, $b:expr) => {{
|
||||
let (a, b) = (&$a, &$b);
|
||||
assert!(
|
||||
(*a - *b).abs() < 1.0e-6,
|
||||
"{} is not approximately equal to {}",
|
||||
*a,
|
||||
*b
|
||||
);
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_exec_time() {
|
||||
assert_approx_eq!(
|
||||
parse_exec_time(&format!(
|
||||
"🎄 Part 1 🎄\n0 (elapsed: 74.13ns){ANSI_RESET}\n🎄 Part 2 🎄\n0 (elapsed: 50.00ns){ANSI_RESET}"
|
||||
)),
|
||||
0_f64
|
||||
);
|
||||
|
||||
assert_approx_eq!(
|
||||
parse_exec_time("🎄 Part 1 🎄\n0 (elapsed: 755µs)\n🎄 Part 2 🎄\n0 (elapsed: 700µs)"),
|
||||
1.455_f64
|
||||
);
|
||||
|
||||
assert_approx_eq!(
|
||||
parse_exec_time("🎄 Part 1 🎄\n0 (elapsed: 70µs)\n🎄 Part 2 🎄\n0 (elapsed: 1.45ms)"),
|
||||
1.52_f64
|
||||
);
|
||||
|
||||
assert_approx_eq!(
|
||||
parse_exec_time(
|
||||
"🎄 Part 1 🎄\n0 (elapsed: 10.3s)\n🎄 Part 2 🎄\n0 (elapsed: 100.50ms)"
|
||||
),
|
||||
10400.50_f64
|
||||
);
|
||||
}
|
||||
}
|
||||
43
src/main.rs
Normal file
43
src/main.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file contains template code.
|
||||
* There is no need to edit this file unless you want to change template functionality.
|
||||
*/
|
||||
use aoc::{ANSI_BOLD, ANSI_ITALIC, ANSI_RESET};
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let total: f64 = (1..=25)
|
||||
.map(|day| {
|
||||
let day = format!("{day:02}");
|
||||
|
||||
let cmd = Command::new("cargo")
|
||||
.args(["run", "--release", "--bin", &day])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
println!("----------");
|
||||
println!("{ANSI_BOLD}| Day {day} |{ANSI_RESET}");
|
||||
println!("----------");
|
||||
|
||||
let output = String::from_utf8(cmd.stdout).unwrap();
|
||||
let is_empty = output.is_empty();
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
if is_empty {
|
||||
"Not solved."
|
||||
} else {
|
||||
output.trim()
|
||||
}
|
||||
);
|
||||
|
||||
if is_empty {
|
||||
0_f64
|
||||
} else {
|
||||
aoc::parse_exec_time(&output)
|
||||
}
|
||||
})
|
||||
.sum();
|
||||
|
||||
println!("{ANSI_BOLD}Total:{ANSI_RESET} {ANSI_ITALIC}{total:.2}ms{ANSI_RESET}");
|
||||
}
|
||||
Reference in New Issue
Block a user