#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(
missing_debug_implementations,
clippy::missing_panics_doc,
clippy::unwrap_used,
clippy::print_stderr,
clippy::print_stdout
)]
pub mod def;
pub use def::*;
mod parsing;
use parsing::*;
pub mod serde;
use anyhow::{Context, Result};
use glob::glob;
use std::{
io::Read,
path::Path,
process::{Command, Stdio},
time::SystemTime,
};
pub fn parse_file<T>(path: T) -> Result<Vec<FunctionDef>>
where
T: AsRef<Path>,
{
let path = path.as_ref();
let mut file = std::fs::File::open(path)
.with_context(|| format!("Fail to open file: `{}`", path.display()))?;
let mut source_code = String::new();
file.read_to_string(&mut source_code)
.with_context(|| format!("Fail to read file: `{}`", path.display()))?;
let res = syn::parse_file(&source_code)
.with_context(|| format!("Fail to parse file: `{}`", path.display()))?
.items
.into_iter()
.fold(Vec::new(), |mut acc, item| {
acc.extend(item.parse());
acc
});
Ok(res)
}
pub fn parse_directory<T>(directory: T) -> Result<Vec<File>>
where
T: Into<String>,
{
let regex = format!("{}/**/*.rs", directory.into()).replace("//", "/");
let mut res = Vec::new();
let context = || format!("Invalid glob regex: `{regex}`");
for path in glob(®ex).with_context(context)?.filter_map(Result::ok) {
let modified = path_created(&path);
let file_path = path_to_str(&path).into();
let functions = parse_file(&path)
.with_context(|| format!("Fail to parse file: `{}`", path.display()))?;
res.push(File {
path: file_path,
functions,
crate_name: "".into(),
modified,
});
}
Ok(res)
}
pub fn parse_workspace<T>(directory: T) -> Result<Workspace>
where
T: Into<String>,
{
let dir = directory.into();
let dir = dir.trim_end_matches('/');
let res = Command::new("cargo")
.current_dir(dir)
.arg("metadata")
.arg("--format-version=1")
.stderr(Stdio::inherit())
.output()?;
if !res.status.success() {
panic!("Fail to parse workspace metadata");
}
let workspace_raw =
serde_json::from_slice(&res.stdout).context("Fail to deserialize JSON string")?;
Ok(parsing::parse_workspace(workspace_raw))
}
fn path_to_str(path: &Path) -> &str {
path.to_str().expect("Failed to convert Path to &str")
}
fn path_created(path: &Path) -> SystemTime {
path.metadata()
.expect("No metadata")
.created()
.expect("No created time")
}