workgroups/vendor/github.com/posener/goreadme/goreadme.go
Marvin Preuss f121b05b58
Some checks failed
continuous-integration/drone/push Build is failing
repo: adds goreadme
2021-09-24 18:29:05 +02:00

295 lines
9.7 KiB
Go

// Package goreadme generates readme markdown file from go doc.
//
// The package can be used as a command line tool and as Github action, described below:
//
// Github Action
//
// Github actions can be configured to update the README file automatically every time it is needed.
// Below there is an example that on every time a new change is pushed to the main branch, the
// action is trigerred, generates a new README file, and if there is a change - commits and pushes
// it to the main branch. In pull requests that affect the README content, if the `github-token`
// is given, the action will post a comment on the pull request with changes that will be made to
// the README file.
//
// To use this with Github actions, add the following content to `.github/workflows/goreadme.yml`.
// See ./action.yml for all available input options.
//
// on:
// push:
// branches: [main]
// pull_request:
// branches: [main]
// permissions:
// # Goreadme needs permissions to update pull requests comments.
// pull-requests: write
// jobs:
// goreadme:
// runs-on: ubuntu-latest
// steps:
// - name: Check out repository
// uses: actions/checkout@v2
// - name: Update readme according to Go doc
// uses: posener/goreadme@v1
// with:
// badge-travisci: 'true'
// badge-codecov: 'true'
// badge-godoc: 'true'
// badge-goreadme: 'true'
// # Optional: Token allows goreadme to comment the PR with diff preview.
// github-token: '${{ secrets.GITHUB_TOKEN }}'
//
// Use as a command line tool
//
// $ GO111MODULE=on go get github.com/posener/goreadme/cmd/goreadme
// $ goreadme -h
//
// Why Should You Use It
//
// Both Go doc and readme files are important. Go doc to be used by your user's library, and README
// file to welcome users to use your library. They share common content, which is usually duplicated
// from the doc to the readme or vice versa once the library is ready. The problem is that keeping
// documentation updated is important, and hard enough - keeping both updated is twice as hard.
//
// Go Doc Instructions
//
// The formatting of the README.md is done by the go doc parser. This makes the result README.md a
// bit more limited. Currently, `goreadme` supports the formatting as explained in
// (godoc page) https://blog.golang.org/godoc-documenting-go-code, or
// (here) https://pkg.go.dev/github.com/fluhus/godoc-tricks. Meaning:
//
// * A header is a single line that is separated from a paragraph above.
//
// * Code block is recognized by indentation as Go code.
//
// func main() {
// ...
// }
//
// * Inline code is marked with `backticks`.
//
// * URLs will just automatically be converted to links: https://github.com/posener/goreadme
//
// Additionally, the syntax was extended to include some more markdown features while keeping the Go
// doc readable:
//
// * Bulleted and numbered lists are possible when each bullet item is followed by an empty line.
//
// * Diff blocks are automatically detected when each line in a code block starts with a `' '`,
// `'-'` or `'+'`:
//
// -removed line starts with '-'
// remained line starts with ' '
// +added line starts with '+'
//
// * A repository file can be linked when providing a path that start with `./`: ./goreadme.go.
//
// * A link can have a link text by prefixing it with parenthesised text:
// (goreadme page) https://github.com/posener/goreadme.
//
// * A link to repository file and can have a link text: (goreadme main file) ./goreamde.go.
//
// * An image can be added by prefixing a link to an image with `(image/<image title>)`:
//
// (image/title of image) https://github.githubassets.com/images/icons/emoji/unicode/1f44c.png
//
// Testing
//
// The goreadme tests the test cases in the ./testdata directory. It generates readme files for
// all the packages in that directory and asserts that the result readme matches the existing one.
// When modifying goreadme behavior, there is no need to manually change these readme files. It is
// possible to run `WRITE_READMES=1 go test ./...` which regenerates them and check the changes
// match the expected (optionally using `git diff`).
package goreadme
import (
"context"
"encoding/json"
"io"
"log"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"github.com/golang/gddo/doc"
"github.com/pkg/errors"
"github.com/posener/goreadme/internal/markdown"
"github.com/posener/goreadme/internal/template"
)
// New returns a GoReadme object with a custom client.
// client is an HTTP client used to perform the requests. It can be used
// to authenticate github requests, for example, a github client can be used:
//
// oauth2.NewClient(ctx, oauth2.StaticTokenSource(
// &oauth2.Token{AccessToken: "...github access token..."
// ))
func New(c *http.Client) *GoReadme {
return &GoReadme{client: c}
}
// GoReadme enables getting readme.md text from a go package.
type GoReadme struct {
client *http.Client
config Config
}
type Config struct {
// Override readme title. Default is package name.
Title string `json:"title"`
// ImportPath is used to override the import path. For example: github.com/user/project,
// github.com/user/project/package or github.com/user/project/version.
ImportPath string `json:"import_path"`
// Consts will make constants documentation to be added to the README.
// If Types is specified, constants for each type will also be added to the README.
Consts bool `json:"consts"`
// Vars will make exported variables documentation to be added to the README.
// If Types is specified, exported variables for each type will also be added to the README.
Vars bool `json:"vars"`
// Functions will make functions documentation to be added to the README.
Functions bool `json:"functions"`
// Types will make types documentation to be added to the README.
Types bool `json:"types"`
// Factories will make functions returning a type to be added to the README, if Types is also specified.
// Has no effect if Types is not specified.
Factories bool `json:"factories"`
// Methods will make the methods for a type to be added to the README, if Types is also specified.
// Has no effect if Types is not specified.
Methods bool `json:"methods"`
// SkipExamples will omit the examples section from the README.
SkipExamples bool `json:"skip_examples"`
// SkipSubPackages will omit the sub packages section from the README.
SkipSubPackages bool `json:"skip_sub_packages"`
// NoDiffBlocks disables marking code blocks as diffs if they start with minus or plus signes.
NoDiffBlocks bool `json:"no_diff_blocks"`
// RecursiveSubPackages will retrieved subpackages information recursively.
// If false, only one level of subpackages will be retrieved.
RecursiveSubPackages bool `json:"recursive_sub_packages"`
Badges struct {
TravisCI bool `json:"travis_ci"`
CodeCov bool `json:"code_cov"`
GolangCI bool `json:"golang_ci"`
GoDoc bool `json:"go_doc"`
GoReportCard bool `json:"go_report_card"`
} `json:"badges"`
Credit bool `json:"credit"`
}
// Create writes the content of readme.md to w, with the default client.
// name should be a Go repository name, such as "github.com/posener/goreadme".
func Create(ctx context.Context, name string, w io.Writer) error {
g := GoReadme{client: http.DefaultClient}
return g.Create(ctx, name, w)
}
// WithConfig returns a copy of the converter with the given configuration.
func (r GoReadme) WithConfig(cfg Config) *GoReadme {
r.config = cfg
return &r
}
// Create writes the content of readme.md to w, with r's HTTP client.
// name should be a Go repository name, such as "github.com/posener/goreadme".
func (r *GoReadme) Create(ctx context.Context, name string, w io.Writer) error {
p, err := r.get(ctx, name)
if err != nil {
return err
}
return template.Execute(w, p, r.config, markdown.OptNoDiff(r.config.NoDiffBlocks))
}
// pkg contains information about a go package, to be used in the template.
type pkg struct {
Package *doc.Package
SubPackages []subPkg
}
// subPkg is information about sub package, to be used in the template.
type subPkg struct {
Path string
Package *doc.Package
}
func (r *GoReadme) get(ctx context.Context, name string) (*pkg, error) {
log.Printf("Getting %s", name)
p, err := docGet(ctx, r.client, name, "")
if err != nil {
return nil, errors.Wrapf(err, "failed getting %s", name)
}
sort.Strings(p.Subdirectories)
// If functions were not requested to be added to the readme, add their
// examples to the main readme.
if !r.config.Functions {
for _, f := range p.Funcs {
for _, e := range f.Examples {
if e.Name == "" {
e.Name = f.Name
}
if e.Doc == "" {
e.Doc = f.Doc
}
p.Examples = append(p.Examples, e)
}
}
}
// If types were not requested to be added to the readme, add their
// examples to the main readme.
if !r.config.Types {
for _, f := range p.Types {
for _, e := range f.Examples {
if e.Name == "" {
e.Name = f.Name
}
if e.Doc == "" {
e.Doc = f.Doc
}
p.Examples = append(p.Examples, e)
}
}
}
if p.IsCmd {
// TODO: make this better
p.Name = filepath.Base(name)
p.Doc = strings.TrimPrefix(p.Doc, "Package main is ")
}
if override := r.config.Title; override != "" {
p.Name = override
}
if override := r.config.ImportPath; override != "" {
p.ImportPath = override
}
pkg := &pkg{
Package: p,
}
if !r.config.SkipSubPackages {
f := subpackagesFetcher{
importPath: name,
client: r.client,
recursive: r.config.RecursiveSubPackages,
}
pkg.SubPackages, err = f.Fetch(ctx, p)
if err != nil {
return nil, err
}
}
debug(pkg)
return pkg, nil
}
func debug(p *pkg) {
if os.Getenv("debug") == "" {
return
}
d, _ := json.MarshalIndent(p, " ", " ")
log.Printf("Package data: %s", string(d))
}