Skip to content

Commit 781362c

Browse files
authored
refactor: standardize and simplify command usage text across various tools and add dedicated usage tests. (#4536)
Command usage text has been updated for consistency through the librarian codebase. Usage text is still hard coded, so we've also added a test to help ensure its not changed inadvertently. In the future, this test will allow us to transition to programmatically generated usage text. General rules: 1. `<arg>` = required argument 2. `[arg]` = optional argument 3. `...` = one or more 4. Don't put flags in usage line 5. Use `--all` for all libraries What the usage text would look like: Main command:`librarian [command]` Simple commands: - `librarian version` - `librarian publish` - `librarian tidy [path]` Commands where you specify one library OR `--all`: - `librarian bump <library>` - `librarian generate <library>` Commands with required and optional args - `librarian add <library> [apis...]` Commands with optional source - `librarian update [source]` Fixes #3631
1 parent d5d1315 commit 781362c

11 files changed

Lines changed: 166 additions & 15 deletions

File tree

cmd/librarian/doc.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ NAME:
3333
3434
USAGE:
3535
36-
librarian add <apis...> [flags]
36+
librarian add <apis...>
3737
3838
OPTIONS:
3939
@@ -51,7 +51,7 @@ NAME:
5151
5252
USAGE:
5353
54-
librarian generate [library] [--all]
54+
librarian generate <library>
5555
5656
OPTIONS:
5757
@@ -70,7 +70,7 @@ NAME:
7070
7171
USAGE:
7272
73-
librarian bump [library] [--all] [--version=<version>]
73+
librarian bump <library>
7474
7575
DESCRIPTION:
7676

cmd/librarianops/doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ NAME:
3131
3232
USAGE:
3333
34-
librarianops generate [<repo> | -C <dir>] [--docker]
34+
librarianops generate [<repo> | -C <dir>]
3535
3636
DESCRIPTION:
3737

internal/command/usage_test.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package command
16+
17+
// Usage text rules:
18+
// - <arg> = required argument
19+
// - [arg] = optional argument
20+
// - <arg...> = one or more required arguments
21+
// - Flags are included in the usage line if they are required
22+
// - Use --all for all libraries
23+
//
24+
// Note: We do not test the 'migrate' tool here because it is not built with
25+
// the urfave/cli library and does not follow these usage patterns.
26+
27+
import (
28+
"bytes"
29+
"os/exec"
30+
"strings"
31+
"testing"
32+
33+
"github.com/google/go-cmp/cmp"
34+
)
35+
36+
func TestLibrarianUsage(t *testing.T) {
37+
const bin = "github.com/googleapis/librarian/cmd/librarian"
38+
for _, test := range []struct {
39+
desc string
40+
args []string
41+
want string
42+
}{
43+
{"root", nil, "librarian [command]"},
44+
{"add", []string{"add"}, "librarian add <apis...>"},
45+
{"generate", []string{"generate"}, "librarian generate <library>"},
46+
{"bump", []string{"bump"}, "librarian bump <library>"},
47+
{"tidy", []string{"tidy"}, "librarian tidy"},
48+
{"update", []string{"update"}, "librarian update <sources...>"},
49+
{"version", []string{"version"}, "librarian version"},
50+
{"publish", []string{"publish"}, "librarian publish"},
51+
{"tag", []string{"tag"}, "librarian tag"},
52+
} {
53+
t.Run(test.desc, func(t *testing.T) {
54+
got := runUsage(t, bin, test.args)
55+
if diff := cmp.Diff(test.want, got); diff != "" {
56+
t.Errorf("mismatch (-want +got):\n%s", diff)
57+
}
58+
})
59+
}
60+
}
61+
62+
func TestLibrarianopsUsage(t *testing.T) {
63+
const bin = "github.com/googleapis/librarian/cmd/librarianops"
64+
for _, test := range []struct {
65+
desc string
66+
args []string
67+
want string
68+
}{
69+
{"root", nil, "librarianops [command]"},
70+
{"generate", []string{"generate"}, "librarianops generate [<repo> | -C <dir>]"},
71+
} {
72+
t.Run(test.desc, func(t *testing.T) {
73+
got := runUsage(t, bin, test.args)
74+
if diff := cmp.Diff(test.want, got); diff != "" {
75+
t.Errorf("mismatch (-want +got):\n%s", diff)
76+
}
77+
})
78+
}
79+
}
80+
81+
func TestSurferUsage(t *testing.T) {
82+
const bin = "github.com/googleapis/librarian/cmd/surfer"
83+
for _, test := range []struct {
84+
desc string
85+
args []string
86+
want string
87+
}{
88+
{"root", nil, "surfer [command]"},
89+
{"generate", []string{"generate"}, "surfer generate <path to gcloud.yaml> --googleapis <path>"},
90+
} {
91+
t.Run(test.desc, func(t *testing.T) {
92+
got := runUsage(t, bin, test.args)
93+
if diff := cmp.Diff(test.want, got); diff != "" {
94+
t.Errorf("mismatch (-want +got):\n%s", diff)
95+
}
96+
})
97+
}
98+
}
99+
100+
func TestToolUsage(t *testing.T) {
101+
for _, test := range []struct {
102+
desc string
103+
bin string
104+
args []string
105+
want string
106+
}{
107+
{"import-configs root", "github.com/googleapis/librarian/tool/cmd/importconfigs", nil, "import-configs [command]"},
108+
{"import-configs update-transports", "github.com/googleapis/librarian/tool/cmd/importconfigs", []string{"update-transports"}, "import-configs update-transports --googleapis <path>"},
109+
{"import-metadata", "github.com/googleapis/librarian/tool/cmd/importmetadata", nil, "import-metadata --python-repo <path> --librarian-repo <path>"},
110+
} {
111+
t.Run(test.desc, func(t *testing.T) {
112+
got := runUsage(t, test.bin, test.args)
113+
if diff := cmp.Diff(test.want, got); diff != "" {
114+
t.Errorf("mismatch (-want +got):\n%s", diff)
115+
}
116+
})
117+
}
118+
}
119+
120+
// runUsage executes the binary with the given args and the appropriate help flag,
121+
// returning the captured usage string.
122+
func runUsage(t *testing.T, bin string, args []string) string {
123+
t.Helper()
124+
var stdout bytes.Buffer
125+
fullArgs := append([]string{"run", bin}, args...)
126+
fullArgs = append(fullArgs, "--help")
127+
cmd := exec.Command("go", fullArgs...)
128+
cmd.Stdout = &stdout
129+
cmd.Stderr = &stdout // Some help might go to stderr
130+
if err := cmd.Run(); err != nil {
131+
t.Fatalf("go %v failed: %v\nOutput: %s", fullArgs, err, stdout.String())
132+
}
133+
return captureUsage(t, stdout.String())
134+
}
135+
136+
// captureUsage extracts the usage line from the command output. It looks for
137+
// a line containing "USAGE:" and returns the following line.
138+
func captureUsage(t *testing.T, output string) string {
139+
t.Helper()
140+
lines := strings.Split(output, "\n")
141+
for i, line := range lines {
142+
if strings.Contains(strings.ToUpper(line), "USAGE:") {
143+
if i+1 < len(lines) {
144+
return strings.TrimSpace(lines[i+1])
145+
}
146+
}
147+
}
148+
return ""
149+
}

internal/librarian/add.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func addCommand() *cli.Command {
4242
return &cli.Command{
4343
Name: "add",
4444
Usage: "add a new client library to librarian.yaml",
45-
UsageText: "librarian add <apis...> [flags]",
45+
UsageText: "librarian add <apis...>",
4646
Action: func(ctx context.Context, c *cli.Command) error {
4747
apis := c.Args().Slice()
4848
if len(apis) == 0 {

internal/librarian/bump.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func bumpCommand() *cli.Command {
6262
return &cli.Command{
6363
Name: "bump",
6464
Usage: "update versions and prepare release artifacts",
65-
UsageText: "librarian bump [library] [--all] [--version=<version>]",
65+
UsageText: "librarian bump <library>",
6666
Description: `bump updates version numbers and prepares the files needed for a new release.
6767
6868
If a library name is given, only that library is updated. The --all flag updates every

internal/librarian/generate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func generateCommand() *cli.Command {
4343
return &cli.Command{
4444
Name: "generate",
4545
Usage: "generate a client library",
46-
UsageText: "librarian generate [library] [--all]",
46+
UsageText: "librarian generate <library>",
4747
Flags: []cli.Flag{
4848
&cli.BoolFlag{
4949
Name: "all",

internal/librarianops/generate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func generateCommand() *cli.Command {
4444
return &cli.Command{
4545
Name: "generate",
4646
Usage: "generate libraries across repositories",
47-
UsageText: "librarianops generate [<repo> | -C <dir>] [--docker]",
47+
UsageText: "librarianops generate [<repo> | -C <dir>]",
4848
Description: `Examples:
4949
librarianops generate google-cloud-rust
5050
librarianops generate -C ~/workspace/google-cloud-rust

internal/librarianops/upgrade.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func upgradeCommand() *cli.Command {
3030
return &cli.Command{
3131
Name: "upgrade",
3232
Usage: "upgrade librarian version in librarian.yaml",
33-
UsageText: "librarianops upgrade [<repo> | -C <dir> ]",
33+
UsageText: "librarianops upgrade [<repo> | -C <dir>]",
3434
Description: `Examples:
3535
librarianops upgrade google-cloud-rust
3636
librarianops upgrade -C ~/workspace/google-cloud-rust

internal/surfer/surfer/surfer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func Run(ctx context.Context, args ...string) error {
2828
cmd := &cli.Command{
2929
Name: "surfer",
3030
Usage: "generates gcloud command YAML files",
31-
UsageText: "surfer generate [arguments]",
31+
UsageText: "surfer [command]",
3232
Description: "surfer generates gcloud command YAML files",
3333
Commands: []*cli.Command{
3434
generateCommand(),
@@ -41,7 +41,7 @@ func generateCommand() *cli.Command {
4141
return &cli.Command{
4242
Name: "generate",
4343
Usage: "generates gcloud commands",
44-
UsageText: "surfer generate <path to gcloud.yaml> --googleapis <path> [--out <path>]",
44+
UsageText: "surfer generate <path to gcloud.yaml> --googleapis <path>",
4545
Description: `generate generates gcloud command files from protobuf API specifications,
4646
service config yaml, and gcloud.yaml.`,
4747
Flags: []cli.Flag{

tool/cmd/importconfigs/update_transports.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ const bazelLangs = 7
4646

4747
func updateTransportsCommand() *cli.Command {
4848
return &cli.Command{
49-
Name: "update-transports",
50-
Usage: "update transport values in internal/serviceconfig/api.go from BUILD.bazel files",
49+
Name: "update-transports",
50+
Usage: "update transport values in internal/serviceconfig/api.go from BUILD.bazel files",
51+
UsageText: "import-configs update-transports --googleapis <path>",
5152
Flags: []cli.Flag{
5253
&cli.StringFlag{
5354
Name: "googleapis",

0 commit comments

Comments
 (0)