Skip to content

Commit

Permalink
feat(godocfx): add friendlyAPIName (#6447)
Browse files Browse the repository at this point in the history
  • Loading branch information
tbpg committed Aug 3, 2022
1 parent d28e55d commit c6d3ba4
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 25 deletions.
67 changes: 65 additions & 2 deletions internal/godocfx/godocfx_test.go
Expand Up @@ -19,9 +19,12 @@ package main

import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
Expand All @@ -42,9 +45,28 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}

func fakeMetaServer() *httptest.Server {
meta := repoMetadata{
"cloud.google.com/go/storage": repoMetadataItem{
Description: "Storage API",
},
"cloud.google.com/iam/apiv1beta1": repoMetadataItem{
Description: "IAM",
},
"cloud.google.com/go/cloudbuild/apiv1/v2": repoMetadataItem{
Description: "Cloud Build API",
},
}
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(meta)
}))
}

func TestParse(t *testing.T) {
mod := "cloud.google.com/go/bigquery"
r, err := parse(mod+"/...", ".", []string{"README.md"}, nil)
metaServer := fakeMetaServer()
defer metaServer.Close()
r, err := parse(mod+"/...", ".", []string{"README.md"}, nil, &friendlyAPINamer{metaURL: metaServer.URL})
if err != nil {
t.Fatalf("Parse: %v", err)
}
Expand Down Expand Up @@ -110,7 +132,9 @@ func TestGoldens(t *testing.T) {
extraFiles := []string{"README.md"}

testPath := "cloud.google.com/go/storage"
r, err := parse(testPath, ".", extraFiles, nil)
metaServer := fakeMetaServer()
defer metaServer.Close()
r, err := parse(testPath, ".", extraFiles, nil, &friendlyAPINamer{metaURL: metaServer.URL})
if err != nil {
t.Fatalf("parse: %v", err)
}
Expand Down Expand Up @@ -282,3 +306,42 @@ Deprecated: use Reader.Attrs.Size.`,
}
}
}

func TestFriendlyAPIName(t *testing.T) {
metaServer := fakeMetaServer()
defer metaServer.Close()
namer := &friendlyAPINamer{metaURL: metaServer.URL}

tests := []struct {
importPath string
want string
}{
{
importPath: "cloud.google.com/go/storage",
want: "Storage API",
},
{
importPath: "cloud.google.com/iam/apiv1beta1",
want: "IAM v1beta1",
},
{
importPath: "cloud.google.com/go/cloudbuild/apiv1/v2",
want: "Cloud Build API v1",
},
{
importPath: "not found",
want: "",
},
}

for _, test := range tests {
got, err := namer.friendlyAPIName(test.importPath)
if err != nil {
t.Errorf("friendlyAPIName(%q) got err: %v", test.importPath, err)
continue
}
if got != test.want {
t.Errorf("friendlyAPIName(%q) got %q, want %q", test.importPath, got, test.want)
}
}
}
5 changes: 4 additions & 1 deletion internal/godocfx/main.go
Expand Up @@ -171,7 +171,10 @@ func process(mod indexEntry, workingDir, outDir string, print bool) error {

log.Println("Starting to parse")
optionalExtraFiles := []string{}
r, err := parse(mod.Path+"/...", workingDir, optionalExtraFiles, filter)
namer := &friendlyAPINamer{
metaURL: "https://raw.githubusercontent.com/googleapis/google-cloud-go/main/internal/.repo-metadata-full.json",
}
r, err := parse(mod.Path+"/...", workingDir, optionalExtraFiles, filter, namer)
if err != nil {
return fmt.Errorf("parse: %v", err)
}
Expand Down
118 changes: 96 additions & 22 deletions internal/godocfx/parse.go
Expand Up @@ -25,16 +25,19 @@ package main

import (
"bytes"
"encoding/json"
"fmt"
"go/format"
"go/printer"
"go/token"
"log"
"net/http"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"sync"

goldmarkcodeblock "cloud.google.com/go/internal/godocfx/goldmark-codeblock"
"cloud.google.com/go/internal/godocfx/pkgload"
Expand Down Expand Up @@ -84,18 +87,19 @@ type example struct {

// item represents a DocFX item.
type item struct {
UID string `yaml:"uid"`
Name string `yaml:"name,omitempty"`
ID string `yaml:"id,omitempty"`
Summary string `yaml:"summary,omitempty"`
Parent string `yaml:"parent,omitempty"`
Type string `yaml:"type,omitempty"`
Langs []string `yaml:"langs,omitempty"`
Syntax syntax `yaml:"syntax,omitempty"`
Examples []example `yaml:"codeexamples,omitempty"`
Children []child `yaml:"children,omitempty"`
AltLink string `yaml:"alt_link,omitempty"`
Status string `yaml:"status,omitempty"`
UID string `yaml:"uid"`
Name string `yaml:"name,omitempty"`
ID string `yaml:"id,omitempty"`
Summary string `yaml:"summary,omitempty"`
Parent string `yaml:"parent,omitempty"`
Type string `yaml:"type,omitempty"`
Langs []string `yaml:"langs,omitempty"`
Syntax syntax `yaml:"syntax,omitempty"`
Examples []example `yaml:"codeexamples,omitempty"`
Children []child `yaml:"children,omitempty"`
AltLink string `yaml:"alt_link,omitempty"`
Status string `yaml:"status,omitempty"`
FriendlyAPIName string `yaml:"friendlyApiName,omitempty"`
}

func (p *page) addItem(i *item) {
Expand Down Expand Up @@ -125,7 +129,7 @@ type result struct {
// workingDir is the directory to use to run go commands.
//
// optionalExtraFiles is a list of paths relative to the module root to include.
func parse(glob string, workingDir string, optionalExtraFiles []string, filter []string) (*result, error) {
func parse(glob string, workingDir string, optionalExtraFiles []string, filter []string, namer *friendlyAPINamer) (*result, error) {
pages := map[string]*page{}

pkgInfos, err := pkgload.Load(glob, workingDir, filter)
Expand Down Expand Up @@ -162,16 +166,21 @@ func parse(glob string, workingDir string, optionalExtraFiles []string, filter [
for _, pi := range pkgInfos {
link := newLinker(pi)
topLevelDecls := pkgsite.TopLevelDecls(pi.Doc)
friendly, err := namer.friendlyAPIName(pi.Doc.ImportPath)
if err != nil {
return nil, err
}
pkgItem := &item{
UID: pi.Doc.ImportPath,
Name: pi.Doc.ImportPath,
ID: pi.Doc.Name,
Summary: toHTML(pi.Doc.Doc),
Langs: onlyGo,
Type: "package",
Examples: processExamples(pi.Doc.Examples, pi.Fset),
AltLink: "https://pkg.go.dev/" + pi.Doc.ImportPath,
Status: pi.Status,
UID: pi.Doc.ImportPath,
Name: pi.Doc.ImportPath,
ID: pi.Doc.Name,
Summary: toHTML(pi.Doc.Doc),
Langs: onlyGo,
Type: "package",
Examples: processExamples(pi.Doc.Examples, pi.Fset),
AltLink: "https://pkg.go.dev/" + pi.Doc.ImportPath,
Status: pi.Status,
FriendlyAPIName: friendly,
}
pkgPage := &page{Items: []*item{pkgItem}}
pages[pi.Doc.ImportPath] = pkgPage
Expand Down Expand Up @@ -640,3 +649,68 @@ func hasPrefix(s string, prefixes []string) bool {
}
return false
}

// repoMetadata is the JSON format of the .repo-metadata-full.json file.
// See https://raw.githubusercontent.com/googleapis/google-cloud-go/main/internal/.repo-metadata-full.json.
type repoMetadata map[string]repoMetadataItem

type repoMetadataItem struct {
Description string `json:"description"`
}

type friendlyAPINamer struct {
// metaURL is the URL to .repo-metadata-full.json, which contains metadata
// about packages in this repo. See
// https://raw.githubusercontent.com/googleapis/google-cloud-go/main/internal/.repo-metadata-full.json.
metaURL string

// metadata caches the repo metadata results.
metadata repoMetadata
// getOnce ensures we only fetch the metadata JSON once.
getOnce sync.Once
}

// vNumberRE is a heuristic for API versions.
var vNumberRE = regexp.MustCompile(`apiv[0-9][^/]*`)

// friendlyAPIName returns the friendlyAPIName for the given import path.
// We rely on the .repo-metadata-full.json file to get the description of the
// API for the given import path. We use the importPath to parse out the
// API version, since that isn't included in the metadata.
//
// If no API description is found, friendlyAPIName returns "".
// If no API version is found, friendlyAPIName only returns the description.
//
// See https://github.com/googleapis/google-cloud-go/issues/5949.
func (d *friendlyAPINamer) friendlyAPIName(importPath string) (string, error) {
var err error
d.getOnce.Do(func() {
resp, getErr := http.Get(d.metaURL)
if err != nil {
err = fmt.Errorf("error getting repo metadata: %v", getErr)
return
}
defer resp.Body.Close()
if decodeErr := json.NewDecoder(resp.Body).Decode(&d.metadata); err != nil {
err = fmt.Errorf("failed to decode repo metadata: %v", decodeErr)
return
}
})
if err != nil {
return "", err
}
if d.metadata == nil {
return "", fmt.Errorf("no metadata found: earlier error fetching?")
}
pkg, ok := d.metadata[importPath]
if !ok {
return "", nil
}

if apiV := vNumberRE.FindString(importPath); apiV != "" {
version := strings.TrimPrefix(apiV, "api")
return fmt.Sprintf("%s %s", pkg.Description, version), nil
}

return pkg.Description, nil
}
1 change: 1 addition & 0 deletions internal/godocfx/testdata/golden/index.yml
Expand Up @@ -288,6 +288,7 @@ items:
- cloud.google.com/go/storage.Writer.Write
- cloud.google.com/go/storage.SignedURL
alt_link: https://pkg.go.dev/cloud.google.com/go/storage
friendlyApiName: Storage API
- uid: cloud.google.com/go/storage.DeleteAction,SetStorageClassAction,AbortIncompleteMPUAction
name: DeleteAction, SetStorageClassAction, AbortIncompleteMPUAction
id: DeleteAction,SetStorageClassAction,AbortIncompleteMPUAction
Expand Down

0 comments on commit c6d3ba4

Please sign in to comment.