diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 9194f08..ffc1d31 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -8,5 +8,8 @@ proc-macro = true [dependencies] quote = "1.0.*" -syn = "2.0.*" -proc-macro2 = {version = "*", features = ["span-locations"]} \ No newline at end of file +proc-macro2 = {version = "*", features = ["span-locations"]} + +[dependencies.syn] +version = ">=2.0.90" +features = ["full"] \ No newline at end of file diff --git a/macros/src/lib.rs b/macros/src/lib.rs index b391bbd..54f98ba 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,113 +1,107 @@ use std::fs; use proc_macro::TokenStream; -use quote::quote; +use quote::{format_ident, quote}; use syn::{ buffer::Cursor, parse::{Parse, ParseStream, StepCursor}, - Error, LitStr, + spanned::Spanned, + Error, Ident, Lit, LitInt, }; -use syn::{parse, LitInt, Result}; +use syn::{parse, Result}; const DATA_DIR: &str = "data"; const EXAMPLE_DIR: &str = "examples"; +const VAR_NAME: &str = "DATA"; #[derive(Debug, Clone)] struct IncludeData { var: String, - year: String, - day: String, + year: Option, + day: Option, } -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, +impl Default for IncludeData { + fn default() -> Self { + IncludeData { + var: VAR_NAME.into(), + year: None, + day: None, + } + } +} + +fn get_text(item: T) -> Result { + let span = item.span(); + match span.source_text() { + Some(text) => Ok(text), None => { let start = span.start(); let end = span.end(); - return Err(Error::new( + Err(Error::new( span, format!( "Failed to get sourcetext for {}:{}-{}:{}", start.line, start.column, end.line, end.column, ), - )); + )) } + } +} + +fn get_literal<'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 text = get_text(lit)?; + Ok((text, c)) +} + +fn get_ident<'a, 'b>(cursor: StepCursor<'a, 'b>) -> Result<(String, Cursor<'a>)> { + let (lit, c) = match cursor.ident() { + Some(literal) => literal, + None => return Err(Error::new(cursor.span(), "Failed to get literal")), + }; + let text = get_text(lit)?; Ok((text, c)) } impl Parse for IncludeData { fn parse(input: ParseStream) -> Result { - let mut data = IncludeData { - var: "".into(), - year: "".into(), - day: "".into(), - }; - let mut look = input.lookahead1(); - if !look.peek(LitStr) { - let s = input.span(); - return Err(Error::new( - s, - format!("{} is not an valid Token", s.source_text().unwrap()), - )); + let mut data = IncludeData::default(); + if input.peek(Ident) && !input.peek(LitInt) { + data.var = get_text(input.parse::().unwrap())?; } - data.var = input.step(get_string).expect("Wanted a string"); - 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"); + data.year = Some(get_text(input.parse::()?)?); + data.day = Some(get_text(input.parse::()?)?); + 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 +fn canonicalize(dir: &str, input: IncludeData) -> Option { + 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, } - } - Err(err) => { - println!("ERROR: {} does not exist: {}", p, err); + } else { None } } - } else { - None + Err(_) => None, } } /// includes Data from Advent of code /// ```ignore (cannot-doctest-external-file-dependency) /// include_data!(DATA 2015 01) +/// // or +/// include_data!(2015 01) /// /// fn main() { /// print!("{DATA}"); @@ -119,37 +113,59 @@ fn canonicalize(dir: &str, input: Result) -> Option { /// ``` #[proc_macro] pub fn include_data(data: TokenStream) -> TokenStream { - let input = parse::(data); - let name = match input.clone() { - Ok(d) => d.var, - Err(_)=>"DATA".into(), + let input = match parse::(data) { + Ok(include) => include, + Err(error) => return error.into_compile_error().into(), }; - let mut path = canonicalize(DATA_DIR, input.clone()); - if path.is_none() { - path = canonicalize(EXAMPLE_DIR, input) - } - match path { - Some(p) => quote! { - static #name: &str = include_str!(#p); + 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) } - .into(), - None => quote! { - static #name: &str = ""; + match path { + Some(p) => { + return quote! { + #[doc = #comment] + static #ident: &str = include_str!(#p); + } + .into() + } + None => comment = "failed to get data from the paths".into(), } - .into(), + } else { + comment = format!( + "Failed to get the year({:?}) or day({:?})", + input.year, input.day + ); } + quote! { + #[doc = #comment] + static #ident: &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); + let input = parse::(data).unwrap_or_default(); + let ident = format_ident!("{}", input.var); + if input.day.is_some() && input.year.is_some() { + match canonicalize(EXAMPLE_DIR, input.clone()) { + Some(p) => { + return quote! { + static #ident: &str = include_str!(#p); + } + .into() + } + None => {} } - .into(), - None => quote! { - static DATA: &str = ""; - } - .into(), } + quote! { + static #ident: &str = ""; + } + .into() }