#![doc = env!("CARGO_PKG_DESCRIPTION")]
#![doc = ""]
use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
use rsticle::{SLASH, convert_str};
use std::{borrow::Cow, path::PathBuf};
struct Error {
msg: Cow<'static, str>,
span: Span,
}
impl Error {
fn new(msg: impl Into<Cow<'static, str>>, span: Span) -> Self {
Self {
msg: msg.into(),
span,
}
}
}
#[proc_macro]
pub fn include_as_doc(args: TokenStream) -> TokenStream {
macro_main(args).unwrap_or_else(compile_error)
}
fn macro_main(args: TokenStream) -> Result<TokenStream, Error> {
let mut input = args.into_iter();
let Some(literal) = input.next() else {
return Err(Error::new(
"expected filename, found empty parameter list",
Span::call_site(),
));
};
let arg_span = literal.span();
let TokenTree::Literal(literal) = literal else {
return Err(Error::new(
format!("expected literal, found \"{literal}\""),
arg_span,
));
};
let arg_literal = literal.to_string();
let Some(path) = arg_literal
.strip_prefix('"')
.and_then(|it| it.strip_suffix('"'))
else {
return Err(Error::new(
format!("Expected path to file, got {arg_literal}"),
arg_span,
));
};
let path = PathBuf::from(path);
if path.extension().is_none_or(|it| it != "rs") {
return Err(Error::new("Path must point to an .rs file", arg_span));
}
let path = if path.is_absolute() {
path
} else {
let manifest_dir = std::env::var_os("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.ok_or_else(|| {
Error::new(
"the CARGO_MANIFEST_DIR environment variable is not set",
Span::call_site(),
)
})?;
manifest_dir.join(path)
};
let source = std::fs::read_to_string(&path)
.map_err(|e| Error::new(format!("Could not read file {path:?}: {e}"), arg_span))?;
let markdown = convert_str(&SLASH, &source)
.map_err(|e| Error::new(format!("Conversion failed: {e}"), arg_span))?;
let markdown = proc_macro::Literal::string(&markdown);
Ok(TokenTree::Literal(markdown).into())
}
fn compile_error(error: Error) -> TokenStream {
let compile_error: TokenTree = Ident::new("compile_error", error.span).into();
let exclaim: TokenTree = Punct::new('!', Spacing::Joint).into();
let msg: TokenTree = Literal::string(&error.msg).into();
let parens: TokenTree =
Group::new(Delimiter::Parenthesis, TokenStream::from_iter([msg])).into();
TokenStream::from_iter([compile_error, exclaim, parens])
}