adventofcode/macros/src/lib.rs

167 Zeilen
4,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;
2024-12-03 09:18:18 +01:00
use quote::{format_ident, quote};
use syn::{parse, Result};
2024-12-01 16:30:48 +01:00
use syn::{
parse::{Parse, ParseStream},
2024-12-03 09:18:18 +01:00
spanned::Spanned,
Error, Ident, Lit, LitInt,
2024-12-01 16:30:48 +01:00
};
2024-12-02 21:18:42 +01:00
const DATA_DIR: &str = "data";
const EXAMPLE_DIR: &str = "examples";
2024-12-03 09:18:18 +01:00
const VAR_NAME: &str = "DATA";
2024-12-02 21:18:42 +01:00
#[derive(Debug, Clone)]
2024-12-01 16:30:48 +01:00
struct IncludeData {
var: String,
2024-12-03 09:18:18 +01:00
year: Option<String>,
day: Option<String>,
2024-12-01 16:30:48 +01:00
}
2024-12-03 09:18:18 +01:00
impl Default for IncludeData {
fn default() -> Self {
IncludeData {
var: VAR_NAME.into(),
year: None,
day: None,
}
}
}
fn get_text<T: Spanned>(item: T) -> Result<String> {
let span = item.span();
match span.source_text() {
Some(text) => Ok(text),
None => {
let start = span.start();
let end = span.end();
2024-12-03 09:18:18 +01:00
Err(Error::new(
span,
format!(
"Failed to get sourcetext for {}:{}-{}:{}",
start.line, start.column, end.line, end.column,
),
2024-12-03 09:18:18 +01:00
))
}
2024-12-03 09:18:18 +01:00
}
}
2024-12-01 16:30:48 +01:00
impl Parse for IncludeData {
fn parse(input: ParseStream) -> Result<Self> {
2024-12-03 09:18:18 +01:00
let mut data = IncludeData::default();
if input.peek(Ident) && !input.peek(LitInt) {
data.var = get_text(input.parse::<Ident>().unwrap())?;
2024-12-01 16:30:48 +01:00
}
2024-12-03 09:18:18 +01:00
data.year = Some(get_text(input.parse::<LitInt>()?)?);
data.day = Some(get_text(input.parse::<Lit>()?)?);
2024-12-01 16:30:48 +01:00
Ok(data)
}
}
2024-12-03 09:18:18 +01:00
fn canonicalize(dir: &str, input: IncludeData) -> Option<String> {
let pathname = format!("{}/{}/{}.txt", dir, input.year.unwrap(), input.day.unwrap());
match fs::canonicalize(pathname) {
Ok(canon) => {
if canon.is_file() {
match canon.to_str() {
Some(c) => Some(c.to_string()),
None => None,
2024-12-02 21:18:42 +01:00
}
2024-12-03 09:18:18 +01:00
} else {
2024-12-02 21:18:42 +01:00
None
}
}
2024-12-03 09:18:18 +01:00
Err(_) => None,
2024-12-02 21:18:42 +01:00
}
}
2024-12-01 16:30:48 +01:00
/// includes Data from Advent of code
/// ```ignore (cannot-doctest-external-file-dependency)
/// include_data!(DATA 2015 01)
2024-12-03 09:18:18 +01:00
/// // or
/// 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-03 09:18:18 +01:00
let input = match parse::<IncludeData>(data) {
Ok(include) => include,
Err(error) => return error.into_compile_error().into(),
};
2024-12-03 09:18:18 +01:00
let ident = format_ident!("{}", input.var);
let mut comment: String;
if input.day.is_some() && input.year.is_some() {
comment = "Data from the data dir".into();
let mut path = canonicalize(DATA_DIR, input.clone());
if path.is_none() {
comment = "Data from the examples".into();
path = canonicalize(EXAMPLE_DIR, input)
2024-12-02 21:18:42 +01:00
}
2024-12-03 09:18:18 +01:00
match path {
Some(p) => {
return quote! {
#[doc = #comment]
const #ident: &str = include_str!(#p).trim_ascii();
2024-12-03 09:18:18 +01:00
}
.into()
}
None => comment = "failed to get data from the paths".into(),
2024-12-02 21:18:42 +01:00
}
2024-12-03 09:18:18 +01:00
} else {
comment = format!(
"Failed to get the year({:?}) or day({:?}) falling back to default",
2024-12-03 09:18:18 +01:00
input.year, input.day
);
}
quote! {
#[doc = #comment]
const #ident: &str = "";
2024-12-02 21:18:42 +01:00
}
2024-12-03 09:18:18 +01:00
.into()
2024-12-01 16:30:48 +01:00
}
/// Same as include_data!, but it only gets data from the example directory.
/// This is useful for testing the examples, even when Data from the AOC is available
#[proc_macro]
2024-12-02 21:18:42 +01:00
pub fn include_example(data: TokenStream) -> TokenStream {
let input = match parse::<IncludeData>(data) {
Ok(include) => include,
Err(error) => return error.into_compile_error().into(),
};
2024-12-03 09:18:18 +01:00
let ident = format_ident!("{}", input.var);
let mut comment: String;
2024-12-03 09:18:18 +01:00
if input.day.is_some() && input.year.is_some() {
comment = "Data from the Example dir".into();
2024-12-03 09:18:18 +01:00
match canonicalize(EXAMPLE_DIR, input.clone()) {
Some(p) => {
return quote! {
#[doc = #comment]
const #ident: &str = include_str!(#p).trim_ascii();
2024-12-03 09:18:18 +01:00
}
.into()
}
None => comment = "failed to get data from the path".into(),
2024-12-02 21:18:42 +01:00
}
} else {
comment = format!(
"Failed to get the year({:?}) or day({:?}) falling back to default",
input.year, input.day
);
2024-12-02 21:18:42 +01:00
}
2024-12-03 09:18:18 +01:00
quote! {
#[doc = #comment]
const #ident: &str = "";
2024-12-03 09:18:18 +01:00
}
.into()
2024-12-02 21:18:42 +01:00
}