use std::{
ffi::{OsStr, OsString},
fs,
path::PathBuf,
str::FromStr,
};
use anyhow::{anyhow, Context};
use clap::Parser;
use tap::Tap;
use xshell::{cmd, Shell};
#[derive(Parser)] #[clap(name = "cargo")]
#[clap(bin_name = "cargo")]
enum Cargo {
Examples(Examples),
}
#[derive(Parser)]
#[clap(version, about, long_about = None, args_conflicts_with_subcommands = true)]
struct Examples {
#[clap(long, value_parser, value_name = "FILE")]
manifest_path: Option<PathBuf>,
#[clap(short, long)]
list: bool,
#[clap(short, long)]
print: bool,
#[clap(short, long, value_name = "EXAMPLE")]
from: Option<OsString>,
#[clap(short, long)]
no_run: bool,
#[clap(raw = true)]
cargo_args: Option<String>,
}
enum Example {
File(PathBuf),
MultiFile(PathBuf),
SubProject(PathBuf),
}
impl Example {
fn name(&self) -> Option<&OsStr> {
match self {
Example::File(path) => Some(path.file_stem()?),
Example::SubProject(path) | Example::MultiFile(path) => {
Some(path.parent()?.file_name()?)
}
}
}
}
fn main() -> anyhow::Result<()> {
let Cargo::Examples(cli) = Cargo::parse();
let sh = Shell::new()?;
let manifest_path = cli
.manifest_path
.unwrap_or(PathBuf::from_str("Cargo.toml").unwrap());
if !manifest_path.is_file() {
return Err(anyhow!(
"the manifest-path must be a path to a Cargo.toml file"
));
}
let root_dir = manifest_path
.parent()
.context("Cargo.toml does not have parent directory")?;
let examples_dir = root_dir.to_path_buf().tap_mut(|p| p.push("examples"));
let examples: Vec<Example> = fs::read_dir(examples_dir)?
.filter_map(|entry| entry.ok()) .map(|entry| entry.path())
.filter_map(|path| {
if path.is_file() {
if path.extension().map_or(false, |ext| ext == "rs") {
return Some(Example::File(path));
}
} else if path.is_dir() {
let example_main_path = path.clone().tap_mut(|p| p.push("main.rs"));
if example_main_path.is_file() {
return Some(Example::MultiFile(example_main_path));
}
let example_manifest_path = path.clone().tap_mut(|p| p.push("Cargo.toml"));
if example_manifest_path.is_file() {
return Some(Example::SubProject(example_manifest_path));
}
}
None
})
.filter(|example| example.name().is_some())
.collect::<Vec<_>>()
.tap_mut(|examples| examples.sort_by(|a, b| a.name().unwrap().cmp(b.name().unwrap())));
if cli.list {
for example in &examples {
println!("{}", example.name().unwrap().to_string_lossy());
}
}
let mut run_examples = cli.from.is_none();
for example in &examples {
if let Some(ref from) = cli.from {
if from == example.name().unwrap() {
run_examples = true;
}
}
if !run_examples {
continue;
}
if cli.print {
println!("{}", example.name().unwrap().to_string_lossy());
}
if cli.no_run {
continue;
}
sh.change_dir(root_dir);
let command = match example {
Example::File(_) => {
let name = example.name().unwrap();
cmd!(
sh,
"cargo run --manifest-path {manifest_path} --example {name}"
)
}
Example::MultiFile(_) => {
let name = example.name().unwrap();
cmd!(
sh,
"cargo run --manifest-path {manifest_path} --example {name}"
)
}
Example::SubProject(manifest_path) => {
cmd!(sh, "cargo run --manifest-path {manifest_path}")
}
};
if let Some(ref args) = cli.cargo_args {
command.arg(args).run()?;
} else {
command.run()?;
}
}
Ok(())
}