Skip to content

Commit 43e0a10

Browse files
authored
docs: start generating package godoc (#1956)
Created a means for us to generate the doc.go file based off output from the help text. There is much more doc that still needs to be put into this file but wanted to land this as a proof of concept at least. Updates: #207
1 parent 70bb3c6 commit 43e0a10

File tree

5 files changed

+207
-4
lines changed

5 files changed

+207
-4
lines changed

.github/workflows/librarian.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ jobs:
4949
runs-on: ubuntu-latest
5050
steps:
5151
- uses: actions/checkout@v4
52+
- uses: actions/setup-go@v5
53+
with:
54+
go-version-file: 'go.mod'
55+
- name: Install tools
56+
run: |
57+
set -e
58+
curl -fSSL -o /tmp/protoc.zip https://github.com/protocolbuffers/protobuf/releases/download/v28.3/protoc-28.3-linux-x86_64.zip
59+
cd /usr/local
60+
sudo unzip -x /tmp/protoc.zip
61+
protoc --version
62+
go install github.com/google/yamlfmt/cmd/[email protected]
63+
go install golang.org/x/tools/cmd/goimports@latest
5264
- uses: docker/setup-docker-action@v4
5365
- name: Build the test image
5466
run: |

internal/librarian/doc.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
//go:generate go run -tags docgen doc_generate.go
16+
17+
/*
18+
Package librarian contains the business logic for the Librarian CLI.
19+
Implementation details for interacting with other systems (Git, GitHub,
20+
Docker etc.) are abstracted into other packages.
21+
22+
Usage:
23+
24+
librarian <command> [arguments]
25+
26+
The commands are:
27+
28+
generate generates client library code for a single API
29+
release manages releases of libraries.
30+
version prints the version information
31+
*/
32+
package librarian

internal/librarian/doc_generate.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2024 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+
//go:build docgen
16+
17+
package main
18+
19+
import (
20+
"bytes"
21+
"errors"
22+
"fmt"
23+
"log"
24+
"os"
25+
"os/exec"
26+
"strings"
27+
"text/template"
28+
)
29+
30+
const docTemplate = `// Copyright 2025 Google LLC
31+
//
32+
// Licensed under the Apache License, Version 2.0 (the "License");
33+
// you may not use this file except in compliance with the License.
34+
// You may obtain a copy of the License at
35+
//
36+
// https://www.apache.org/licenses/LICENSE-2.0
37+
//
38+
// Unless required by applicable law or agreed to in writing, software
39+
// distributed under the License is distributed on an "AS IS" BASIS,
40+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
41+
// See the License for the specific language governing permissions and
42+
// limitations under the License.
43+
44+
//go:generate go run -tags docgen doc_generate.go
45+
46+
/*
47+
Package librarian contains the business logic for the Librarian CLI.
48+
Implementation details for interacting with other systems (Git, GitHub,
49+
Docker etc.) are abstracted into other packages.
50+
51+
Usage:
52+
53+
librarian <command> [arguments]
54+
55+
The commands are:
56+
{{.Commands}}
57+
*/
58+
package librarian
59+
`
60+
61+
func main() {
62+
if err := run(); err != nil {
63+
log.Fatal(err)
64+
}
65+
}
66+
67+
func run() error {
68+
if err := processFile(); err != nil {
69+
return err
70+
}
71+
cmd := exec.Command("goimports", "-w", "doc.go")
72+
if err := cmd.Run(); err != nil {
73+
log.Fatalf("goimports: %v", err)
74+
}
75+
return nil
76+
}
77+
78+
func processFile() error {
79+
// Get the help text.
80+
cmd := exec.Command("go", "run", "../../cmd/librarian/")
81+
var out bytes.Buffer
82+
cmd.Stdout = &out
83+
cmd.Stderr = &out
84+
err := cmd.Run()
85+
if err != nil {
86+
// The command exits with status 1 if no subcommand is given, which is
87+
// the case when we are generating the help text. We can ignore the
88+
// error if there is output.
89+
if out.Len() == 0 {
90+
return fmt.Errorf("cmd.Run() failed with %s\n%s", err, out.String())
91+
}
92+
}
93+
helpText := out.Bytes()
94+
95+
commands, err := extractCommands(helpText)
96+
97+
docFile, err := os.Create("doc.go")
98+
if err != nil {
99+
return fmt.Errorf("could not create doc.go: %v", err)
100+
}
101+
defer docFile.Close()
102+
103+
tmpl := template.Must(template.New("doc").Parse(docTemplate))
104+
if err := tmpl.Execute(docFile, struct{ Commands string }{Commands: string(commands)}); err != nil {
105+
return fmt.Errorf("could not execute template: %v", err)
106+
}
107+
return nil
108+
}
109+
110+
func extractCommands(helpText []byte) ([]byte, error) {
111+
const (
112+
commandsHeader = "Commands:\n\n"
113+
)
114+
ss := string(helpText)
115+
start := strings.Index(ss, commandsHeader)
116+
if start == -1 {
117+
return nil, errors.New("could not find commands header")
118+
}
119+
start += len(commandsHeader)
120+
return []byte(ss[start:]), nil
121+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 librarian
16+
17+
import (
18+
"bytes"
19+
"os/exec"
20+
"testing"
21+
)
22+
23+
func TestGoGenerate(t *testing.T) {
24+
t.Parallel()
25+
cmd := exec.Command("go", "generate", "./...")
26+
var stderr, stdout bytes.Buffer
27+
cmd.Stderr = &stderr
28+
cmd.Stdout = &stdout
29+
t.Log(stderr.String())
30+
t.Log(stdout.String())
31+
if err := cmd.Run(); err != nil {
32+
t.Fatalf("%v: %v", cmd, err)
33+
}
34+
cmd = exec.Command("git", "diff", "--exit-code")
35+
if err := cmd.Run(); err != nil {
36+
t.Errorf("go generate produced a diff, please run `go generate ./...` and commit the changes")
37+
cmd := exec.Command("git", "diff")
38+
out, _ := cmd.CombinedOutput()
39+
t.Logf("diff:\n%s", out)
40+
}
41+
}

internal/librarian/librarian.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
// Package librarian contains the business logic for the Librarian CLI.
16-
// Implementation details for interacting with other systems (Git, GitHub,
17-
// Docker etc.) are abstracted into other packages.
1815
package librarian
1916

2017
import (
@@ -53,7 +50,7 @@ func Run(ctx context.Context, arg ...string) error {
5350
}
5451
if len(arg) == 0 {
5552
CmdLibrarian.Flags.Usage()
56-
return fmt.Errorf("command not specified")
53+
return nil
5754
}
5855
cmd, arg, err := lookupCommand(CmdLibrarian, arg)
5956
if err != nil {

0 commit comments

Comments
 (0)