Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(godocfx): add friendlyAPIName #6447

Merged
merged 2 commits into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 65 additions & 2 deletions internal/godocfx/godocfx_test.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
dansaadati marked this conversation as resolved.
Show resolved Hide resolved
- uid: cloud.google.com/go/storage.DeleteAction,SetStorageClassAction,AbortIncompleteMPUAction
name: DeleteAction, SetStorageClassAction, AbortIncompleteMPUAction
id: DeleteAction,SetStorageClassAction,AbortIncompleteMPUAction
Expand Down