generated from janezicmatej/aoc-template
feat: create repo from template
This commit is contained in:
commit
000000007a
|
@ -0,0 +1,4 @@
|
||||||
|
[alias]
|
||||||
|
scaffold = "run -p scaffold --release --quiet --"
|
||||||
|
download = "run -p download --release --quiet --"
|
||||||
|
solve = "run --bin"
|
|
@ -0,0 +1,2 @@
|
||||||
|
TOKEN=secret
|
||||||
|
YEAR=2024
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
debug/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# aoc-template
|
||||||
|
# env vars
|
||||||
|
.env
|
||||||
|
|
||||||
|
# downloaded inputs
|
||||||
|
/data/inputs/*
|
||||||
|
!/data/inputs/.keep
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,24 @@
|
||||||
|
[workspace.package]
|
||||||
|
description = "template for advent of code"
|
||||||
|
readme = "README.md"
|
||||||
|
version = "48.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT"
|
||||||
|
authors = ["Matej Janežič <janezic.mj@gmail.com>"]
|
||||||
|
repository = "https://git.janezic.dev/janezicmatej/aoc-template.git"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["utils/download", "utils/scaffold"]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "aoc"
|
||||||
|
description.workspace = true
|
||||||
|
readme.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Matej Janežič
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Advent-of-Code 2024
|
||||||
|
*This is a dumbed down version of [fspoettel/advent-of-code-rust](https://github.com/fspoettel/advent-of-code-rust) with some extra features*
|
||||||
|
|
||||||
|
## Project overview
|
||||||
|
|
||||||
|
### Project structure
|
||||||
|
- `data/` :
|
||||||
|
- `examples/`: example files go here; you can push this as test are run in ci
|
||||||
|
- `inputs/`: this directory is gitignored, input files go here
|
||||||
|
- `src/` :
|
||||||
|
- `bin/`:
|
||||||
|
- `<day>.rs`: solution files
|
||||||
|
- `lib.rs`: library entrypoint, reusable code goes here
|
||||||
|
- `template.rs`: contains template code
|
||||||
|
- `utils/`: binary packages with convenience scripts structured using cargo workspaces
|
||||||
|
- `.env.example`: example dotenv file
|
||||||
|
|
||||||
|
### Cli
|
||||||
|
- `cargo scaffold <day>`: prepare solution files for `day`
|
||||||
|
- `cargo download <day>`: download input file for `day`
|
||||||
|
- `cargo solve <day>`: run solution against input for `day`
|
||||||
|
|
||||||
|
*Run `cargo build --workspace --release` once so scaffold and download packages get compiled, otherwise they will have to be compiled on first run.*
|
||||||
|
|
||||||
|
### dotenv
|
||||||
|
|
||||||
|
set `TOKEN` to AoC session Cookie
|
||||||
|
|
||||||
|
### FAQ
|
||||||
|
|
||||||
|
#### How are your commits numbered in ascending order?
|
||||||
|
[https://westling.dev/b/extremely-linear-git](https://westling.dev/b/extremely-linear-git)
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod template;
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* This file contains template code.
|
||||||
|
* There is no need to edit this file unless you want to change template functionality.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
fn time_solution<T>(func: impl FnOnce(&str) -> Option<T>, input: &str) -> Option<(T, Duration)> {
|
||||||
|
let timer = Instant::now();
|
||||||
|
let result = func(input);
|
||||||
|
let elapsed = timer.elapsed();
|
||||||
|
|
||||||
|
result.map(|result| (result, elapsed))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_result<T: Display>(func: impl FnOnce(&str) -> Option<T>, input: &str, part: u8) {
|
||||||
|
match time_solution(func, input) {
|
||||||
|
Some((result, elapsed)) => {
|
||||||
|
println!(
|
||||||
|
"{}Part {}{}: {} {}(elapsed: {:.2?}){}",
|
||||||
|
ANSI_BOLD, part, ANSI_RESET, result, ANSI_ITALIC, elapsed, ANSI_RESET
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!("{}Part {}{}: not solved.", ANSI_BOLD, part, ANSI_RESET)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! solution {
|
||||||
|
($day:expr) => {
|
||||||
|
fn main() {
|
||||||
|
let input = aoc::template::read_file("inputs", $day);
|
||||||
|
aoc::template::print_result(part_one, &input, 1);
|
||||||
|
aoc::template::print_result(part_two, &input, 2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn read_file(folder: &str, day: u8) -> String {
|
||||||
|
let cwd = env::current_dir().unwrap();
|
||||||
|
let filepath = cwd.join("data").join(folder).join(format!("{day:02}.txt"));
|
||||||
|
let f = fs::read_to_string(filepath);
|
||||||
|
f.expect("could not open input file").trim().to_string()
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "download"
|
||||||
|
description.workspace = true
|
||||||
|
readme.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = { version = "0.11.22", features = ["blocking"] }
|
||||||
|
dotenvy = "0.15.6"
|
||||||
|
pico-args = "0.5.0"
|
|
@ -0,0 +1,64 @@
|
||||||
|
use dotenvy::dotenv;
|
||||||
|
use reqwest::blocking::Client;
|
||||||
|
use reqwest::header;
|
||||||
|
use std::{env, fs::OpenOptions, io::Write, process};
|
||||||
|
|
||||||
|
pub fn parse_args() -> Result<u8, pico_args::Error> {
|
||||||
|
let mut args = pico_args::Arguments::from_env();
|
||||||
|
args.free_from_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let day: u8 = match 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()
|
||||||
|
.unwrap()
|
||||||
|
.text()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let input_path = format!("data/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "scaffold"
|
||||||
|
description.workspace = true
|
||||||
|
readme.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = { version = "0.11.22", features = ["blocking"] }
|
||||||
|
dotenvy = "0.15.6"
|
||||||
|
pico-args = "0.5.0"
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
|
||||||
|
aoc::solution!(DAY);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_part_one() {
|
||||||
|
assert_eq!(part_one(&aoc::template::read_file("examples", DAY)), None);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_part_two() {
|
||||||
|
assert_eq!(part_two(&aoc::template::read_file("examples", DAY)), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
pub 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 create_file(path: &str) -> Result<File, std::io::Error> {
|
||||||
|
OpenOptions::new().write(true).create(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!("{day:02}");
|
||||||
|
|
||||||
|
let input_path = format!("data/inputs/{day_padded}.txt");
|
||||||
|
let example_path = format!("data/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);
|
||||||
|
}
|
Loading…
Reference in New Issue