From 4cba342aaec67ced332759edf58970d58528b910 Mon Sep 17 00:00:00 2001
From: Florian Forster <fforster@gitlab.com>
Date: Fri, 12 Jan 2024 08:49:07 +0100
Subject: [PATCH 1/2] feat: Add the `WithUsage` function.

---
 args.go      | 13 +++++++++++++
 args_test.go | 24 ++++++++++++++++++++++++
 2 files changed, 37 insertions(+)

diff --git a/args.go b/args.go
index ed1e70ce..08f92f1c 100644
--- a/args.go
+++ b/args.go
@@ -129,3 +129,16 @@ func MatchAll(pargs ...PositionalArgs) PositionalArgs {
 func ExactValidArgs(n int) PositionalArgs {
 	return MatchAll(ExactArgs(n), OnlyValidArgs)
 }
+
+// WithUsage wraps another PositionalArgs function. If the function fails
+// (returns a non-nil error), the error is supplemented with the command's
+// usage message, providing the user with the information required to run the
+// command correctly.
+func WithUsage(wrapped PositionalArgs) PositionalArgs {
+	return func(cmd *Command, args []string) error {
+		if err := wrapped(cmd, args); err != nil {
+			return fmt.Errorf("%w\n\n%s", err, cmd.UsageString())
+		}
+		return nil
+	}
+}
diff --git a/args_test.go b/args_test.go
index 90d174cc..077a2f3f 100644
--- a/args_test.go
+++ b/args_test.go
@@ -539,3 +539,27 @@ func TestLegacyArgsSubcmdAcceptsArgs(t *testing.T) {
 		t.Fatalf("Unexpected error: %v", err)
 	}
 }
+
+// WithUsage
+
+func TestWithUsage(t *testing.T) {
+	c := getCommand(WithUsage(ExactArgs(1)), false)
+
+	_, err := executeCommand(c /* no args */)
+	if err == nil {
+		t.Fatalf("Expected error, got nil")
+	}
+
+	got, want := err.Error(), c.UsageString()
+	if !strings.Contains(got, want) {
+		t.Errorf("Expected error containing %q, got %q", want, got)
+	}
+}
+
+func ExampleWithUsage() {
+	cmd := &Command{
+		Use:  "example <arg>",
+		Args: WithUsage(ExactArgs(1)),
+	}
+	_ = cmd // ignore unused
+}

From 399779240ee4fd3565dfc8dd826b160c3e7e5734 Mon Sep 17 00:00:00 2001
From: Florian Forster <fforster@gitlab.com>
Date: Fri, 12 Jan 2024 09:13:41 +0100
Subject: [PATCH 2/2] doc: Improve the `WithUsage` example.

---
 args_test.go | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/args_test.go b/args_test.go
index 077a2f3f..ec17fdc7 100644
--- a/args_test.go
+++ b/args_test.go
@@ -560,6 +560,21 @@ func ExampleWithUsage() {
 	cmd := &Command{
 		Use:  "example <arg>",
 		Args: WithUsage(ExactArgs(1)),
+		Run: func(*Command, []string) {
+			panic("not reached")
+		},
 	}
-	_ = cmd // ignore unused
+
+	cmd.SetArgs([]string{"1", "2"})
+	err := cmd.Execute()
+	fmt.Print(err)
+
+	// Output:
+	// accepts 1 arg(s), received 2
+	//
+	// Usage:
+	//   example <arg> [flags]
+	//
+	// Flags:
+	//   -h, --help   help for example
 }