#![doc = include_str!("../README.md")]
#![warn(rust_2018_idioms)]
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{
self,
parse::{Parse, ParseStream},
parse_macro_input, AttrStyle, Attribute, Error, Lit, LitStr, Meta, MetaNameValue, Result,
};
use std::fs;
mod save_http_png;
use crypto::digest::Digest;
use crypto::sha1::Sha1;
#[derive(Clone)]
enum MaybeDocAttr {
Doc(Attribute, MetaNameValue),
Other(Attribute),
}
impl MaybeDocAttr {
fn from_attribute(attr: Attribute) -> Result<Self> {
if attr.path.is_ident("doc") {
let meta = attr.parse_meta()?;
if let Meta::NameValue(nv) = meta {
if let Lit::Str(_) = nv.lit {
Ok(MaybeDocAttr::Doc(attr, nv))
} else {
Err(Error::new(nv.lit.span(), "doc comment must be a string"))
}
} else {
Ok(MaybeDocAttr::Other(attr))
}
} else {
Ok(MaybeDocAttr::Other(attr))
}
}
}
impl ToTokens for MaybeDocAttr {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
MaybeDocAttr::Doc(attr, nv) => {
attr.pound_token.to_tokens(tokens);
if let AttrStyle::Inner(ref b) = attr.style {
b.to_tokens(tokens);
}
attr.bracket_token.surround(tokens, |tokens| {
nv.to_tokens(tokens);
});
}
MaybeDocAttr::Other(attr) => attr.to_tokens(tokens),
}
}
}
impl Into<Attribute> for MaybeDocAttr {
fn into(self) -> Attribute {
match self {
MaybeDocAttr::Doc(mut attr, nv) => {
let lit = nv.lit;
attr.tokens = quote! { = #lit };
attr
}
MaybeDocAttr::Other(attr) => attr,
}
}
}
enum StrOrDocAttrs {
Str(LitStr),
Attrs(Vec<syn::Attribute>),
}
impl Parse for StrOrDocAttrs {
fn parse(input: ParseStream<'_>) -> Result<Self> {
if let Ok(lit_str) = input.parse() {
Ok(Self::Str(lit_str))
} else {
let mut attrs = Attribute::parse_inner(input)?;
attrs.extend(Attribute::parse_outer(input)?);
Ok(Self::Attrs(attrs))
}
}
}
#[proc_macro]
pub fn image(img:proc_macro::TokenStream) -> proc_macro::TokenStream {
let img = img.to_string().replace(" / ", "/");
let path_s = "./target/doc/images/".to_string() + &img;
let path = std::path::Path::new(path_s.as_str());
let prefix = path.parent().unwrap();
std::fs::create_dir_all(prefix).unwrap();
if let Err( es ) = fs::copy(&img, path_s)
{
println!("{:?}", es);
}
handle_error(|| {
let output = format!("</p><img src=\"../../images/{}\"/>", img);
Ok(LitStr::new(&output, Span::call_site())
.into_token_stream()
.into())
})
}
#[proc_macro]
pub fn plantuml_file(img:proc_macro::TokenStream) -> proc_macro::TokenStream {
let img = img.to_string().replace(" / ", "/");
handle_error(|| {
let ret = fs::read_to_string(&img);
if let Ok(data) = ret{
let output = save_plantuml(&data);
Ok(LitStr::new(&output, Span::call_site())
.into_token_stream()
.into())
}
else{
Ok(LitStr::new("", Span::call_site())
.into_token_stream()
.into())
}
})
}
fn save_plantuml(uml_str:&str) -> String{
let mut output = String::new();
let mut hasher = Sha1::new();
hasher.input_str(¨_str);
let uml_file_name = &("./target/doc/images/puml_files/".to_string() + hasher.result_str().as_str() + ".png");
if true == save_http_png::download_puml(¨_str, uml_file_name)
{
output.push_str(&("</p><img src = \"".to_string() + "../../images/puml_files/" + hasher.result_str().as_str() + ".png" + "\" />\n"));
}
else
{
println!("warning: failed to download the planduml picture, use dynamic hyperlink instead");
}
output
}
#[proc_macro]
pub fn plantuml(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: StrOrDocAttrs = parse_macro_input!(tokens);
let (mut iter1, mut iter2);
let iter: &mut dyn Iterator<Item = Result<LitStr>> = match input {
StrOrDocAttrs::Str(s) => {
iter1 = std::iter::once(Ok(s));
&mut iter1
}
StrOrDocAttrs::Attrs(attrs) => {
iter2 = attrs
.into_iter()
.map(|attr| match MaybeDocAttr::from_attribute(attr)? {
MaybeDocAttr::Doc(
_,
syn::MetaNameValue {
lit: syn::Lit::Str(s),
..
},
) => Ok(s),
MaybeDocAttr::Doc(attr, _) | MaybeDocAttr::Other(attr) => {
Err(Error::new_spanned(
&attr,
"only `#[doc = ...]` attributes or a string literal are allowed here",
))
}
});
&mut iter2
}
};
handle_error(|| {
let mut uml_str = String::new();
for lit_str in iter {
let lit_str = lit_str?;
let st = lit_str.value();
uml_str.push_str((st + "\n").as_str());
}
let output = save_plantuml(¨_str);
Ok(LitStr::new(&output, Span::call_site())
.into_token_stream()
.into())
})
}
fn handle_error(cb: impl FnOnce() -> Result<proc_macro::TokenStream>) -> proc_macro::TokenStream {
match cb() {
Ok(tokens) => tokens,
Err(e) => e.to_compile_error().into(),
}
}