Skip to content

Commit 58f943a

Browse files
authored
docs(librarian): add godoc for nested commands (#2225)
Added Go's versions of partials to the text template so we can recurse through nested commands and print all of thier help text godoc. Fixes: #207
1 parent 710e85a commit 58f943a

File tree

2 files changed

+178
-31
lines changed

2 files changed

+178
-31
lines changed

internal/librarian/doc.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,118 @@ Commands:
139139
init initiates a release by creating a release pull request.
140140
tag-and-release tags and creates a GitHub release for a merged pull request.
141141
142+
# release init
143+
144+
The 'release init' command is the primary entry point for initiating
145+
a new release. It automates the creation of a release pull request by parsing
146+
conventional commits, determining the next semantic version for each library,
147+
and generating a changelog. Librarian is environment aware and will check if the
148+
current directory is the root of a librarian repository. If you are not
149+
executing in such a directory the '--repo' flag must be provided.
150+
151+
This command scans the git history since the last release, identifies changes
152+
(feat, fix, BREAKING CHANGE), and calculates the appropriate version bump
153+
according to semver rules. It then delegates all language-specific file
154+
modifications, such as updating a CHANGELOG.md or bumping the version in a pom.xml,
155+
to the configured language-specific container.
156+
157+
By default, 'release init' leaves the changes in your local working directory
158+
for inspection. Use the '--push' flag to automatically commit the changes to
159+
a new branch and create a pull request on GitHub. The '--commit' flag may be
160+
used to create a local commit without creating a pull request; this flag is
161+
ignored if '--push' is also specified.
162+
163+
Examples:
164+
165+
# Create a release PR for all libraries with pending changes.
166+
librarian release init --push
167+
168+
# Create a release PR for a single library.
169+
librarian release init --library=secretmanager --push
170+
171+
# Manually specify a version for a single library, overriding the calculation.
172+
librarian release init --library=secretmanager --library-version=2.0.0 --push
173+
174+
Usage:
175+
176+
librarian release init [flags]
177+
178+
Flags:
179+
180+
-branch string
181+
The branch to use with remote code repositories. This is used to specify
182+
which branch to clone and which branch to use as the base for a pull
183+
request. (default "main")
184+
-commit
185+
If true, librarian will create a commit for the release but not create
186+
a pull request. This flag is ignored if push is set to true.
187+
-image string
188+
Language specific image used to invoke code generation and releasing.
189+
If not specified, the image configured in the state.yaml is used.
190+
-library string
191+
The library ID to generate or release (e.g. google-cloud-secretmanager-v1).
192+
This corresponds to a releasable language unit.
193+
-library-version string
194+
Overrides the automatic semantic version calculation and forces a specific
195+
version for a library. Requires the --library flag to be specified.
196+
-output string
197+
Working directory root. When this is not specified, a working directory
198+
will be created in /tmp.
199+
-push
200+
If true, Librarian will create a commit and a pull request for the changes.
201+
A GitHub token with push access must be provided via the
202+
LIBRARIAN_GITHUB_TOKEN environment variable.
203+
-repo string
204+
Code repository where the generated code will reside. Can be a remote
205+
in the format of a remote URL such as https://github.com/{owner}/{repo} or a
206+
local file path like /path/to/repo. Both absolute and relative paths are
207+
supported. If not specified, will try to detect if the current working directory
208+
is configured as a language repository.
209+
210+
# release tag-and-release
211+
212+
The 'tag-and-release' command is the final step in the release
213+
process. It is designed to be run after a release pull request, created by
214+
'release init', has been merged.
215+
216+
This command's primary responsibilities are to:
217+
218+
- Create a Git tag for each library version included in the merged pull request.
219+
- Create a corresponding GitHub Release for each tag, using the release notes
220+
from the pull request body.
221+
- Update the pull request's label from 'release:pending' to 'release:done' to
222+
mark the process as complete.
223+
224+
You can target a specific merged pull request using the '--pr' flag. If no pull
225+
request is specified, the command will automatically search for and process all
226+
merged pull requests with the 'release:pending' label from the last 30 days.
227+
228+
Examples:
229+
230+
# Tag and create a GitHub release for a specific merged PR.
231+
librarian release tag-and-release --repo=https://github.com/googleapis/google-cloud-go --pr=https://github.com/googleapis/google-cloud-go/pull/123
232+
233+
# Find and process all pending merged release PRs in a repository.
234+
librarian release tag-and-release --repo=https://github.com/googleapis/google-cloud-go
235+
236+
Usage:
237+
238+
librarian release tag-and-release [arguments]
239+
240+
Flags:
241+
242+
-pr string
243+
The URL of a pull request to operate on.
244+
It should be in the format of https://github.com/{owner}/{repo}/pull/{number}.
245+
If not specified, will search for all merged pull requests with the label
246+
"release:pending" in the last 30 days.
247+
-repo string
248+
Code repository where the generated code will reside. Can be a remote
249+
in the format of a remote URL such as https://github.com/{owner}/{repo} or a
250+
local file path like /path/to/repo. Both absolute and relative paths are
251+
supported. If not specified, will try to detect if the current working directory
252+
is configured as a language repository.
253+
142254
# version
143255
144256
Version prints version information for the librarian binary.

internal/librarian/doc_generate.go

Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,26 @@ Usage:
5353
librarian <command> [arguments]
5454
5555
The commands are:
56-
{{range .Commands}}
56+
{{range .Commands}}{{template "command" .}}{{end}}
57+
*/
58+
package librarian
59+
60+
{{define "command"}}
5761
5862
# {{.Name}}
5963
6064
{{.HelpText}}
65+
{{if .Commands}}
66+
{{range .Commands}}{{template "command" .}}{{end}}
67+
{{end}}
6168
{{end}}
62-
*/
63-
package librarian
6469
`
6570

6671
// CommandDoc holds the documentation for a single CLI command.
6772
type CommandDoc struct {
6873
Name string
6974
HelpText string
75+
Commands []CommandDoc
7076
}
7177

7278
func main() {
@@ -87,36 +93,11 @@ func run() error {
8793
}
8894

8995
func processFile() error {
90-
// Get the help text.
91-
cmd := exec.Command("go", "run", "../../cmd/librarian/")
92-
var out bytes.Buffer
93-
cmd.Stdout = &out
94-
cmd.Stderr = &out
95-
err := cmd.Run()
96-
if err != nil {
97-
// The command exits with status 1 if no subcommand is given, which is
98-
// the case when we are generating the help text. We can ignore the
99-
// error if there is output.
100-
if out.Len() == 0 {
101-
return fmt.Errorf("cmd.Run() failed with %s\n%s", err, out.String())
102-
}
103-
}
104-
helpText := out.Bytes()
105-
106-
commandNames, err := extractCommandNames(helpText)
96+
commands, err := buildCommandDocs("")
10797
if err != nil {
10898
return err
10999
}
110100

111-
var commands []CommandDoc
112-
for _, name := range commandNames {
113-
help, err := getCommandHelp(name)
114-
if err != nil {
115-
return fmt.Errorf("getting help for command %s: %w", name, err)
116-
}
117-
commands = append(commands, CommandDoc{Name: name, HelpText: help})
118-
}
119-
120101
docFile, err := os.Create("doc.go")
121102
if err != nil {
122103
return fmt.Errorf("could not create doc.go: %v", err)
@@ -130,8 +111,62 @@ func processFile() error {
130111
return nil
131112
}
132113

133-
func getCommandHelp(command string) (string, error) {
134-
cmd := exec.Command("go", "run", "../../cmd/librarian/", command, "--help")
114+
func buildCommandDocs(parentCommand string) ([]CommandDoc, error) {
115+
var parentParts []string
116+
if parentCommand != "" {
117+
parentParts = strings.Fields(parentCommand)
118+
}
119+
120+
// Get help text for parent to find subcommands.
121+
args := []string{"run", "../../cmd/librarian/"}
122+
args = append(args, parentParts...)
123+
cmd := exec.Command("go", args...)
124+
var out bytes.Buffer
125+
cmd.Stdout = &out
126+
cmd.Stderr = &out
127+
// Ignore error, help text is printed on error when no subcommand is provided.
128+
_ = cmd.Run()
129+
130+
commandNames, err := extractCommandNames(out.Bytes())
131+
if err != nil {
132+
// Not an error, just means no subcommands.
133+
return nil, nil
134+
}
135+
136+
var commands []CommandDoc
137+
for _, name := range commandNames {
138+
fullCommandName := name
139+
if parentCommand != "" {
140+
fullCommandName = parentCommand + " " + name
141+
}
142+
143+
helpText, err := getCommandHelpText(fullCommandName)
144+
if err != nil {
145+
return nil, fmt.Errorf("getting help text for command %s: %w", fullCommandName, err)
146+
}
147+
148+
// Recurse.
149+
subCommands, err := buildCommandDocs(fullCommandName)
150+
if err != nil {
151+
return nil, err
152+
}
153+
154+
commands = append(commands, CommandDoc{
155+
Name: fullCommandName,
156+
HelpText: helpText,
157+
Commands: subCommands,
158+
})
159+
}
160+
161+
return commands, nil
162+
}
163+
164+
func getCommandHelpText(command string) (string, error) {
165+
parts := strings.Fields(command)
166+
args := []string{"run", "../../cmd/librarian/"}
167+
args = append(args, parts...)
168+
args = append(args, "--help")
169+
cmd := exec.Command("go", args...)
135170
var out bytes.Buffer
136171
cmd.Stdout = &out
137172
cmd.Stderr = &out

0 commit comments

Comments
 (0)