use std::fmt;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::mem::swap;
use syn;
use syn::visit::Visit;
#[derive(Debug)]
pub enum Error {
ParseCannotOpenFile {
src_path: String,
},
ParseSyntaxError {
src_path: String,
error: syn::parse::Error,
},
LexError {
src_path: String,
line: u32,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::ParseCannotOpenFile { ref src_path } => {
write!(f, "Parsing crate: cannot open file `{}`.", src_path)
}
Error::ParseSyntaxError {
ref src_path,
ref error,
} => write!(f, "Parsing crate:`{}`:\n{:?}", src_path, error),
Error::LexError {
ref src_path,
ref line,
} => write!(f, "{}:{}: Lexing error", src_path, line + 1),
}
}
}
pub struct Parser<ModVisitor> {
current_path: PathBuf, mod_dir: PathBuf,
mod_error: Option<Error>, mod_visitor: ModVisitor
}
impl<ModVisitor> Parser<ModVisitor>
where ModVisitor : FnMut(&PathBuf, &str, &syn::File) -> Result<(), Error>
{
pub fn with_visitor(mod_visitor: ModVisitor) -> Self {
Parser {
current_path: PathBuf::default(),
mod_dir: PathBuf::default(),
mod_error: Option::default(),
mod_visitor
}
}
pub fn parse_crate<P: AsRef<Path>>(&mut self, crate_root: P) -> Result<(), Error> {
self.parse_mod(crate_root)
}
fn parse_mod<P: AsRef<Path>>(&mut self, mod_path: P) -> Result<(), Error> {
let mut s = String::new();
let mut f = File::open(&mod_path).map_err(|_| Error::ParseCannotOpenFile {
src_path: mod_path.as_ref().to_str().unwrap().to_owned(),
})?;
f.read_to_string(&mut s)
.map_err(|_| Error::ParseCannotOpenFile {
src_path: mod_path.as_ref().to_str().unwrap().to_owned(),
})?;
let fi = syn::parse_file(&s).map_err(|x| Error::ParseSyntaxError {
src_path: "".to_owned(),
error: x,
})?;
let mut current_path = mod_path.as_ref().into();
let mut mod_dir = mod_path.as_ref().parent().unwrap().into();
swap(&mut self.current_path, &mut current_path);
swap(&mut self.mod_dir, &mut mod_dir);
self.visit_file(&fi);
if let Some(err) = self.mod_error.take() {
return Err(err);
}
(self.mod_visitor)(¤t_path, &s, &fi);
swap(&mut self.current_path, &mut current_path);
swap(&mut self.mod_dir, &mut mod_dir);
Ok(())
}
}
impl<'ast, ModVisitor> Visit<'ast> for Parser<ModVisitor>
where ModVisitor : FnMut(&PathBuf, &str, &syn::File) -> Result<(), Error>
{
fn visit_item_mod(&mut self, item: &'ast syn::ItemMod) {
if self.mod_error.is_some() {
return;
}
if item.content.is_some() {
let mut parent = self.mod_dir.join(item.ident.to_string());
swap(&mut self.mod_dir, &mut parent);
syn::visit::visit_item_mod(self, item);
swap(&mut self.mod_dir, &mut parent);
return;
}
for attr in &item.attrs {
match attr.interpret_meta() {
Some(syn::Meta::NameValue(syn::MetaNameValue {
ident: ref id,
lit: syn::Lit::Str(ref s),
..
})) if id == "path" =>
{
let mod_path = self.mod_dir.join(&s.value());
return self.parse_mod(mod_path).unwrap_or_else(|err| self.mod_error = Some(err));
}
_ => {}
}
}
let mod_name = item.ident.to_string();
let mut subdir = self.mod_dir.join(mod_name.clone());
subdir.push("mod.rs");
if subdir.is_file() {
return self.parse_mod(subdir).unwrap_or_else(|err| self.mod_error = Some(err));
}
let adjacent = self.mod_dir.join(&format!("{}.rs", mod_name));
if adjacent.is_file() {
return self.parse_mod(adjacent).unwrap_or_else(|err| self.mod_error = Some(err));
}
panic!(
"No file with module definition for `mod {}` in file {:?}",
mod_name, self.current_path
);
}
}