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},
|
2024-12-02 21:50:56 +01:00
|
|
|
Error, LitStr,
|
2024-12-01 16:30:48 +01:00
|
|
|
};
|
|
|
|
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 {
|
2024-12-02 21:50:56 +01:00
|
|
|
var: String,
|
2024-12-01 16:30:48 +01:00
|
|
|
year: String,
|
|
|
|
day: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_string<'a, 'b>(cursor: StepCursor<'a, 'b>) -> Result<(String, Cursor<'a>)> {
|
2024-12-01 22:25:51 +01:00
|
|
|
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 {
|
2024-12-02 21:50:56 +01:00
|
|
|
var: "".into(),
|
2024-12-01 16:30:48 +01:00
|
|
|
year: "".into(),
|
|
|
|
day: "".into(),
|
|
|
|
};
|
|
|
|
let mut look = input.lookahead1();
|
2024-12-02 21:50:56 +01:00
|
|
|
if !look.peek(LitStr) {
|
|
|
|
let s = input.span();
|
|
|
|
return Err(Error::new(
|
|
|
|
s,
|
|
|
|
format!("{} is not an valid Token", s.source_text().unwrap()),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
data.var = input.step(get_string).expect("Wanted a string");
|
|
|
|
look = input.lookahead1();
|
2024-12-01 16:30:48 +01:00
|
|
|
if !look.peek(LitInt) {
|
|
|
|
let s = input.span();
|
2024-12-01 22:25:51 +01:00
|
|
|
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();
|
2024-12-01 22:25:51 +01:00
|
|
|
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-01 22:25:51 +01:00
|
|
|
}
|
2024-12-02 21:18:42 +01:00
|
|
|
Err(err) => {
|
|
|
|
println!("ERROR: {} does not exist: {}", p, err);
|
|
|
|
None
|
2024-12-01 22:25:51 +01:00
|
|
|
}
|
|
|
|
}
|
2024-12-02 21:18:42 +01:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2024-12-01 22:25:51 +01:00
|
|
|
}
|
|
|
|
|
2024-12-01 16:30:48 +01:00
|
|
|
/// includes Data from Advent of code
|
|
|
|
/// ```ignore (cannot-doctest-external-file-dependency)
|
2024-12-02 21:50:56 +01:00
|
|
|
/// include_data!(DATA 2015 01)
|
2024-12-01 22:25:51 +01:00
|
|
|
///
|
2024-12-01 16:30:48 +01:00
|
|
|
/// fn main() {
|
|
|
|
/// print!("{DATA}");
|
|
|
|
/// }
|
|
|
|
/// ```
|
2024-12-01 22:25:51 +01:00
|
|
|
/// 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]
|
2024-12-01 22:25:51 +01:00
|
|
|
pub fn include_data(data: TokenStream) -> TokenStream {
|
2024-12-02 21:18:42 +01:00
|
|
|
let input = parse::<IncludeData>(data);
|
2024-12-02 21:50:56 +01:00
|
|
|
let name = match input.clone() {
|
|
|
|
Ok(d) => d.var,
|
|
|
|
Err(_)=>"DATA".into(),
|
|
|
|
};
|
2024-12-02 21:18:42 +01:00
|
|
|
let mut path = canonicalize(DATA_DIR, input.clone());
|
|
|
|
if path.is_none() {
|
|
|
|
path = canonicalize(EXAMPLE_DIR, input)
|
|
|
|
}
|
|
|
|
match path {
|
|
|
|
Some(p) => quote! {
|
2024-12-02 21:50:56 +01:00
|
|
|
static #name: &str = include_str!(#p);
|
2024-12-02 21:18:42 +01:00
|
|
|
}
|
|
|
|
.into(),
|
|
|
|
None => quote! {
|
2024-12-02 21:50:56 +01:00
|
|
|
static #name: &str = "";
|
2024-12-02 21:18:42 +01:00
|
|
|
}
|
|
|
|
.into(),
|
|
|
|
}
|
2024-12-01 16:30:48 +01:00
|
|
|
}
|
2024-12-01 22:25:51 +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(),
|
|
|
|
}
|
|
|
|
}
|