use crate::*;
use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashSet};
use std::fmt::Debug;
use std::hash::{Hash, Hasher};
use std::io::{Error, ErrorKind};
use std::path::{Component, Path, PathBuf};
use std::result::Result as StdResult;
use std::{env, fs};
use cargo_toml::{Manifest, Product};
use path_absolutize::*;
macro_rules! path {
( $($segment:expr),+ ) => {{
let mut path = ::std::path::PathBuf::new();
$(path.push($segment);)*
path
}};
( $($segment:expr),+; capacity = $n:expr ) => {{
let mut path = ::std::path::PathBuf::with_capacity($n);
$(path.push($segment);)*
path
}};
}
#[derive(Debug)]
pub struct Paths {
pub root_path: PathBuf,
pub examples_path: PathBuf,
pub cargo_toml_path: PathBuf,
pub manifest: Manifest,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ExampleType {
Simple,
MultiFile,
Crate(PathBuf, Option<String>),
Custom,
}
#[derive(Clone, Debug, Eq)]
pub struct ExampleFile {
pub name: String,
pub path: PathBuf,
pub path_type: ExampleType,
pub required_features: Option<String>,
}
impl Ord for ExampleFile {
fn cmp(&self, other: &Self) -> Ordering {
self.name.cmp(&other.name)
}
}
impl PartialOrd for ExampleFile {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq<Self> for ExampleFile {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl Hash for ExampleFile {
fn hash<H: Hasher>(&self, state: &mut H) {
self.path.hash(state)
}
}
impl PartialEq<PathBuf> for ExampleFile {
fn eq(&self, other: &PathBuf) -> bool {
&self.path == other
}
}
impl TryFrom<PathBuf> for ExampleFile {
type Error = Error;
fn try_from(path: PathBuf) -> StdResult<Self, Self::Error> {
let file_type = path.metadata()?.file_type();
if file_type.is_dir() {
let main_rs = path.join(MAIN_RS);
if main_rs.is_file() {
return Ok(Self {
name: path.last(),
path: main_rs,
path_type: ExampleType::MultiFile,
required_features: None,
});
}
let cargo_toml = path.join(CARGO_TOML);
if cargo_toml.is_file() {
let name = path.last();
let main_rs = path!(
path,
"src",
MAIN_RS;
capacity = path.as_os_str().len() + 12
);
let path = if main_rs.is_file() {
main_rs
} else {
cargo_toml.clone()
};
return Ok(Self {
name,
path,
path_type: ExampleType::Crate(cargo_toml, None),
required_features: None,
});
}
} else if file_type.is_file() && matches!(path.extension(), Some(e) if e == RUST_FILE_EXT) {
let name = path.file_stem().unwrap().to_str().unwrap().to_owned();
return Ok(Self {
name,
path,
path_type: ExampleType::Simple,
required_features: None,
});
}
Err(Error::new(ErrorKind::InvalidData, "not an example file"))
}
}
impl ExampleFile {
pub fn new<P: AsRef<Path>>(
root: &Path,
name: String,
path: P,
required_features: Option<String>,
) -> Self {
let abs_path = path.as_ref().absolutize_from(root).unwrap();
let path = PathBuf::from(abs_path);
Self {
name,
path,
path_type: ExampleType::Custom,
required_features,
}
}
}
impl Paths {
pub fn resolve() -> Result<Self> {
let current_dir = env::current_dir()?;
let examples_folder = Path::new(EXAMPLES_FOLDER);
let cargo_toml_file = Path::new(CARGO_TOML);
let examples_path = current_dir.join(examples_folder);
let cargo_toml_path = current_dir.join(cargo_toml_file);
if examples_path.is_dir() && cargo_toml_path.is_file() {
let manifest_contents = fs::read(&cargo_toml_path)?;
let manifest = Manifest::from_slice(&manifest_contents)?;
return Ok(Self {
root_path: current_dir,
examples_path,
cargo_toml_path,
manifest,
});
}
let mut comps = current_dir.components();
while let Some(p) = comps.next_back() {
match p {
Component::Normal(_) | Component::CurDir | Component::ParentDir => {
let root_path = comps.as_path().to_path_buf();
let examples_path = root_path.join(examples_folder);
let cargo_toml_path = root_path.join(cargo_toml_file);
if examples_path.is_dir() && cargo_toml_path.is_file() {
let manifest_contents = fs::read(&cargo_toml_path)?;
let manifest = Manifest::from_slice(&manifest_contents)?;
return Ok(Self {
root_path,
examples_path,
cargo_toml_path,
manifest,
});
}
}
_ => {}
};
}
Err(Box::new(Error::new(
ErrorKind::NotFound,
format!(
"could not find `{CARGO_TOML}` with an `{EXAMPLES_FOLDER}` folder \
in `{cwd}` or any parent directory",
cwd = current_dir.to_str().unwrap()
),
)))
}
pub fn example_files(&self) -> Result<BTreeMap<Cow<'_, str>, ExampleFile>> {
let mut files: BTreeMap<Cow<'_, str>, _> = BTreeMap::new();
let mut file_paths: HashSet<PathBuf> = HashSet::new();
#[inline]
fn required_features(example: &Product) -> Option<String> {
if example.required_features.is_empty() {
None
} else {
Some(example.required_features.join(" "))
}
}
let manifest = &self.manifest;
let root = &self.root_path;
for example in manifest.example.iter() {
if let Some(ref path) = example.path {
if let Some(ref name) = example.name {
let f =
ExampleFile::new(root, name.to_owned(), path, required_features(example));
file_paths.insert(f.path.clone());
files.insert(Cow::Borrowed(name), f);
}
}
else if let Some(ref name) = example.name {
let required_features = required_features(example);
if required_features.is_some() {
let f = ExampleFile::new(root, name.to_owned(), "N/A", required_features);
files.insert(Cow::Borrowed(name), f);
}
}
}
for entry in fs::read_dir(&self.examples_path)?.filter_map(StdResult::ok) {
let path: PathBuf = entry.path();
if file_paths.contains(&path) {
continue;
}
if let Ok(f) = ExampleFile::try_from(path) {
if let ExampleType::Crate(ref cargo_toml, _) = f.path_type {
let manifest_contents = fs::read_to_string(cargo_toml)?;
let num_bins = manifest_contents.matches("[[bin]]").count();
if num_bins > 1 {
let crate_dir = cargo_toml.parent().unwrap();
let manifest = Manifest::from_str(&manifest_contents)?;
for ref bin in manifest.bin {
if let Some(ref name) = bin.name {
let key = Cow::Owned(name.to_owned());
let path_type =
ExampleType::Crate(cargo_toml.clone(), Some(name.to_owned()));
let path = if let Some(ref p) = bin.path {
Path::new(p).absolutize_from(crate_dir).unwrap().into()
} else {
f.path.clone()
};
let bin_f = ExampleFile {
name: name.to_owned(),
path,
path_type,
required_features: required_features(bin),
};
files.insert(key, bin_f);
}
}
continue;
}
}
let key = Cow::Owned(f.name.to_owned());
if let Some(example) = files.get_mut(&key) {
example.path = f.path;
example.path_type = f.path_type;
}
else {
files.insert(key, f);
}
}
}
Ok(files)
}
}