Skip to content

derive: make error messages slightly more readable, add manually-driven test #133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion derive/src/argument.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ pub fn parse_arguments_attr(attrs: &[Attribute]) -> ArgumentsAttr {

pub fn parse_argument(v: Variant) -> Vec<Argument> {
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() {
Expand Down
11 changes: 6 additions & 5 deletions derive/src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -74,16 +75,16 @@ 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
// -f[value]

// 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:
Expand Down
8 changes: 5 additions & 3 deletions derive/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
3 changes: 2 additions & 1 deletion derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,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()]
Expand Down
84 changes: 84 additions & 0 deletions examples/test_compile_errors_manually.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use uutils_args::{Arguments, Options, Value};

// Using a fully-fledged compile-error testsuite is a bit overkill, but we still
// want to make sure that the `derive` crate generates reasonable error messages.
// That's what this "example" is for. In the following, there are blocks of
// lines, one marked as POSITIVE and multiple lines marked as NEGATIVE. The
// committed version of this file should only contain POSITIVE. In order to run a
// test, comment out the POSITIVE line, and use a NEGATIVE line instead, and
// manually check whether you see a reasonable error message – ideally the error
// message indicated by the comment. One way to do this is:

// $ cargo build --example test_compile_errors_manually

#[derive(Value, Debug, Default)]
enum Flavor {
#[default]
#[value("kind", "nice")]
Kind,
#[value("condescending")] // POSITIVE
// #[value(condescending)] // NEGATIVE: "expected comma-separated list of string literals"
Condescending,
}

#[derive(Arguments)]
#[arguments(file = "examples/hello_world_help.md")] // POSITIVE
// #[arguments(file = "examples/nonexistent.md")] // NEGATIVE: "cannot open help-string file"
// #[arguments(file = "/dev/full")] // NEGATIVE: Causes OOM, FIXME
// #[arguments(file = "/")] // NEGATIVE: "cannot read from help-string file"
// #[arguments(file = "path/to/some/WRITE-ONLY/file")] // NEGATIVE: "cannot open help-string file"
enum Arg {
/// The name to greet
#[arg("-n NAME", "--name[=NAME]", "name=NAME")] // POSITIVE
// #[arg("-")] // NEGATIVE: flag name must be non-empty (cannot be just '-')
// #[arg("-n NAME", "--name[NAME]", "name=NAME")] // NEGATIVE: "expected '=' after '[' in flag pattern"
// #[arg("-n NAME", "--name[=NAME", "name=NAME")] // NEGATIVE: "expected final ']' in flag pattern"
// #[arg(key="name")] // NEGATIVE: "can't parse arg attributes, expected one or more strings"
Name(String),

/// The number of times to greet
#[arg("-c N", "--count=N")]
Count(u8),

#[arg("--flavor=FLAVOR")]
Flavor(Flavor),
}

struct Settings {
name: String,
count: u8,
flavor: Flavor,
}

impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) -> Result<(), uutils_args::Error> {
match arg {
Arg::Name(n) => self.name = n,
Arg::Count(c) => self.count = c,
Arg::Flavor(flavor) => self.flavor = flavor,
}
Ok(())
}
}

fn main() -> Result<(), uutils_args::Error> {
let (settings, _operands) = Settings {
name: String::new(),
count: 1,
flavor: Flavor::Kind,
}
.parse(std::env::args_os())
.unwrap();

for _ in 0..settings.count {
match settings.flavor {
Flavor::Kind => {
println!("Hello, {}!", settings.name);
}
Flavor::Condescending => {
println!("Ugh, {}.", settings.name);
}
}
}
Ok(())
}