use proc_macro2::{
Delimiter::{Brace, Bracket},
Group, TokenStream as TS, TokenTree as TT,
};
use std::collections::HashSet;
use std::error::Error;
use std::fs::{read_dir, File};
use std::io::Read;
use std::path::{Path, PathBuf};
use syn::parse::{Parse, ParseStream as PS};
use syn::{fold::Fold, Ident, LitStr, Macro, Token};
static EMIT: &str = "accio_emit";
pub(crate) struct Args {
scopes: Vec<String>,
paths: Vec<String>,
}
impl Parse for Args {
fn parse(t: PS) -> syn::Result<Self> {
let mut scopes = vec![t.parse::<Ident>()?.to_string()];
while !t.is_empty() {
if t.parse::<Token![+]>().is_err() {
break;
}
scopes.push(t.parse::<Ident>()?.to_string());
}
let mut paths = vec![];
while !t.is_empty() {
t.parse::<Token![,]>()?;
paths.push(t.parse::<LitStr>()?.value());
}
paths.push("src/".to_string());
paths.push(".".to_string());
Ok(Self { scopes, paths })
}
}
pub(crate) fn fill_body<T>(t: T, ts: TS) -> TS
where
TS: From<T>,
{
fn repl(t: TS, ts: &mut Option<TS>) -> TS {
t.into_iter()
.map(|tt| {
if let TT::Group(g) = tt {
let mut m = g.stream();
let d = g.delimiter();
TT::Group(Group::new(
d,
if m.is_empty() && (d == Brace || d == Bracket) {
m.extend(ts.take());
m
} else {
repl(m, ts)
},
))
} else {
tt
}
})
.collect()
}
repl(TS::from(t), &mut Some(ts))
}
pub(crate) fn collect_all(args: Args) -> TS {
fn recurse(path: &PathBuf, scope: &str, s: &mut HashSet<String>, ts: &mut TS) {
if let Ok(path) = path.canonicalize() {
if let Some(ps) = path.to_str() {
if s.contains(ps) {
return;
}
s.insert(ps.to_string());
}
}
if path.is_dir() {
if let Ok(entries) = read_dir(path) {
for e in entries.flatten() {
recurse(&e.path(), scope, s, ts);
}
}
} else if is_rust_file(path) {
_ = parse_rs(path, scope, ts);
}
}
let mut ts = TS::new();
for scope in &args.scopes {
let mut seen = HashSet::new();
for path in &args.paths {
recurse(&PathBuf::from(path), scope, &mut seen, &mut ts);
}
}
ts
}
fn is_rust_file(path: &Path) -> bool {
match path.extension() {
Some(ext) => ext == "rs",
None => false,
}
}
fn parse_rs(path: &PathBuf, scope: &str, ts: &mut TS) -> Result<(), Box<dyn Error>> {
let mut f = File::open(path)?;
let mut b = String::new();
f.read_to_string(&mut b)?;
if b.contains(EMIT) {
let tokens = syn::parse_file(&b)?;
EmitVisitor(scope, ts).fold_file(tokens);
}
Ok(())
}
struct EmitVisitor<'a>(&'a str, &'a mut TS);
impl<'a> Fold for EmitVisitor<'a> {
fn fold_macro(&mut self, m: Macro) -> Macro {
let id = match m.path.segments.last() {
Some(id) => id,
None => return m,
};
if id.ident != EMIT {
return m;
}
let mut it = m.tokens.clone().into_iter();
while match (it.next(), it.next()) {
(Some(k), Some(TT::Group(g))) => {
if g.delimiter() != Brace {
panic!("wrong delimiter; {{ block }} expected after scope")
}
if k.to_string() == self.0 {
self.1.extend(g.stream());
}
true
}
(None, None) => false,
(_, _) => panic!("emit should contain 'scope {{ block }}' pairs"),
} { }
m
}
}