diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d82ce72..ba4c73b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -60,8 +60,10 @@ jobs: run: | cargo test --target ${{ matrix.target }} cargo test --all-features --target ${{ matrix.target }} + echo "foo/bar" | cargo run -q --example "list" --target ${{ matrix.target }} -- --list - - name: Tests (Release) run: | cargo test --release --target ${{ matrix.target }} cargo test --release --all-features --target ${{ matrix.target }} + echo "foo/bar" | cargo run --release -q --example "list" --target ${{ matrix.target }} -- --list - diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fb1878..1ea4c70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ +## [0.7.0](https://github.com/Blobfolio/argyle/releases/tag/v0.7.0) - 2024-01-20 + +### Breaking + +* Bump MSRV to `1.70` +* `Argue::with_list` will now read lines from STDIN when the path is given as `-` + +### New + +* `Argue::with_trailing_args` + +### Changed + +* `Argue::with_list` now buffers file reads (instead of reading everything in one go) + + + ## [0.6.8](https://github.com/Blobfolio/argyle/releases/tag/v0.6.8) - 2023-06-01 ### Changed diff --git a/CREDITS.md b/CREDITS.md index 566a6f9..c8ff0f2 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,6 +1,6 @@ # Project Dependencies Package: argyle - Version: 0.6.8 - Generated: 2023-06-01 20:08:59 UTC + Version: 0.7.0 + Generated: 2024-01-21 05:07:09 UTC This package has no dependencies. diff --git a/Cargo.toml b/Cargo.toml index a2f786f..380190c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "argyle" -version = "0.6.8" +version = "0.7.0" authors = ["Blobfolio, LLC. "] edition = "2021" -rust-version = "1.62" +rust-version = "1.70" description = "A lightweight, agnostic CLI argument parser." license = "WTFPL" repository = "https://github.com/Blobfolio/argyle" diff --git a/README.md b/README.md index 1c0d964..31eb688 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Add `argyle` to your `dependencies` in `Cargo.toml`, like: ``` [dependencies] -argyle = "0.6.*" +argyle = "0.7.*" ``` @@ -80,7 +80,7 @@ fn _main() -> Result<(), ArgyleError> { See also: [CREDITS.md](CREDITS.md) -Copyright © 2023 [Blobfolio, LLC](https://blobfolio.com) <hello@blobfolio.com> +Copyright © 2024 [Blobfolio, LLC](https://blobfolio.com) <hello@blobfolio.com> This work is free. You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2. diff --git a/examples/list.rs b/examples/list.rs new file mode 100644 index 0000000..68cd747 --- /dev/null +++ b/examples/list.rs @@ -0,0 +1,31 @@ +/*! +# Argyle: Argue + +This example prints the trailing arguments, if any, including those from +-l/--list. +*/ + +fn main() { + let args = argyle::Argue::new(argyle::FLAG_REQUIRED); + match args { + Ok(mut a) => { + a = a.with_list(); + println!("\x1b[2mArguments:\x1b[0m"); + let mut any = false; + for v in a.args_os() { + any = true; + println!(" {v:?}"); + } + if ! any { + println!(" \x1b[91mNo Arguments Passed\x1b[0m"); + std::process::exit(1); + } + + println!(""); + }, + Err(e) => { + println!("\x1b[1;91mError:\x1b[0m {e}"); + std::process::exit(1); + }, + } +} diff --git a/justfile b/justfile index 5753501..a13427b 100644 --- a/justfile +++ b/justfile @@ -74,7 +74,7 @@ bench BENCH="": # Build and Run Example. -@demo +ARGS: +@debug +ARGS: clear cargo run \ -q \ @@ -86,6 +86,18 @@ bench BENCH="": -- {{ ARGS }} +# Build and Run Example. +@debug-args: + clear + find . -name '*.rs' -type f -exec realpath {} + | cargo run \ + -q \ + --release \ + --example "list" \ + --target x86_64-unknown-linux-gnu \ + --target-dir "{{ cargo_dir }}" \ + -- -l - "foo/bar" + + # Build Docs. @doc: # Make sure nightly is installed; this version generates better docs. @@ -165,6 +177,7 @@ version: # Set the release version! just _version "{{ justfile_directory() }}" "$_ver2" + just credits # Set version for real. diff --git a/src/argue.rs b/src/argue.rs index 7c8d68c..7db6644 100644 --- a/src/argue.rs +++ b/src/argue.rs @@ -15,6 +15,12 @@ use std::{ OsStr, OsString, }, + fs::File, + io::{ + BufRead, + BufReader, + IsTerminal, + }, ops::{ BitOr, Deref, @@ -400,21 +406,18 @@ impl Argue { } #[must_use] - /// # Add Arguments From a Text File. - /// - /// When chained to `new()`, if either "-l" or "--list" options are found, - /// the subsequent value (if any) is read as a text file, and each non- - /// empty line within is appended to the set as additional arguments, - /// exactly as if they were provided directly. + /// # With "Trailing" Arguments From a Text File or STDIN. /// - /// No judgments are passed on the contents of the file. If a line has - /// length, it is appended. + /// Read lines from the text file — or STDIN if "-" — specified by the + /// built-in `-l`/`--list` option (if present), appending them to the set + /// as "trailing" arguments. (One argument per line.) /// - /// Note: if using this approach to seed a command with file paths, make - /// sure those paths are absolute as their relativity will likely be lost - /// in translation. + /// These arguments, if any, along with anything the user included in the + /// actual command, will then be accessible the usual way, via methods like + /// [`Argue::args`], etc. /// - /// This method always transparently returns `self`. + /// Note that the input must be valid UTF-8. Its lines will be trimmed and + /// checked for length before inclusion, but won't otherwise be altered. /// /// ## Examples /// @@ -422,15 +425,55 @@ impl Argue { /// use argyle::Argue; /// /// let mut args = Argue::new(0).unwrap().with_list(); + /// for arg in args.args() { + /// // Do something… + /// } /// ``` - pub fn with_list(mut self) -> Self { - if let Some(raw) = self.option2_os(b"-l", b"--list").and_then(|p| std::fs::read_to_string(p).ok()) { - for line in raw.lines() { - let bytes = line.trim().as_bytes(); - if ! bytes.is_empty() { - self.args.push(bytes.to_vec()); + pub fn with_list(self) -> Self { + if let Some(p) = self.option2_os(b"-l", b"--list") { + // STDIN. + if p == "-" { + // But only if the stream appears to be redirected… + let stdin = std::io::stdin(); + if ! stdin.is_terminal() { + return self.with_trailing_args(stdin.lines().flatten()); } } + // Text file. + else if let Ok(raw) = File::open(p).map(BufReader::new) { + return self.with_trailing_args(raw.lines().flatten()); + } + } + + self + } + + #[must_use] + /// # With "Trailing" Arguments. + /// + /// Append arbitrary strings to the set as "trailing" arguments, making + /// them — along with anything the user included in the actual command — + /// available via methods like [`Argue::args`], etc. + /// + /// Note that arguments are trimmed and checked for length before being + /// added, but are otherwise passed through as-are. + /// + /// ## Examples + /// + /// ```no_run + /// use argyle::Argue; + /// + /// let mut args = Argue::new(0) + /// .unwrap() + /// .with_trailing_args(&["apples", "bananas", "carrots"]); + /// ``` + pub fn with_trailing_args(mut self, args: I) -> Self + where B: AsRef, I: IntoIterator { + for arg in args { + let bytes = arg.as_ref().trim().as_bytes(); + if ! bytes.is_empty() { + self.args.push(bytes.to_vec()); + } } self @@ -1284,4 +1327,22 @@ mod tests { assert_eq!(args.option_by_prefix(b"--foo"), None); assert_eq!(args.option_by_prefix(b"--key-1"), None); // Full matches suppressed. } + + #[test] + fn t_with_trailing_args() { + let base: Vec<&[u8]> = vec![ b"foo" ]; + + // As is. + let args = base.iter().cloned().collect::(); + assert_eq!(args.args(), base); + + // With extra stuff. + let args = base.iter().cloned().collect::() + .with_trailing_args(&[ + "bar", + " baz ", // Should be trimmed. + " ", // Should be ignored. + ]); + assert_eq!(args.args(), &[b"foo", b"bar", b"baz"]); + } } diff --git a/src/lib.rs b/src/lib.rs index b0bc378..e4c385d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,17 +19,6 @@ For simple applications, this agnostic approach can significantly reduce the ove -## Installation - -Add `argyle` to your `dependencies` in `Cargo.toml`, like: - -```ignore -[dependencies] -argyle = "0.6.*" -``` - - - ## Example A general setup might look something like the following. Refer to the documentation for [`Argue`] for more information, caveats, etc.