use std::fs; use proc_macro::TokenStream; use quote::quote; use syn::{ buffer::Cursor, parse::{Parse, ParseStream, StepCursor}, Error, }; use syn::{parse, LitInt, Result}; const DATA_DIR: &str = "data"; const EXAMPLE_DIR: &str = "examples"; #[derive(Debug, Clone)] 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)) } impl Parse for IncludeData { fn parse(input: ParseStream) -> Result { 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()), )); } 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()), )); } data.day = input.step(get_string).expect("Wanted an string"); Ok(data) } } fn canonicalize(dir: &str, input: Result) -> Option { 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 } } Err(err) => { println!("ERROR: {} does not exist: {}", p, err); None } } } else { None } } /// includes Data from Advent of code /// ```ignore (cannot-doctest-external-file-dependency) /// include_data!(2015 01) /// /// fn main() { /// print!("{DATA}"); /// } /// ``` /// The Equivalent is /// ```ignore (cannot-doctest-external-file-dependency) /// static DATA: &str = include_str!("../../../data/2015/01.txt"); /// ``` #[proc_macro] pub fn include_data(data: TokenStream) -> TokenStream { let input = parse::(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(), } } #[proc_macro] pub fn include_example(data: TokenStream) -> TokenStream { match canonicalize(EXAMPLE_DIR, parse::(data)) { Some(p) => quote! { static DATA: &str = include_str!(#p); } .into(), None => quote! { static DATA: &str = ""; } .into(), } }