diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..05daec8 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ + github: [rnag] + custom: ["https://www.buymeacoffee.com/ritviknag"] diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bc20e1..224774d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,16 @@ Possible header types: - `other_method()` --> +## v0.3.0 (2023-03-13) + +### Features +- Add support for [Cargo crates with binaries] within the `examples/` folder, + each containing their own `Cargo.toml` file. 🎉 + - This calls `cargo run --manifest-path ` internally, passing `--bin` in the case of multiple [binary targets]. + +[Cargo crates with binaries]: https://github.com/rnag/cargo-rx/issues/19 +[binary targets]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries + ## v0.2.0 (2022-04-25) ### Breaking Changes diff --git a/README.md b/README.md index 3df6c93..f76cd1e 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,13 @@ [github](https://github.com/rnag/cargo-rx) [crates.io](https://crates.io/crates/cargo-rx) [docs.rs](https://docs.rs/cargo-rx) -[build status](https://github.com/rnag/cargo-rx/actions?query=branch%3Amain) +[build status](https://github.com/rnag/cargo-rx/actions/workflows/build.yml?query=branch%3Amain) **`cargo-rx` is a simple fuzzy finder and *R*unner for *Ex*amples in a [Cargo] project.** -[![rx demo](https://asciinema.org/a/483363.svg)](https://asciinema.org/a/483363) + + rx demo + [Cargo]: http://doc.crates.io/ @@ -15,6 +17,8 @@ This crate provides a single executable: `rx`. Basically anywhere you would use `cargo run --example` in a Rust project, try `rx` instead. +**If this project has helped you, please consider making a [donation](https://www.buymeacoffee.com/ritviknag).** + @@ -27,6 +31,7 @@ Rust project, try `rx` instead. * [Windows](#windows) * [Features](#features) * [Contributing](#contributing) +* [Buy me a coffee](#buy-me-a-coffee) * [License](#license) * [Authors](#authors) @@ -75,11 +80,13 @@ thus, the `fzf` tool serves as a stand-in alternative for now. * Fuzzy finder, which leverages [skim] to sort and search for *examples* in a Cargo project -- when called with just `rx`. * Pass arguments after `--` to the selected example. * Automatically [enables required-features] when running an example. +* Support for nested [crates with binary targets]. * Play back of most recently run example via the `--replay` option. * Automatically enables `+nightly` toolchain when passed in *unstable options* to `cargo run`, such as `--unit-graph`. [skim]: https://github.com/lotabout/skim [enables required-features]: https://github.com/rust-lang/cargo/issues/4663 +[crates with binary targets]: https://github.com/rnag/cargo-rx/issues/19 ## Contributing @@ -91,6 +98,12 @@ Check out the [Contributing][] section in the docs for more info. [Contributing]: CONTRIBUTING.md [open an issue]: https://github.com/rnag/cargo-rx/issues +## Buy me a coffee + +Liked some of my work? Buy me a coffee (or more likely a beer) + +Buy Me A Coffee + ## License This project is proudly licensed under the MIT license ([LICENSE](LICENSE) diff --git a/examples/hello-world-axum/Cargo.toml b/examples/hello-world-axum/Cargo.toml new file mode 100644 index 0000000..91fc1ab --- /dev/null +++ b/examples/hello-world-axum/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "hello-world-axum" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axum = { version = "0.6.10" } +tokio = { version = "1.0", features = ["full"] } diff --git a/examples/hello-world-axum/src/main.rs b/examples/hello-world-axum/src/main.rs new file mode 100644 index 0000000..24df4ac --- /dev/null +++ b/examples/hello-world-axum/src/main.rs @@ -0,0 +1,17 @@ +//! This example is a custom use-case where we have Cargo +//! (binary) crates within the `examples/` folder. +//! +//! # Note +//! +//! This is a use case that not even `cargo run --example` +//! currently supports -- in fact, this is not even +//! detected as an example otherwise! +//! +//! # Sample Projects With This Setup +//! +//! * [axum](https://github.com/tokio-rs/axum) +//! * [tract](https://github.com/sonos/tract) +//! +fn main() { + println!("Hello, world!"); +} diff --git a/examples/hello-world-multi-bin/Cargo.toml b/examples/hello-world-multi-bin/Cargo.toml new file mode 100644 index 0000000..0c57b05 --- /dev/null +++ b/examples/hello-world-multi-bin/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "hello-world-multi-bin" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { version = "1.0", features = ["full"] } + +[[bin]] +name = "hello-world-bin1" +path = "src/main.rs" + +[[bin]] +name = "hello-world-bin2" +path = "src/alt.rs" diff --git a/examples/hello-world-multi-bin/src/alt.rs b/examples/hello-world-multi-bin/src/alt.rs new file mode 100644 index 0000000..a9d2289 --- /dev/null +++ b/examples/hello-world-multi-bin/src/alt.rs @@ -0,0 +1,19 @@ +//! # Binary Target #2 +//! +//! This example is a custom use-case where we have multiple +//! Cargo (binary) crates within the `examples/` folder. +//! +//! # Note +//! +//! This is a use case that not even `cargo run --example` +//! currently supports -- in fact, this is not even +//! detected as an example otherwise! +//! +//! # Sample Projects With This Setup +//! +//! * [axum](https://github.com/tokio-rs/axum) +//! * [tract](https://github.com/sonos/tract) +//! +fn main() { + println!("Hello, from crate `hello-world-multi-bin` -> Bin #2!"); +} diff --git a/examples/hello-world-multi-bin/src/main.rs b/examples/hello-world-multi-bin/src/main.rs new file mode 100644 index 0000000..85ede86 --- /dev/null +++ b/examples/hello-world-multi-bin/src/main.rs @@ -0,0 +1,19 @@ +//! # Binary Target #1 +//! +//! This example is a custom use-case where we have multiple +//! Cargo (binary) crates within the `examples/` folder. +//! +//! # Note +//! +//! This is a use case that not even `cargo run --example` +//! currently supports -- in fact, this is not even +//! detected as an example otherwise! +//! +//! # Sample Projects With This Setup +//! +//! * [axum](https://github.com/tokio-rs/axum) +//! * [tract](https://github.com/sonos/tract) +//! +fn main() { + println!("Hello, from crate `hello-world-multi-bin` -> Bin #1!"); +} diff --git a/src/constants.rs b/src/constants.rs index 6d9508d..3871efe 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -14,6 +14,8 @@ pub const RUST_FILE_EXT: &str = "rs"; /// Name of the `Cargo.toml` file in Cargo projects pub const CARGO_TOML: &str = "Cargo.toml"; +/// Name of the `main.rs` file in Cargo projects +pub const MAIN_RS: &str = "main.rs"; /// Name of the `settings.toml` file for local project cache pub const SETTINGS_TOML: &str = "settings.toml"; diff --git a/src/lib.rs b/src/lib.rs index 00f2ef8..a54c6a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,6 +88,7 @@ mod constants; mod models; // noinspection SpellCheckingInspection mod osstringext; +mod pathbufext; mod run_ext; mod run_impl; mod types; @@ -96,6 +97,7 @@ use cache::*; pub use constants::*; pub use models::*; pub use osstringext::*; +pub use pathbufext::*; pub use run_ext::*; pub(crate) use run_impl::*; pub use types::*; @@ -109,10 +111,9 @@ pub fn process_input(args: Args) -> Result<()> { let p = Paths::resolve()?; - let name_to_required_features = p.example_to_required_features()?; - let files = p.example_file_paths()?; + let files = p.example_files()?; - process_input_inner(files, p, args, name_to_required_features) + process_input_inner(files, &p, args) } /// This is a **patch** so that the `colored` output works as expected diff --git a/src/models/path.rs b/src/models/path.rs index 6813986..b7656f0 100644 --- a/src/models/path.rs +++ b/src/models/path.rs @@ -1,7 +1,8 @@ use crate::*; +use std::borrow::Cow; use std::cmp::Ordering; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashSet}; use std::fmt::Debug; use std::hash::{Hash, Hasher}; use std::io::{Error, ErrorKind}; @@ -9,9 +10,25 @@ use std::path::{Component, Path, PathBuf}; use std::result::Result as StdResult; use std::{env, fs}; -use cargo_toml::Manifest; +use cargo_toml::{Manifest, Product}; use path_absolutize::*; +/// Allows creating a Path with a Builder Pattern +/// +/// Credits: https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/16?u=rnag +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 + }}; +} + /// Represents *path info* on a Cargo project. #[derive(Debug)] pub struct Paths { @@ -29,7 +46,7 @@ pub struct Paths { } /// Represents the *type* of an example file. -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum ExampleType { /// This represents a *simple* example file, for ex. a `hello_world.rs` /// file in an `examples/` folder. @@ -40,13 +57,19 @@ pub enum ExampleType { /// example. MultiFile, + /// This represents a binary crate, i.e. a sub-folder containing a + /// `Cargo.toml` file in an `examples/` folder; Note that Cargo (and + /// notably `cargo run --example`) does not support this particular + /// use-case, as of yet. + Crate(PathBuf, Option), + /// This represents an example file with a *custom path* defined in the /// `Cargo.toml` file of a Cargo project. Custom, } /// Represents an *example file* in a Cargo project. -#[derive(Debug, Eq)] +#[derive(Clone, Debug, Eq)] pub struct ExampleFile { /// The *file stem* (i.e. filename without the extension) for an /// example file, or the *folder name* in the case of a `main.rs` @@ -58,6 +81,15 @@ pub struct ExampleFile { /// Type of example file pub path_type: ExampleType, + + /// A space-separated list of required features for the example (or binary) to run. + /// + /// The required-features field specifies which features the product needs in order to be built. + /// + /// If any of the required features are not selected, the product will be skipped. + /// This is only relevant for the `[[bin]]`, `[[bench]]`, `[[test]]`, and `[[example]]` sections, + /// it has no effect on `[lib]`. + pub required_features: Option, } /// *order* a sequence of `ExampleFile`s by the `name` field. @@ -98,28 +130,48 @@ impl TryFrom for ExampleFile { type Error = Error; /// Try to create an `ExampleFile` from a `PathBuf` object. - fn try_from(mut path: PathBuf) -> StdResult { + fn try_from(path: PathBuf) -> StdResult { let file_type = path.metadata()?.file_type(); if file_type.is_dir() { - path.push("main.rs"); - if path.is_file() { - let mut comps = path.components(); - // discard the filename (main.rs) - let _ = comps.next_back(); - // return the parent folder name - let name = comps - .next_back() - .unwrap() - .as_os_str() - .to_str() - .unwrap() - .to_owned(); + let main_rs = path.join(MAIN_RS); + + if main_rs.is_file() { + return Ok(Self { + // return the parent folder name + 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() { + // the parent folder name + let name = path.last(); + + let main_rs = path!( + path, + "src", + MAIN_RS; + // "srcmain.rs".len() == 10 + // adding +2 because of slashes (/) between components + 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::MultiFile, + 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) { @@ -129,6 +181,7 @@ impl TryFrom for ExampleFile { name, path, path_type: ExampleType::Simple, + required_features: None, }); } @@ -138,8 +191,8 @@ impl TryFrom for ExampleFile { } impl ExampleFile { - /// Create an `ExampleFile` from a example `name` and a (possibly relative) - /// `path`. + /// Create an `ExampleFile` from a example `name`, a (possibly relative) + /// `path`, and a list of *required features* for the example. /// /// # Arguments /// * `root` - The current working directory, used in case the path is @@ -147,7 +200,12 @@ impl ExampleFile { /// * `name` - The name of the example file, without the extension /// * `path` - The path to the example file. This can be a relative path /// and contain characters such as `.` and `..` for instance. - pub fn from_name_and_path>(root: &Path, name: String, path: P) -> Self { + pub fn new>( + root: &Path, + name: String, + path: P, + required_features: Option, + ) -> Self { let abs_path = path.as_ref().absolutize_from(root).unwrap(); let path = PathBuf::from(abs_path); @@ -155,6 +213,7 @@ impl ExampleFile { name, path, path_type: ExampleType::Custom, + required_features, } } } @@ -177,8 +236,8 @@ impl Paths { 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); + 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)?; @@ -199,8 +258,8 @@ impl Paths { 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); + 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)?; @@ -228,33 +287,26 @@ impl Paths { ))) } - /// Return a mapping of *example name* to a list of *required features* for an example. - pub fn example_to_required_features(&self) -> Result> { - let mut name_to_required_features: HashMap = - HashMap::with_capacity(self.manifest.example.len()); - let manifest = &self.manifest; - - for example in manifest.example.iter() { - match example.name { - Some(ref name) if !example.required_features.is_empty() => { - name_to_required_features - .insert(name.clone(), example.required_features.join(" ")); - } - _ => (), - }; + /// Returns an ordered (A -> Z) mapping of file name to resolved file + /// (`ExampleFile` objects) of each *example* file in the Cargo project. + pub fn example_files(&self) -> Result, ExampleFile>> { + let mut files: BTreeMap, _> = BTreeMap::new(); + let mut file_paths: HashSet = HashSet::new(); + + #[inline] + fn required_features(example: &Product) -> Option { + if example.required_features.is_empty() { + None + } else { + Some(example.required_features.join(" ")) + } } - Ok(name_to_required_features) - } - - /// Returns file paths (`PathBuf` objects) of each *example* file in the Cargo project. - pub fn example_file_paths(&self) -> Result> { - let mut files = HashSet::new(); - let manifest = &self.manifest; let root = &self.root_path; for example in manifest.example.iter() { + // only if `name` and `path` are both provided if let Some(ref path) = example.path { if let Some(ref name) = example.name { // I debated whether to add this for Mac/Linux, however it @@ -264,9 +316,20 @@ impl Paths { // #[cfg(not(target_family = "windows"))] // let path = path.replace('\\', "/"); + let f = + ExampleFile::new(root, name.to_owned(), path, required_features(example)); + + file_paths.insert(f.path.clone()); + files.insert(Cow::Borrowed(name), f); + } + } + // only if `name` and `required-features` are both provided + else if let Some(ref name) = example.name { + let required_features = required_features(example); - let f = ExampleFile::from_name_and_path(root, name.to_owned(), path); - files.insert(f); + if required_features.is_some() { + let f = ExampleFile::new(root, name.to_owned(), "N/A", required_features); + files.insert(Cow::Borrowed(name), f); } } } @@ -274,8 +337,61 @@ impl Paths { for entry in fs::read_dir(&self.examples_path)?.filter_map(StdResult::ok) { let path: PathBuf = entry.path(); + // if a file path is already specified in the `Cargo.toml`, then this + // is an `ExampleType::Custom`, so we don't need to check the file. + if file_paths.contains(&path) { + continue; + } + if let Ok(f) = ExampleFile::try_from(path) { - files.insert(f); + // if we have a Cargo crate (with its own `Cargo.toml`) in + // the `examples/` folder, we'll need to check that here. + 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 we have multiple binary (or `[[bin]]`) targets + // listed in the `Cargo.toml`, then we'll need to + // separately add each target as an example. + if num_bins > 1 { + let crate_dir = cargo_toml.parent().unwrap(); + let manifest = Manifest::from_str(&manifest_contents)?; + // iterate over each binary target + 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() + }; + // add the binary target to the list of (runnable) files + 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 the example name already exists in `Cargo.toml`, then + // just update values as needed. + if let Some(example) = files.get_mut(&key) { + example.path = f.path; + example.path_type = f.path_type; + } + // else, we record and add a new example file that can be run. + else { + files.insert(key, f); + } } } diff --git a/src/pathbufext.rs b/src/pathbufext.rs new file mode 100644 index 0000000..5fed268 --- /dev/null +++ b/src/pathbufext.rs @@ -0,0 +1,21 @@ +//! Extensions for `PathBuf` +//! +use std::path::PathBuf; + +#[doc(hidden)] +pub trait PathBufExt { + fn last(&self) -> String; +} + +impl PathBufExt for PathBuf { + /// returns the last component of a `PathBuf`, as a string. + fn last(&self) -> String { + self.components() + .next_back() + .unwrap() + .as_os_str() + .to_str() + .unwrap() + .to_owned() + } +} diff --git a/src/run_ext.rs b/src/run_ext.rs index 39f3989..5dd7604 100644 --- a/src/run_ext.rs +++ b/src/run_ext.rs @@ -13,6 +13,7 @@ pub trait RunExampleExt { /// /// # Arguments /// + /// * `ex_type` - Type of example. /// * `root_path` - the base path to the Cargo directory with a /// `Cargo.toml` file. /// * `name` - the name of the Cargo example to run. @@ -21,28 +22,52 @@ pub trait RunExampleExt { /// the example. fn run_example<'a, T: IntoIterator>( &self, + ex_type: &'a ExampleType, root_path: &'a Path, name: &'a str, args: T, - required_features: Option<&'a String>, + required_features: &'a Option, ) -> Result<()> where ::Item: AsRef; } -/// Add `run --example ` as arguments to Command `cmd` +/// Add `run --example ` (or `run --manifest-path ) as arguments to Command `cmd` #[inline] -fn add_run_example(cmd: &mut Command, name: &str) { - cmd.arg("run").arg("--example").arg(name); +fn add_run_arg(cmd: &mut Command, name: &str, ex_type: &ExampleType, root_path: &Path) { + /// call `run` with a `--manifest-path` + #[inline] + fn run_with_manifest<'a>( + cmd: &'a mut Command, + root_path: &'a Path, + manifest_path: &'a Path, + ) -> &'a mut Command { + cmd.arg("run") + .arg("--manifest-path") + .arg(manifest_path.strip_prefix(root_path).unwrap()) + } + match ex_type { + // call `run` with `--manifest-path` + ExampleType::Crate(manifest_path, None) => run_with_manifest(cmd, root_path, manifest_path), + // call `run` with `--manifest-path` and `--bin` + ExampleType::Crate(manifest_path, Some(bin)) => { + run_with_manifest(cmd, root_path, manifest_path) + .arg("--bin") + .arg(bin) + } + // call `run --example` + _ => cmd.arg("run").arg("--example").arg(name), + }; } impl RunExampleExt for CommonOptions { fn run_example<'a, T: IntoIterator>( &self, + ex_type: &'a ExampleType, root_path: &'a Path, name: &'a str, args: T, - required_features: Option<&'a String>, + required_features: &'a Option, ) -> Result<()> where ::Item: AsRef, @@ -58,11 +83,11 @@ impl RunExampleExt for CommonOptions { let has_unstable_opts = has_config || has_unstable_flags || self.unit_graph; if !has_unstable_opts { - add_run_example(&mut run, name); + add_run_arg(&mut run, name, ex_type, root_path); } else { // enable the `+nightly` toolchain run.arg("+nightly"); - add_run_example(&mut run, name); + add_run_arg(&mut run, name, ex_type, root_path); // enable the `unstable-options` run.arg("-Z").arg("unstable-options"); } diff --git a/src/run_impl.rs b/src/run_impl.rs index b273106..cd56f35 100644 --- a/src/run_impl.rs +++ b/src/run_impl.rs @@ -8,33 +8,31 @@ mod inner_impl { use super::*; use std::borrow::Cow; - use std::collections::{HashMap, HashSet}; + use std::collections::BTreeMap; use std::io::Write; use std::process::{Command, Output, Stdio}; //noinspection DuplicatedCode pub(crate) fn process_input_inner( - example_files: HashSet, - dir: Paths, + example_files: BTreeMap, ExampleFile>, + dir: &Paths, args: Args, - name_to_required_features: HashMap, ) -> Result<()> { let script_args = args.args; let mut cfg: ReplayConfig = Default::default(); let output: Output; - let examples = if args.replay { + let examples_to_run = if args.replay { cfg = get_last_replay()?; vec![Cow::Owned(cfg.last_run.name)] } else if let Some(example) = args.name { vec![Cow::Owned(example)] } else { - let mut example_names: Vec<_> = example_files.iter().map(|f| f.name.as_str()).collect(); - - // Sort A -> Z, using the names of example files - example_names.sort_unstable(); - - let example_names = example_names.join("\n"); + let example_names: String = example_files + .keys() + .map(|k| k.as_ref()) + .collect::>() + .join("\n"); // I was previously testing with the `echo` command -- i.e. the // equivalent of `echo "one\ntwo\nthree" | fzf` -- however this is @@ -102,20 +100,25 @@ mod inner_impl { let root_ref = &dir.root_path; // Save info on the example we're running, so we can `--replay` it if needed - match examples.first() { + match examples_to_run.first() { Some(name) if !args.replay => { save_last_replay(name, example_args_ref)?; } _ => {} }; - for example in examples { - let name = example.as_ref(); - let req_features: Option<&String> = name_to_required_features.get(name); + for example_name in examples_to_run { + let name = example_name.as_ref(); + let example = example_files.get(name).unwrap(); // Run the Cargo example script - args.cargo - .run_example(root_ref, name, example_args_ref, req_features)?; + args.cargo.run_example( + &example.path_type, + root_ref, + name, + example_args_ref, + &example.required_features, + )?; } Ok(()) @@ -127,7 +130,7 @@ mod inner_impl { use super::*; use std::borrow::Cow; - use std::collections::{HashMap, HashSet}; + use std::collections::BTreeMap; use std::io::Write; use std::sync::Arc; @@ -135,26 +138,20 @@ mod inner_impl { //noinspection DuplicatedCode pub(crate) fn process_input_inner( - example_files: HashSet, - dir: Paths, + example_files: BTreeMap, ExampleFile>, + dir: &Paths, args: Args, - name_to_required_features: HashMap, ) -> Result<()> { let script_args = args.args; let selected_items: Vec>; let mut cfg: ReplayConfig = Default::default(); - let examples = if args.replay { + let examples_to_run = if args.replay { cfg = get_last_replay()?; vec![Cow::Owned(cfg.last_run.name)] } else if let Some(example) = args.name { vec![Cow::Owned(example)] } else { - let mut example_files: Vec<_> = Vec::from_iter(example_files); - - // Sort A -> Z, using the names of example files - example_files.sort_unstable(); - let options = SkimOptionsBuilder::default() // .height(Some("50%")) .preview_window(Some("right:70%")) @@ -165,10 +162,10 @@ mod inner_impl { let (tx_item, rx_item): (SkimItemSender, SkimItemReceiver) = unbounded(); - for ex_file in example_files.into_iter() { + for example in example_files.values() { let _ = tx_item.send(Arc::new(ExampleFileItem { - file_stem: ex_file.name, - file_path: ex_file.path, + file_stem: example.name.clone(), + file_path: example.path.clone(), })); } drop(tx_item); // so that skim could know when to stop waiting for more items. @@ -225,20 +222,25 @@ mod inner_impl { let root_ref = &dir.root_path; // Save info on the example we're running, so we can `--replay` it if needed - match examples.first() { + match examples_to_run.first() { Some(name) if !args.replay => { save_last_replay(name, example_args_ref)?; } _ => {} }; - for example in examples { - let name = example.as_ref(); - let req_features: Option<&String> = name_to_required_features.get(name); + for example_name in examples_to_run { + let name = example_name.as_ref(); + let example = example_files.get(name).unwrap(); // Run the Cargo example script - args.cargo - .run_example(root_ref, name, example_args_ref, req_features)?; + args.cargo.run_example( + &example.path_type, + root_ref, + name, + example_args_ref, + &example.required_features, + )?; } Ok(())