#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
#![deny(missing_docs)]
use std::path::PathBuf;
use thiserror::Error;
pub extern crate proc_macro2;
pub extern crate syn;
use im_rc::Vector;
pub type UserError = Box<dyn std::error::Error + Send + Sync + 'static>;
#[allow(missing_docs)]
#[derive(Error, Debug)]
pub enum AttrParseError {
#[error("#[path] attribute has not exactly two tokens: equal sign and a path")]
NotExactlyTwoTokens,
#[error("#[path] attribute's first token is not an equal sign punctuation")]
FirstTokenIsNotEqualSign,
#[error("#[path] attribute's second token is not a string literal")]
SecondTokenIsNotStringLiteral,
#[error("#[cfg_attr] attribute is not followed by a single round brackets group")]
CfgAttrNotRoundGroup,
#[error("#[cfg_attr] attribute does not have exactly two parameters")]
CfgAttrNotTwoParams,
#[error("`#[cfg` is not followed by a sole round parentheses group")]
MalformedCfg,
}
#[allow(missing_docs)]
#[derive(Error, Debug)]
pub enum ErrorCase {
#[error("Cannot open file {path}: {e}")]
FailedToOpenFile { path: PathBuf, e: std::io::Error },
#[error("The module have multiple explicit #[path] directives")]
MultipleExplicitPathsSpecifiedForOneModule,
#[error("Both name/mod.rs and name.rs present")]
BothModRsAndNameRsPresent,
#[error("error parsing attribute: {0}")]
AttrParseError(AttrParseError),
#[error("syn parsing error: {0}")]
SynParseError(syn::parse::Error),
#[error("Error from callback: {0}")]
ErrorFromCallback(UserError),
}
#[derive(Error, Debug)]
#[error("Expanding module `{module}`: {inner}", module=PathForDisplay(module))]
pub struct Error {
pub module: syn::Path,
pub inner: ErrorCase,
}
struct PathForDisplay<'a>(&'a syn::Path);
impl<'a> std::fmt::Display for PathForDisplay<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
itertools::join(self.0.segments.iter().map(|x| x.ident.to_string()), "::").fmt(f)
}
}
pub trait Resolver {
fn resolve(
&mut self,
module_name: syn::Path,
path_relative_to_crate_root: PathBuf,
) -> Result<Option<syn::File>, Error>;
fn check_cfg(&mut self, cfg: syn::Meta) -> Result<bool, UserError>;
}
pub struct ResolverHelper<F1, F2>(pub F1, pub F2)
where
F1: FnMut(syn::Path, PathBuf) -> Result<Option<syn::File>, Error>,
F2: FnMut(syn::Meta) -> Result<bool, UserError>;
impl<F1, F2> Resolver for ResolverHelper<F1, F2>
where
F1: FnMut(syn::Path, PathBuf) -> Result<Option<syn::File>, Error>,
F2: FnMut(syn::Meta) -> Result<bool, UserError>,
{
fn resolve(
&mut self,
module_name: syn::Path,
path_relative_to_crate_root: PathBuf,
) -> Result<Option<syn::File>, Error> {
(self.0)(module_name, path_relative_to_crate_root)
}
fn check_cfg(&mut self, cfg: syn::Meta) -> Result<bool, UserError> {
(self.1)(cfg)
}
}
pub fn expand_modules_into_inline_modules<R: Resolver>(
content: &mut syn::File,
resolver: &mut R,
) -> Result<(), Error> {
let dirs = Vector::new();
expand_impl::expand_impl(
&mut content.items,
resolver,
Vector::new(),
dirs.clone(),
dirs,
)?;
Ok(())
}
pub fn read_full_crate_source_code(
path: impl AsRef<std::path::Path>,
cfg_attr_path_handler: impl FnMut(syn::Meta) -> Result<bool, UserError>,
) -> Result<syn::File, Error> {
let path = path.as_ref();
let root_source = std::fs::read_to_string(path).map_err(|e| Error {
module: syn::Path {
leading_colon: None,
segments: syn::punctuated::Punctuated::new(),
},
inner: ErrorCase::FailedToOpenFile {
path: path.to_owned(),
e,
},
})?;
let mut root_source: syn::File = syn::parse_file(&root_source).map_err(|e| Error {
module: syn::Path {
leading_colon: None,
segments: syn::punctuated::Punctuated::new(),
},
inner: ErrorCase::SynParseError(e),
})?;
let parent_dir = path.parent();
struct MyResolver<'a, F: FnMut(syn::Meta) -> Result<bool, UserError>> {
cfg_attr_path_handler: F,
parent_dir: Option<&'a std::path::Path>,
}
impl<'a, F: FnMut(syn::Meta) -> Result<bool, UserError>> Resolver for MyResolver<'a, F> {
fn resolve(
&mut self,
module_name: syn::Path,
path_relative_to_crate_root: PathBuf,
) -> Result<Option<syn::File>, Error> {
let path = if let Some(parent_dir) = self.parent_dir {
parent_dir.join(&path_relative_to_crate_root)
} else {
path_relative_to_crate_root
};
let module_source = std::fs::read_to_string(&path).map_err(|e| Error {
module: module_name.clone(),
inner: ErrorCase::FailedToOpenFile {
path: path.clone(),
e,
},
})?;
let module_source: syn::File = syn::parse_file(&module_source).map_err(|e| Error {
module: module_name,
inner: ErrorCase::SynParseError(e),
})?;
Ok(Some(module_source))
}
fn check_cfg(&mut self, cfg: syn::Meta) -> Result<bool, UserError> {
(self.cfg_attr_path_handler)(cfg)
}
}
expand_modules_into_inline_modules(
&mut root_source,
&mut MyResolver {
cfg_attr_path_handler,
parent_dir,
},
)?;
Ok(root_source)
}
mod attrs;
mod expand_impl;