adventofcode/macros/src/lib.rs

141 Zeilen
3,7 KiB
Rust

2024-12-02 21:18:42 +01:00
use std::fs;
2024-12-01 16:30:48 +01:00
use proc_macro::TokenStream;
use quote::quote;
use syn::{
buffer::Cursor,
parse::{Parse, ParseStream, StepCursor},
Error,
};
use syn::{parse, LitInt, Result};
2024-12-02 21:18:42 +01:00
const DATA_DIR: &str = "data";
const EXAMPLE_DIR: &str = "examples";
#[derive(Debug, Clone)]
2024-12-01 16:30:48 +01:00
struct IncludeData {
year: String,
day: String,
}
fn get_string<'a, 'b>(cursor: StepCursor<'a, 'b>) -> Result<(String, Cursor<'a>)> {
let (lit, c) = match cursor.literal() {
Some(literal) => literal,
None => return Err(Error::new(cursor.span(), "Failed to get literal")),
};
let span = lit.span();
let text = match span.source_text() {
Some(text) => text,
None => {
let start = span.start();
let end = span.end();
return Err(Error::new(
span,
format!(
"Failed to get sourcetext for {}:{}-{}:{}",
start.line, start.column, end.line, end.column,
),
));
}
};
Ok((text, c))
2024-12-01 16:30:48 +01:00
}
impl Parse for IncludeData {
fn parse(input: ParseStream) -> Result<Self> {
let mut data = IncludeData {
year: "".into(),
day: "".into(),
};
let mut look = input.lookahead1();
if !look.peek(LitInt) {
let s = input.span();
return Err(Error::new(
s,
format!("{} is not an valid Year", s.source_text().unwrap()),
));
2024-12-01 16:30:48 +01:00
}
data.year = input.step(get_string).expect("Wanted an string");
look = input.lookahead1();
if !look.peek(LitInt) {
let s = input.span();
return Err(Error::new(
s,
format!("{} is not an valid Day", s.source_text().unwrap()),
));
2024-12-01 16:30:48 +01:00
}
data.day = input.step(get_string).expect("Wanted an string");
Ok(data)
}
}
2024-12-02 21:18:42 +01:00
fn canonicalize(dir: &str, input: Result<IncludeData>) -> Option<String> {
let pathname = match input {
Ok(id) => Some(format!("{}/{}/{}.txt", dir, id.year, id.day)),
Err(_) => None,
};
if let Some(p) = pathname {
match fs::canonicalize(p.clone()) {
Ok(canon) => {
if canon.is_file() {
Some(canon.to_str().unwrap().to_string())
} else {
println!("ERROR: {:?} is not a file", canon);
None
}
}
2024-12-02 21:18:42 +01:00
Err(err) => {
println!("ERROR: {} does not exist: {}", p, err);
None
}
}
2024-12-02 21:18:42 +01:00
} else {
None
}
}
2024-12-01 16:30:48 +01:00
/// includes Data from Advent of code
/// ```ignore (cannot-doctest-external-file-dependency)
/// include_data!(2015 01)
///
2024-12-01 16:30:48 +01:00
/// fn main() {
/// print!("{DATA}");
/// }
/// ```
/// The Equivalent is
/// ```ignore (cannot-doctest-external-file-dependency)
/// static DATA: &str = include_str!("../../../data/2015/01.txt");
/// ```
2024-12-01 16:30:48 +01:00
#[proc_macro]
pub fn include_data(data: TokenStream) -> TokenStream {
2024-12-02 21:18:42 +01:00
let input = parse::<IncludeData>(data);
let mut path = canonicalize(DATA_DIR, input.clone());
if path.is_none() {
path = canonicalize(EXAMPLE_DIR, input)
}
match path {
Some(p) => quote! {
static DATA: &str = include_str!(#p);
}
.into(),
None => quote! {
static DATA: &str = "";
}
.into(),
}
2024-12-01 16:30:48 +01:00
}
#[proc_macro]
2024-12-02 21:18:42 +01:00
pub fn include_example(data: TokenStream) -> TokenStream {
match canonicalize(EXAMPLE_DIR, parse::<IncludeData>(data)) {
Some(p) => quote! {
static DATA: &str = include_str!(#p);
}
.into(),
None => quote! {
static DATA: &str = "";
}
.into(),
}
}