1 unstable release
new 0.1.1 | Dec 1, 2024 |
---|---|
0.1.0 |
|
#547 in Rust patterns
215 downloads per month
25KB
308 lines
elvish
Overengineered Advent of Code framework for Rust - not quite Santa's elves.
elvish is a framework for writing your Advent of Code solutions with the least amount of boilerplate as possible.
Features
- Declare solutions with simple macro
- Fetching and caching of user input
- Run solutions as binary
- Automatically copy solutions to clipboard
- Simple and consice syntax to write out 90% of required tests
- See the puzzle description as docs on the annotated function
- Conditional compilation to compile a single day
Quick example
This is my solution for day 1 of 2023 using elvish:
struct Solutions;
#[elvish::solution(day = 1, example = 142)]
fn part1(input: &str) -> u32 {
input
.lines()
.filter(|line| !line.is_empty())
.map(|line| {
let mut iter = line.chars().filter_map(|c| c.to_digit(10));
let a = iter.next().unwrap();
let b = iter.last().unwrap_or(a);
a * 10 + b
})
.sum()
}
#[elvish::solution(day = 1, example = 281)]
fn part2(input: &str) -> u32 {
let parse_slice = |slice: &str| {
let digits = [
"one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
];
let digit = slice.chars().next().unwrap().to_digit(10);
let named = || {
digits
.iter()
.enumerate()
.find(|(_, &digit)| slice.starts_with(digit))
.map(|(i, _)| i as u32 + 1)
};
digit.or_else(named)
};
input
.lines()
.filter(|line| !line.is_empty())
.map(|line| {
let mut iter = (0..line.len()).filter_map(|i| parse_slice(&line[i..]));
let a = iter.next().unwrap();
let b = iter.last().unwrap_or(a);
a * 10 + b
})
.sum()
}
elvish::example!(
part1: "
1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet
",
part2: "
two1nine
eightwothree
abcone2threexyz
xtwone3four
4nineeightseven2
zoneight234
7pqrstsixteen
"
);
Usage
Note: The examples directory has a full implementation of the 2023 advent of code using elvish, for reference.
Installing
Clone the template repo:
git clone https://github.com/odilf/elvish-template
If you want to use a remote, change the url:
git remote set-url origin your-repo-url
Then, edit the .env.example
by setting your session token and the year, and make it the primary .env
file.
mv .env.example .env
detailed instructions
To use elvish, first add it as a dependency
cargo add elvish
Then, declare a Solutions
struct in the root of the crate:
// in main.rs
struct Solutions;
And add a main function:
// in main.rs
struct Solutions;
elvish::declare::run_fn!();
fn main() -> eyre::Result<()> {
tracing_subscriber::fmt().init();
dotenvy::dotenv()?;
elvish::run::<2023>(&elvish::available_days!(), run_day_part)?;
Ok(())
}
For the data fetching to work you need to add your session token and year in a .env
file (make sure to .gitignore
it).
SESSION_TOKEN=something
YEAR=202X
Finally, you need to add some cargo features to conditionally compile each day:
[features]
# You can add your own features here
# Detected by `elvish`
today = []
generate-docs = []
part1 = []
part2 = []
both = ["part1", "part2"]
1 = ["part1"]
2 = ["part2"]
b = ["both"]
day01 = []
day02 = []
day03 = []
day04 = []
day05 = []
day06 = []
day07 = []
day08 = []
day09 = []
day10 = []
day11 = []
day12 = []
day13 = []
day14 = []
day15 = []
day16 = []
day17 = []
day18 = []
day19 = []
day20 = []
day21 = []
day22 = []
day23 = []
day24 = []
day25 = []
d01 = ["day01"]
d02 = ["day02"]
d03 = ["day03"]
d04 = ["day04"]
d05 = ["day05"]
d06 = ["day06"]
d07 = ["day07"]
d08 = ["day08"]
d09 = ["day09"]
d10 = ["day10"]
d11 = ["day11"]
d12 = ["day12"]
d13 = ["day13"]
d14 = ["day14"]
d15 = ["day15"]
d16 = ["day16"]
d17 = ["day17"]
d18 = ["day18"]
d19 = ["day19"]
d20 = ["day20"]
d21 = ["day21"]
d22 = ["day22"]
d23 = ["day23"]
d24 = ["day24"]
d25 = ["day25"]
all = [
"day01",
"day02",
"day03",
"day04",
"day05",
"day06",
"day07",
"day08",
"day09",
"day10",
"day11",
"day12",
"day13",
"day14",
"day15",
"day16",
"day17",
"day18",
"day19",
"day20",
"day21",
"day22",
"day23",
"day24",
"day25",
]
Running
By default it compiles a binary that includes all days and runs it. To specify a day, you can use feature flags:
cargo run --no-default-features --features "day01 part1" # or part2
or, to run both parts,
cargo run --no-default-features --features "day01 both"
and, as a shorthand
cargo run --no-default-features --features "d01 b" # p1/p2 for part1/2
Test examples from prompts
elvish provides convinient macros to declare example inputs for 90% of cases:
- One example per part
#[elvish::solution(day = 1, example = 142)]
fn part1(input: &str) -> i32 {
// --snip--
}
#[elvish::solution(day = 1, example = 281)]
fn part2(input: &str) -> i32 {
// --snip--
}
elvish::example!(
part1: "
1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet
",
part2: "
1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet
",
);
- Same example for both parts
elvish::example!("
1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet
");
- More than one example for a part
elvish::example!(
part1: "
1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet
",
part1: "
1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet
",
part2: "
1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet
",
);
Other
Future roadmap
- Warn when day shouldn't be available yet
Dependencies
~14–28MB
~439K SLoC