diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29a2eae..ee5dafd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - - run: cargo test --features complete --workspace + - run: cargo test --workspace rustfmt: name: Rustfmt @@ -48,7 +48,7 @@ jobs: components: clippy - uses: Swatinem/rust-cache@v2 - name: Clippy check - run: cargo clippy --all-targets --features complete --workspace -- -D warnings + run: cargo clippy --all-targets --workspace -- -D warnings docs: name: Docs diff --git a/Cargo.toml b/Cargo.toml index e9c6517..89aedf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,14 +10,16 @@ repository = "https://github.com/uutils/uutils-args" readme = "README.md" [dependencies] -uutils-args-derive = { version = "0.1.0", path = "derive" } -uutils-args-complete = { version = "0.1.0", path = "complete", optional = true } -strsim = "0.11.1" lexopt = "0.3.0" +roff = "0.2.1" +strsim = "0.11.1" +uutils-args-derive = { version = "0.1.0", path = "derive" } + +[dev-dependencies] +trybuild = "1.0.104" [features] -parse-is-complete = ["complete"] -complete = ["uutils-args-complete"] +parse-is-complete = [] [workspace] -members = ["derive", "complete"] +members = ["derive"] diff --git a/Justfile b/Justfile index 7790af0..c7cea4d 100644 --- a/Justfile +++ b/Justfile @@ -1,5 +1,5 @@ check: cargo fmt --all - cargo test --features complete - cargo clippy --all-targets --features complete --workspace -- -D warnings + cargo test + cargo clippy --all-targets --workspace -- -D warnings cargo doc diff --git a/complete/Cargo.toml b/complete/Cargo.toml deleted file mode 100644 index c9bae20..0000000 --- a/complete/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "uutils-args-complete" -version = "0.1.0" -edition = "2024" -authors = ["Terts Diepraam"] -license = "MIT" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -roff = "0.2.1" diff --git a/complete/LICENSE b/complete/LICENSE deleted file mode 120000 index ea5b606..0000000 --- a/complete/LICENSE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE \ No newline at end of file diff --git a/derive/src/argument.rs b/derive/src/argument.rs index 94b84eb..7a09b2b 100644 --- a/derive/src/argument.rs +++ b/derive/src/argument.rs @@ -40,7 +40,8 @@ pub fn parse_arguments_attr(attrs: &[Attribute]) -> ArgumentsAttr { pub fn parse_argument(v: Variant) -> Vec { let ident = v.ident; - let attributes = get_arg_attributes(&v.attrs).unwrap(); + let attributes = get_arg_attributes(&v.attrs) + .expect("can't parse arg attributes, expected one or more strings"); // Return early because we don't need to check the fields if it's not used. if attributes.is_empty() { diff --git a/derive/src/attributes.rs b/derive/src/attributes.rs index da582f8..8604b1e 100644 --- a/derive/src/attributes.rs +++ b/derive/src/attributes.rs @@ -69,6 +69,7 @@ impl ArgumentsAttr { } } +#[allow(clippy::large_enum_variant)] pub enum ArgAttr { Option(OptionAttr), Free(FreeAttr), diff --git a/derive/src/complete.rs b/derive/src/complete.rs index 10b9691..9e919d1 100644 --- a/derive/src/complete.rs +++ b/derive/src/complete.rs @@ -49,11 +49,15 @@ pub fn complete(args: &[Argument], file: &Option) -> TokenStream { .map(|Flag { flag, value }| { let flag = flag.to_string(); let value = match value { - Value::No => quote!(::uutils_args_complete::Value::No), - Value::Optional(name) => quote!(::uutils_args_complete::Value::Optional(#name)), - Value::Required(name) => quote!(::uutils_args_complete::Value::Required(#name)), + Value::No => quote!(::uutils_args::complete::Value::No), + Value::Optional(name) => { + quote!(::uutils_args::complete::Value::Optional(#name)) + } + Value::Required(name) => { + quote!(::uutils_args::complete::Value::Required(#name)) + } }; - quote!(::uutils_args_complete::Flag { + quote!(::uutils_args::complete::Flag { flag: #flag, value: #value }) @@ -64,11 +68,15 @@ pub fn complete(args: &[Argument], file: &Option) -> TokenStream { .iter() .map(|Flag { flag, value }| { let value = match value { - Value::No => quote!(::uutils_args_complete::Value::No), - Value::Optional(name) => quote!(::uutils_args_complete::Value::Optional(#name)), - Value::Required(name) => quote!(::uutils_args_complete::Value::Required(#name)), + Value::No => quote!(::uutils_args::complete::Value::No), + Value::Optional(name) => { + quote!(::uutils_args::complete::Value::Optional(#name)) + } + Value::Required(name) => { + quote!(::uutils_args::complete::Value::Required(#name)) + } }; - quote!(::uutils_args_complete::Flag { + quote!(::uutils_args::complete::Flag { flag: #flag, value: #value }) @@ -81,7 +89,7 @@ pub fn complete(args: &[Argument], file: &Option) -> TokenStream { }; arg_specs.push(quote!( - ::uutils_args_complete::Arg { + ::uutils_args::complete::Arg { short: vec![#(#short),*], long: vec![#(#long),*], help: #help, @@ -90,7 +98,7 @@ pub fn complete(args: &[Argument], file: &Option) -> TokenStream { )) } - quote!(::uutils_args_complete::Command { + quote!(::uutils_args::complete::Command { name: option_env!("CARGO_BIN_NAME").unwrap_or(env!("CARGO_PKG_NAME")), summary: #summary, after_options: #after_options, diff --git a/derive/src/flags.rs b/derive/src/flags.rs index 3d3db27..da7497e 100644 --- a/derive/src/flags.rs +++ b/derive/src/flags.rs @@ -60,8 +60,9 @@ impl Flags { } else if sep == '[' { let optional = val .strip_prefix('=') - .and_then(|s| s.strip_suffix(']')) - .unwrap(); + .expect("expected '=' after '[' in flag pattern") + .strip_suffix(']') + .expect("expected final ']' in flag pattern"); assert!( optional .chars() @@ -74,8 +75,6 @@ impl Flags { self.long.push(Flag { flag: f, value }); } else if let Some(s) = flag.strip_prefix('-') { - assert!(!s.is_empty()); - // There are three possible patterns: // -f // -f value @@ -83,7 +82,9 @@ impl Flags { // First we trim up to the = or [ let mut chars = s.chars(); - let f = chars.next().unwrap(); + let f = chars + .next() + .expect("flag name must be non-empty (cannot be just '-')"); let val: String = chars.collect(); // Now check the cases: diff --git a/derive/src/help.rs b/derive/src/help.rs index 77af149..2c99c3a 100644 --- a/derive/src/help.rs +++ b/derive/src/help.rs @@ -100,12 +100,14 @@ pub fn help_string( pub fn read_help_file(file: &str) -> (String, String, String) { let path = Path::new(file); - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let manifest_dir = + std::env::var("CARGO_MANIFEST_DIR").expect("can only run in paths that are valid UTF-8"); let mut location = PathBuf::from(manifest_dir); location.push(path); let mut contents = String::new(); - let mut f = std::fs::File::open(location).unwrap(); - f.read_to_string(&mut contents).unwrap(); + let mut f = std::fs::File::open(location).expect("cannot open help-string file"); + f.read_to_string(&mut contents) + .expect("cannot read from help-string file"); ( parse_about(&contents), diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 5f6d9d7..73a08a2 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -117,8 +117,7 @@ pub fn arguments(input: TokenStream) -> TokenStream { #version_string } - #[cfg(feature = "complete")] - fn complete() -> ::uutils_args_complete::Command<'static> { + fn complete() -> ::uutils_args::complete::Command<'static> { use ::uutils_args::Value; #complete_command } @@ -152,7 +151,8 @@ pub fn value(input: TokenStream) -> TokenStream { continue; } - let ValueAttr { keys, value } = ValueAttr::parse(&attr).unwrap(); + let ValueAttr { keys, value } = + ValueAttr::parse(&attr).expect("expected comma-separated list of string literals"); let keys = if keys.is_empty() { vec![variant_name.to_lowercase()] @@ -212,10 +212,9 @@ pub fn value(input: TokenStream) -> TokenStream { }) } - #[cfg(feature = "complete")] - fn value_hint() -> ::uutils_args_complete::ValueHint { + fn value_hint() -> ::uutils_args::complete::ValueHint { let keys: [&str; #keys_len] = [#(#all_keys),*]; - ::uutils_args_complete::ValueHint::Strings( + ::uutils_args::complete::ValueHint::Strings( keys .into_iter() .map(ToString::to_string) diff --git a/docs/guide/completions.md b/docs/guide/completions.md index d89d3dd..e7d04c1 100644 --- a/docs/guide/completions.md +++ b/docs/guide/completions.md @@ -39,7 +39,7 @@ The `[shell]` value here can be `fish`, `zsh`, `nu`, `sh`, `bash`, `csh`, `elvis Additionally, the values `man` or `md` can be passed to generate man pages and markdown documentation (for `mdbook`). -If you do not want to hijack the [`Options::parse`](crate::Options::parse) function, you can instead enable the `complete` feature flag. This makes the `Options::complete` function available in addition to the [`Options::parse`](crate::Options::parse) function to generate a `String` with the completion. +If you do not want to hijack the [`Options::parse`](crate::Options::parse) function, you can instead use the `Options::complete` function available in addition to the [`Options::parse`](crate::Options::parse) function to generate a `String` with the completion.
diff --git a/complete/src/bash.rs b/src/complete/bash.rs similarity index 97% rename from complete/src/bash.rs rename to src/complete/bash.rs index e72afad..d69eb8b 100644 --- a/complete/src/bash.rs +++ b/src/complete/bash.rs @@ -1,7 +1,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::{Command, Flag}; +use crate::complete::{Command, Flag}; /// Create completion script for `bash` /// @@ -35,7 +35,7 @@ pub fn render(c: &Command) -> String { #[cfg(test)] mod test { use super::render; - use crate::{Arg, Command, Flag, Value}; + use crate::complete::{Arg, Command, Flag, Value}; #[test] fn simple() { diff --git a/complete/src/fish.rs b/src/complete/fish.rs similarity index 97% rename from complete/src/fish.rs rename to src/complete/fish.rs index d6f0f66..d718e81 100644 --- a/complete/src/fish.rs +++ b/src/complete/fish.rs @@ -1,7 +1,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::{Command, Flag, ValueHint}; +use crate::complete::{Command, Flag, ValueHint}; /// Create completion script for `fish` /// @@ -45,7 +45,7 @@ fn render_value_hint(value: &ValueHint) -> String { #[cfg(test)] mod test { use super::render; - use crate::{Arg, Command, Flag, Value, ValueHint}; + use crate::complete::{Arg, Command, Flag, Value, ValueHint}; #[test] fn short() { diff --git a/complete/src/man.rs b/src/complete/man.rs similarity index 97% rename from complete/src/man.rs rename to src/complete/man.rs index 0e0a38c..d4befed 100644 --- a/complete/src/man.rs +++ b/src/complete/man.rs @@ -1,7 +1,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::{Command, Flag, Value}; +use crate::complete::{Command, Flag, Value}; use roff::{Roff, bold, italic, roman}; pub fn render(c: &Command) -> String { diff --git a/complete/src/md.rs b/src/complete/md.rs similarity index 97% rename from complete/src/md.rs rename to src/complete/md.rs index 1c2fa1b..883c5b5 100644 --- a/complete/src/md.rs +++ b/src/complete/md.rs @@ -1,7 +1,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::{Command, Flag, Value}; +use crate::complete::{Command, Flag, Value}; /// Render command to a markdown file for mdbook pub fn render(c: &Command) -> String { diff --git a/complete/src/lib.rs b/src/complete/mod.rs similarity index 100% rename from complete/src/lib.rs rename to src/complete/mod.rs diff --git a/complete/src/nu.rs b/src/complete/nu.rs similarity index 97% rename from complete/src/nu.rs rename to src/complete/nu.rs index fb9016c..e7f0271 100644 --- a/complete/src/nu.rs +++ b/src/complete/nu.rs @@ -1,7 +1,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::{Arg, Command, Flag, Value, ValueHint}; +use crate::complete::{Arg, Command, Flag, Value, ValueHint}; use std::fmt::Write; /// Create completion script for `nushell` diff --git a/complete/src/zsh.rs b/src/complete/zsh.rs similarity index 98% rename from complete/src/zsh.rs rename to src/complete/zsh.rs index 2b709f8..68f9d53 100644 --- a/complete/src/zsh.rs +++ b/src/complete/zsh.rs @@ -1,7 +1,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::{Arg, Command, Flag, Value, ValueHint}; +use crate::complete::{Arg, Command, Flag, Value, ValueHint}; /// Create completion script for `zsh` pub fn render(c: &Command) -> String { diff --git a/src/lib.rs b/src/lib.rs index e7b7058..5a0be4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ //! #![doc = include_str!("../README.md")] +pub mod complete; mod error; pub mod internal; pub mod positional; @@ -103,8 +104,7 @@ pub trait Arguments: Sized { Ok(()) } - #[cfg(feature = "complete")] - fn complete() -> uutils_args_complete::Command<'static>; + fn complete() -> complete::Command<'static>; } /// An iterator over arguments. @@ -197,9 +197,8 @@ pub trait Options: Sized { } } - #[cfg(feature = "complete")] fn complete(shell: &str) -> String { - uutils_args_complete::render(&Arg::complete(), shell) + complete::render(&Arg::complete(), shell) } } diff --git a/src/value.rs b/src/value.rs index 54cfbba..c3efc1d 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,13 +1,12 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use crate::complete::ValueHint; use crate::error::{Error, ErrorKind}; use std::{ ffi::{OsStr, OsString}, path::PathBuf, }; -#[cfg(feature = "complete")] -use uutils_args_complete::ValueHint; pub type ValueResult = Result>; @@ -54,7 +53,6 @@ impl std::fmt::Display for ValueError { pub trait Value: Sized { fn from_value(value: &OsStr) -> ValueResult; - #[cfg(feature = "complete")] fn value_hint() -> ValueHint { ValueHint::Unknown } @@ -71,7 +69,6 @@ impl Value for PathBuf { Ok(PathBuf::from(value)) } - #[cfg(feature = "complete")] fn value_hint() -> ValueHint { ValueHint::AnyPath } @@ -98,8 +95,7 @@ where Ok(Some(T::from_value(value)?)) } - #[cfg(feature = "complete")] - fn value_hint() -> uutils_args_complete::ValueHint { + fn value_hint() -> ValueHint { T::value_hint() } } diff --git a/tests/coreutils.rs b/tests/coreutils.rs index eca5376..94ac4fc 100644 --- a/tests/coreutils.rs +++ b/tests/coreutils.rs @@ -22,6 +22,9 @@ mod date; #[path = "coreutils/dd.rs"] mod dd; +#[path = "coreutils/du.rs"] +mod du; + #[path = "coreutils/echo.rs"] mod echo; diff --git a/tests/coreutils/du.rs b/tests/coreutils/du.rs new file mode 100644 index 0000000..d076a82 --- /dev/null +++ b/tests/coreutils/du.rs @@ -0,0 +1,152 @@ +use std::ffi::OsString; +use uutils_args::{Arguments, Options}; + +#[derive(Arguments)] +enum Arg { + #[arg("--apparent-size")] + ApparentSize, + + #[arg("-B[SIZE]", "--block-size[=SIZE]")] + BlockSize(OsString), + + #[arg("-b", "--bytes")] + Bytes, + + #[arg("-k")] + KibiBytes, + + #[arg("-m")] + MibiBytes, + // Note that --si and -h only affect the *output formatting*, + // and not the size determination itself. +} + +#[derive(Debug, Default, PartialEq, Eq)] +struct Settings { + apparent_size: bool, + block_size_str: Option, +} + +impl Options for Settings { + fn apply(&mut self, arg: Arg) -> Result<(), uutils_args::Error> { + match arg { + Arg::ApparentSize => self.apparent_size = true, + Arg::BlockSize(os_str) => self.block_size_str = Some(os_str), + Arg::Bytes => { + self.apparent_size = true; + self.block_size_str = Some("1".into()); + } + Arg::KibiBytes => self.block_size_str = Some("K".into()), + Arg::MibiBytes => self.block_size_str = Some("M".into()), + } + Ok(()) + } +} + +#[test] +fn noarg() { + let (settings, operands) = Settings::default().parse(["du"]).unwrap(); + assert_eq!(operands, Vec::::new()); + assert_eq!( + settings, + Settings { + apparent_size: false, + block_size_str: None, + } + ); +} + +#[test] +fn bytes() { + let (settings, operands) = Settings::default().parse(["du", "-b"]).unwrap(); + assert_eq!(operands, Vec::::new()); + assert_eq!( + settings, + Settings { + apparent_size: true, + block_size_str: Some("1".into()), + } + ); +} + +#[test] +fn kibibytes() { + let (settings, operands) = Settings::default().parse(["du", "-k"]).unwrap(); + assert_eq!(operands, Vec::::new()); + assert_eq!( + settings, + Settings { + apparent_size: false, + block_size_str: Some("K".into()), + } + ); +} + +#[test] +fn bytes_kibibytes() { + let (settings, operands) = Settings::default().parse(["du", "-bk"]).unwrap(); + assert_eq!(operands, Vec::::new()); + assert_eq!( + settings, + Settings { + apparent_size: true, + block_size_str: Some("K".into()), + } + ); +} + +#[test] +fn kibibytes_bytes() { + let (settings, operands) = Settings::default().parse(["du", "-kb"]).unwrap(); + assert_eq!(operands, Vec::::new()); + assert_eq!( + settings, + Settings { + apparent_size: true, + block_size_str: Some("1".into()), + } + ); +} + +#[test] +fn apparent_size() { + let (settings, operands) = Settings::default() + .parse(["du", "--apparent-size"]) + .unwrap(); + assert_eq!(operands, Vec::::new()); + assert_eq!( + settings, + Settings { + apparent_size: true, + block_size_str: None, + } + ); +} + +#[test] +fn mibibytes() { + let (settings, operands) = Settings::default().parse(["du", "-m"]).unwrap(); + assert_eq!(operands, Vec::::new()); + assert_eq!( + settings, + Settings { + apparent_size: false, + block_size_str: Some("M".into()), + } + ); +} + +#[test] +fn all() { + let (settings, operands) = Settings::default() + .parse(["du", "--apparent-size", "-bkm", "-B123"]) + .unwrap(); + assert_eq!(operands, Vec::::new()); + assert_eq!( + settings, + Settings { + apparent_size: true, + block_size_str: Some("123".into()), + } + ); +} diff --git a/tests/derive.rs b/tests/derive.rs new file mode 100644 index 0000000..f3083d3 --- /dev/null +++ b/tests/derive.rs @@ -0,0 +1,41 @@ +#[test] +fn derive_error_messages_common() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/derive/arg_bare_keyword.rs"); + t.compile_fail("tests/derive/arg_just_minus.rs"); + t.compile_fail("tests/derive/arg_key_value.rs"); + t.compile_fail("tests/derive/arg_missing_closing_bracket.rs"); + t.compile_fail("tests/derive/arg_missing_equals.rs"); + t.compile_fail("tests/derive/arg_missing_field.rs"); + t.compile_fail("tests/derive/arg_missing_metavar.rs"); + t.compile_fail("tests/derive/arguments_file_nonexistent.rs"); + t.compile_fail("tests/derive/value_bare_keyword.rs"); + t.pass("tests/derive/arg_duplicate_other.rs"); // FIXME: Should fail! + t.pass("tests/derive/arg_duplicate_within.rs"); // FIXME: Should fail! +} + +#[cfg(unix)] +#[test] +fn derive_error_messages_unix() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/derive/arguments_file_isdir.rs"); // Needs the directory "/" +} + +#[cfg(target_os = "linux")] +#[test] +fn derive_error_messages_linux_writeonly_file() { + use std::fs::metadata; + use std::os::unix::fs::PermissionsExt; + + // First, verify that /proc/self/clear_refs exists and is write-only: + // https://man.archlinux.org/man/proc_pid_clear_refs.5.en + let metadata = metadata("/proc/self/clear_refs").expect("should be in Linux 2.6.22"); + eprintln!("is_file={}", metadata.is_file()); + eprintln!("permissions={:?}", metadata.permissions()); + assert_eq!(0o100200, metadata.permissions().mode()); + + // The file exists, as it should. Now we can run the test, using this + // special write-only file, without having to worry about clean-up: + let t = trybuild::TestCases::new(); + t.compile_fail("tests/derive/arguments_file_writeonly.rs"); +} diff --git a/tests/derive/arg_bare_keyword.rs b/tests/derive/arg_bare_keyword.rs new file mode 100644 index 0000000..96b2ef8 --- /dev/null +++ b/tests/derive/arg_bare_keyword.rs @@ -0,0 +1,19 @@ +use uutils_args::{Arguments, Options}; + +#[derive(Arguments)] +enum Arg { + #[arg(banana)] // Oops! + Something, +} + +struct Settings {} + +impl Options for Settings { + fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> { + Ok(()) + } +} + +fn main() { + Settings {}.parse(std::env::args_os()).unwrap(); +} diff --git a/tests/derive/arg_bare_keyword.stderr b/tests/derive/arg_bare_keyword.stderr new file mode 100644 index 0000000..10a091c --- /dev/null +++ b/tests/derive/arg_bare_keyword.stderr @@ -0,0 +1,16 @@ +error[E0425]: cannot find function `banana` in this scope + --> tests/derive/arg_bare_keyword.rs:5:11 + | +5 | #[arg(banana)] // Oops! + | ^^^^^^ not found in this scope + +error[E0618]: expected function, found `Arg` + --> tests/derive/arg_bare_keyword.rs:3:10 + | +3 | #[derive(Arguments)] + | ^^^^^^^^^ call expression requires function +... +6 | Something, + | --------- `Arg::Something` defined here + | + = note: this error originates in the derive macro `Arguments` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/derive/arg_duplicate_other.rs b/tests/derive/arg_duplicate_other.rs new file mode 100644 index 0000000..f36607f --- /dev/null +++ b/tests/derive/arg_duplicate_other.rs @@ -0,0 +1,21 @@ +use uutils_args::{Arguments, Options}; + +#[derive(Arguments)] +enum Arg { + #[arg("--foo")] + Something, + #[arg("--foo")] // Oops! + Banana, +} + +struct Settings {} + +impl Options for Settings { + fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> { + Ok(()) + } +} + +fn main() { + Settings {}.parse(std::env::args_os()).unwrap(); +} diff --git a/tests/derive/arg_duplicate_within.rs b/tests/derive/arg_duplicate_within.rs new file mode 100644 index 0000000..dd1ef30 --- /dev/null +++ b/tests/derive/arg_duplicate_within.rs @@ -0,0 +1,19 @@ +use uutils_args::{Arguments, Options}; + +#[derive(Arguments)] +enum Arg { + #[arg("--foo", "--foo")] // Oops! + Something, +} + +struct Settings {} + +impl Options for Settings { + fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> { + Ok(()) + } +} + +fn main() { + Settings {}.parse(std::env::args_os()).unwrap(); +} diff --git a/tests/derive/arg_just_minus.rs b/tests/derive/arg_just_minus.rs new file mode 100644 index 0000000..b2d0e45 --- /dev/null +++ b/tests/derive/arg_just_minus.rs @@ -0,0 +1,19 @@ +use uutils_args::{Arguments, Options}; + +#[derive(Arguments)] +enum Arg { + #[arg("-")] // Oops! + Something, +} + +struct Settings {} + +impl Options for Settings { + fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> { + Ok(()) + } +} + +fn main() { + Settings {}.parse(std::env::args_os()).unwrap(); +} diff --git a/tests/derive/arg_just_minus.stderr b/tests/derive/arg_just_minus.stderr new file mode 100644 index 0000000..7955bfa --- /dev/null +++ b/tests/derive/arg_just_minus.stderr @@ -0,0 +1,34 @@ +error: proc-macro derive panicked + --> tests/derive/arg_just_minus.rs:3:10 + | +3 | #[derive(Arguments)] + | ^^^^^^^^^ + | + = help: message: flag name must be non-empty (cannot be just '-') + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arg_just_minus.rs:11:6 + | +11 | impl Options for Settings { + | ^^^^^^^^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `Options` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options` + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arg_just_minus.rs:18:17 + | +18 | Settings {}.parse(std::env::args_os()).unwrap(); + | ^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `uutils_args::Options::parse` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options::parse` +... + | fn parse(mut self, args: I) -> Result<(Self, Vec), Error> + | ----- required by a bound in this associated function diff --git a/tests/derive/arg_key_value.rs b/tests/derive/arg_key_value.rs new file mode 100644 index 0000000..a525a59 --- /dev/null +++ b/tests/derive/arg_key_value.rs @@ -0,0 +1,19 @@ +use uutils_args::{Arguments, Options}; + +#[derive(Arguments)] +enum Arg { + #[arg(key = "name")] // Oops! + Something, +} + +struct Settings {} + +impl Options for Settings { + fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> { + Ok(()) + } +} + +fn main() { + Settings {}.parse(std::env::args_os()).unwrap(); +} diff --git a/tests/derive/arg_key_value.stderr b/tests/derive/arg_key_value.stderr new file mode 100644 index 0000000..3a67462 --- /dev/null +++ b/tests/derive/arg_key_value.stderr @@ -0,0 +1,34 @@ +error: proc-macro derive panicked + --> tests/derive/arg_key_value.rs:3:10 + | +3 | #[derive(Arguments)] + | ^^^^^^^^^ + | + = help: message: can't parse arg attributes, expected one or more strings: Error("expected `,`") + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arg_key_value.rs:11:6 + | +11 | impl Options for Settings { + | ^^^^^^^^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `Options` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options` + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arg_key_value.rs:18:17 + | +18 | Settings {}.parse(std::env::args_os()).unwrap(); + | ^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `uutils_args::Options::parse` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options::parse` +... + | fn parse(mut self, args: I) -> Result<(Self, Vec), Error> + | ----- required by a bound in this associated function diff --git a/tests/derive/arg_missing_closing_bracket.rs b/tests/derive/arg_missing_closing_bracket.rs new file mode 100644 index 0000000..3edfba1 --- /dev/null +++ b/tests/derive/arg_missing_closing_bracket.rs @@ -0,0 +1,19 @@ +use uutils_args::{Arguments, Options}; + +#[derive(Arguments)] +enum Arg { + #[arg("--foo[=FOO")] // Oops! + Something(String), +} + +struct Settings {} + +impl Options for Settings { + fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> { + Ok(()) + } +} + +fn main() { + Settings {}.parse(std::env::args_os()).unwrap(); +} diff --git a/tests/derive/arg_missing_closing_bracket.stderr b/tests/derive/arg_missing_closing_bracket.stderr new file mode 100644 index 0000000..4aec1fd --- /dev/null +++ b/tests/derive/arg_missing_closing_bracket.stderr @@ -0,0 +1,34 @@ +error: proc-macro derive panicked + --> tests/derive/arg_missing_closing_bracket.rs:3:10 + | +3 | #[derive(Arguments)] + | ^^^^^^^^^ + | + = help: message: expected final ']' in flag pattern + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arg_missing_closing_bracket.rs:11:6 + | +11 | impl Options for Settings { + | ^^^^^^^^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `Options` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options` + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arg_missing_closing_bracket.rs:18:17 + | +18 | Settings {}.parse(std::env::args_os()).unwrap(); + | ^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `uutils_args::Options::parse` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options::parse` +... + | fn parse(mut self, args: I) -> Result<(Self, Vec), Error> + | ----- required by a bound in this associated function diff --git a/tests/derive/arg_missing_equals.rs b/tests/derive/arg_missing_equals.rs new file mode 100644 index 0000000..f988a1a --- /dev/null +++ b/tests/derive/arg_missing_equals.rs @@ -0,0 +1,19 @@ +use uutils_args::{Arguments, Options}; + +#[derive(Arguments)] +enum Arg { + #[arg("--foo[FOO]")] // Oops! + Something(String), +} + +struct Settings {} + +impl Options for Settings { + fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> { + Ok(()) + } +} + +fn main() { + Settings {}.parse(std::env::args_os()).unwrap(); +} diff --git a/tests/derive/arg_missing_equals.stderr b/tests/derive/arg_missing_equals.stderr new file mode 100644 index 0000000..e00b141 --- /dev/null +++ b/tests/derive/arg_missing_equals.stderr @@ -0,0 +1,34 @@ +error: proc-macro derive panicked + --> tests/derive/arg_missing_equals.rs:3:10 + | +3 | #[derive(Arguments)] + | ^^^^^^^^^ + | + = help: message: expected '=' after '[' in flag pattern + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arg_missing_equals.rs:11:6 + | +11 | impl Options for Settings { + | ^^^^^^^^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `Options` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options` + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arg_missing_equals.rs:18:17 + | +18 | Settings {}.parse(std::env::args_os()).unwrap(); + | ^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `uutils_args::Options::parse` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options::parse` +... + | fn parse(mut self, args: I) -> Result<(Self, Vec), Error> + | ----- required by a bound in this associated function diff --git a/tests/derive/arg_missing_field.rs b/tests/derive/arg_missing_field.rs new file mode 100644 index 0000000..88418ed --- /dev/null +++ b/tests/derive/arg_missing_field.rs @@ -0,0 +1,19 @@ +use uutils_args::{Arguments, Options}; + +#[derive(Arguments)] +enum Arg { + #[arg("--foo[=FOO]")] + Something, // Oops! +} + +struct Settings {} + +impl Options for Settings { + fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> { + Ok(()) + } +} + +fn main() { + Settings {}.parse(std::env::args_os()).unwrap(); +} diff --git a/tests/derive/arg_missing_field.stderr b/tests/derive/arg_missing_field.stderr new file mode 100644 index 0000000..eff1a85 --- /dev/null +++ b/tests/derive/arg_missing_field.stderr @@ -0,0 +1,34 @@ +error: proc-macro derive panicked + --> tests/derive/arg_missing_field.rs:3:10 + | +3 | #[derive(Arguments)] + | ^^^^^^^^^ + | + = help: message: Option cannot take a value if the variant doesn't have a field + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arg_missing_field.rs:11:6 + | +11 | impl Options for Settings { + | ^^^^^^^^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `Options` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options` + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arg_missing_field.rs:18:17 + | +18 | Settings {}.parse(std::env::args_os()).unwrap(); + | ^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `uutils_args::Options::parse` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options::parse` +... + | fn parse(mut self, args: I) -> Result<(Self, Vec), Error> + | ----- required by a bound in this associated function diff --git a/tests/derive/arg_missing_metavar.rs b/tests/derive/arg_missing_metavar.rs new file mode 100644 index 0000000..cd68af7 --- /dev/null +++ b/tests/derive/arg_missing_metavar.rs @@ -0,0 +1,23 @@ +use uutils_args::{Arguments, Options}; + +struct Complicated { + // Doesn't "impl Default". Oops! +} + +#[derive(Arguments)] +enum Arg { + #[arg("--foo")] + Something(Complicated), +} + +struct Settings {} + +impl Options for Settings { + fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> { + Ok(()) + } +} + +fn main() { + Settings {}.parse(std::env::args_os()).unwrap(); +} diff --git a/tests/derive/arg_missing_metavar.stderr b/tests/derive/arg_missing_metavar.stderr new file mode 100644 index 0000000..993ce06 --- /dev/null +++ b/tests/derive/arg_missing_metavar.stderr @@ -0,0 +1,12 @@ +error[E0277]: the trait bound `Complicated: Default` is not satisfied + --> tests/derive/arg_missing_metavar.rs:7:10 + | +7 | #[derive(Arguments)] + | ^^^^^^^^^ the trait `Default` is not implemented for `Complicated` + | + = note: this error originates in the derive macro `Arguments` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `Complicated` with `#[derive(Default)]` + | +3 + #[derive(Default)] +4 | struct Complicated { + | diff --git a/tests/derive/arguments_file_isdir.rs b/tests/derive/arguments_file_isdir.rs new file mode 100644 index 0000000..8df1342 --- /dev/null +++ b/tests/derive/arguments_file_isdir.rs @@ -0,0 +1,17 @@ +use uutils_args::{Arguments, Options}; + +#[derive(Arguments)] +#[arguments(file = "/")] // Oops! +enum Arg {} + +struct Settings {} + +impl Options for Settings { + fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> { + Ok(()) + } +} + +fn main() { + Settings {}.parse(std::env::args_os()).unwrap(); +} diff --git a/tests/derive/arguments_file_isdir.stderr b/tests/derive/arguments_file_isdir.stderr new file mode 100644 index 0000000..944a429 --- /dev/null +++ b/tests/derive/arguments_file_isdir.stderr @@ -0,0 +1,34 @@ +error: proc-macro derive panicked + --> tests/derive/arguments_file_isdir.rs:3:10 + | +3 | #[derive(Arguments)] + | ^^^^^^^^^ + | + = help: message: cannot read from help-string file: Os { code: 21, kind: IsADirectory, message: "Is a directory" } + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arguments_file_isdir.rs:9:6 + | +9 | impl Options for Settings { + | ^^^^^^^^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `Options` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options` + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arguments_file_isdir.rs:16:17 + | +16 | Settings {}.parse(std::env::args_os()).unwrap(); + | ^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `uutils_args::Options::parse` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options::parse` +... + | fn parse(mut self, args: I) -> Result<(Self, Vec), Error> + | ----- required by a bound in this associated function diff --git a/tests/derive/arguments_file_nonexistent.rs b/tests/derive/arguments_file_nonexistent.rs new file mode 100644 index 0000000..bdabf54 --- /dev/null +++ b/tests/derive/arguments_file_nonexistent.rs @@ -0,0 +1,17 @@ +use uutils_args::{Arguments, Options}; + +#[derive(Arguments)] +#[arguments(file = "nonexistent")] // Oops! +enum Arg {} + +struct Settings {} + +impl Options for Settings { + fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> { + Ok(()) + } +} + +fn main() { + Settings {}.parse(std::env::args_os()).unwrap(); +} diff --git a/tests/derive/arguments_file_nonexistent.stderr b/tests/derive/arguments_file_nonexistent.stderr new file mode 100644 index 0000000..8ea6b22 --- /dev/null +++ b/tests/derive/arguments_file_nonexistent.stderr @@ -0,0 +1,34 @@ +error: proc-macro derive panicked + --> tests/derive/arguments_file_nonexistent.rs:3:10 + | +3 | #[derive(Arguments)] + | ^^^^^^^^^ + | + = help: message: cannot open help-string file: Os { code: 2, kind: NotFound, message: "No such file or directory" } + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arguments_file_nonexistent.rs:9:6 + | +9 | impl Options for Settings { + | ^^^^^^^^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `Options` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options` + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arguments_file_nonexistent.rs:16:17 + | +16 | Settings {}.parse(std::env::args_os()).unwrap(); + | ^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `uutils_args::Options::parse` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options::parse` +... + | fn parse(mut self, args: I) -> Result<(Self, Vec), Error> + | ----- required by a bound in this associated function diff --git a/tests/derive/arguments_file_writeonly.rs b/tests/derive/arguments_file_writeonly.rs new file mode 100644 index 0000000..f598635 --- /dev/null +++ b/tests/derive/arguments_file_writeonly.rs @@ -0,0 +1,17 @@ +use uutils_args::{Arguments, Options}; + +#[derive(Arguments)] +#[arguments(file = "/proc/self/clear_refs")] // Oops! +enum Arg {} + +struct Settings {} + +impl Options for Settings { + fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> { + Ok(()) + } +} + +fn main() { + Settings {}.parse(std::env::args_os()).unwrap(); +} diff --git a/tests/derive/arguments_file_writeonly.stderr b/tests/derive/arguments_file_writeonly.stderr new file mode 100644 index 0000000..403bd7c --- /dev/null +++ b/tests/derive/arguments_file_writeonly.stderr @@ -0,0 +1,34 @@ +error: proc-macro derive panicked + --> tests/derive/arguments_file_writeonly.rs:3:10 + | +3 | #[derive(Arguments)] + | ^^^^^^^^^ + | + = help: message: cannot open help-string file: Os { code: 13, kind: PermissionDenied, message: "Permission denied" } + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arguments_file_writeonly.rs:9:6 + | +9 | impl Options for Settings { + | ^^^^^^^^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `Options` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options` + +error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied + --> tests/derive/arguments_file_writeonly.rs:16:17 + | +16 | Settings {}.parse(std::env::args_os()).unwrap(); + | ^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg` + | +note: required by a bound in `uutils_args::Options::parse` + --> src/lib.rs + | + | pub trait Options: Sized { + | ^^^^^^^^^ required by this bound in `Options::parse` +... + | fn parse(mut self, args: I) -> Result<(Self, Vec), Error> + | ----- required by a bound in this associated function diff --git a/tests/derive/value_bare_keyword.rs b/tests/derive/value_bare_keyword.rs new file mode 100644 index 0000000..7214066 --- /dev/null +++ b/tests/derive/value_bare_keyword.rs @@ -0,0 +1,26 @@ +use uutils_args::{Arguments, Options, Value}; + +#[derive(Value, Default)] +enum Flavor { + #[default] + #[value(banana)] // Oops! + Banana, +} + +#[derive(Arguments)] +enum Arg { + #[arg("--flavor=FLAVOR")] + Flavor(Flavor), +} + +struct Settings {} + +impl Options for Settings { + fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> { + Ok(()) + } +} + +fn main() { + Settings {}.parse(std::env::args_os()).unwrap(); +} diff --git a/tests/derive/value_bare_keyword.stderr b/tests/derive/value_bare_keyword.stderr new file mode 100644 index 0000000..1fdb7e8 --- /dev/null +++ b/tests/derive/value_bare_keyword.stderr @@ -0,0 +1,30 @@ +error: proc-macro derive panicked + --> tests/derive/value_bare_keyword.rs:3:10 + | +3 | #[derive(Value, Default)] + | ^^^^^ + | + = help: message: expected comma-separated list of string literals: Error("unexpected end of input, unrecognized keyword in value attribute") + +error[E0277]: the trait bound `Flavor: uutils_args::Value` is not satisfied + --> tests/derive/value_bare_keyword.rs:10:10 + | +10 | #[derive(Arguments)] + | ^^^^^^^^^ the trait `uutils_args::Value` is not implemented for `Flavor` + | + = help: the following other types implement trait `uutils_args::Value`: + Option + OsString + PathBuf + String + i128 + i16 + i32 + i64 + and $N others +note: required by a bound in `parse_value_for_option` + --> src/internal.rs + | + | pub fn parse_value_for_option(opt: &str, v: &OsStr) -> Result { + | ^^^^^ required by this bound in `parse_value_for_option` + = note: this error originates in the derive macro `Arguments` (in Nightly builds, run with -Z macro-backtrace for more info)