Skip to content

Commit

Permalink
add man support
Browse files Browse the repository at this point in the history
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
  • Loading branch information
crazy-max committed Jun 18, 2022
1 parent 4baa8f9 commit c9aa829
Show file tree
Hide file tree
Showing 14 changed files with 421 additions and 185 deletions.
75 changes: 72 additions & 3 deletions clidocstool.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ package clidocstool
import (
"errors"
"io"
"log"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)

// Options defines options for cli-docs-tool
Expand All @@ -28,6 +32,8 @@ type Options struct {
SourceDir string
TargetDir string
Plugin bool

ManHeader *doc.GenManHeader
}

// Client represents an active cli-docs-tool object
Expand All @@ -36,6 +42,8 @@ type Client struct {
source string
target string
plugin bool

manHeader *doc.GenManHeader
}

// New initializes a new cli-docs-tool client
Expand All @@ -47,9 +55,10 @@ func New(opts Options) (*Client, error) {
return nil, errors.New("source dir required")
}
c := &Client{
root: opts.Root,
source: opts.SourceDir,
plugin: opts.Plugin,
root: opts.Root,
source: opts.SourceDir,
plugin: opts.Plugin,
manHeader: opts.ManHeader,
}
if len(opts.TargetDir) == 0 {
c.target = c.source
Expand All @@ -72,9 +81,69 @@ func (c *Client) GenAllTree() error {
if err = c.GenYamlTree(c.root); err != nil {
return err
}
if err = c.GenManTree(c.root); err != nil {
return err
}
return nil
}

// loadLongDescription gets long descriptions and examples from markdown.
func (c *Client) loadLongDescription(parentCmd *cobra.Command, generator string) error {
for _, cmd := range parentCmd.Commands() {
if cmd.HasSubCommands() {
if err := c.loadLongDescription(cmd, generator); err != nil {
return err
}
}
name := cmd.CommandPath()
if i := strings.Index(name, " "); c.plugin && i >= 0 {
// remove root command / binary name
name = name[i+1:]
}
if name == "" {
continue
}
mdFile := strings.ReplaceAll(name, " ", "_") + ".md"
sourcePath := filepath.Join(c.source, mdFile)
content, err := os.ReadFile(sourcePath)
if os.IsNotExist(err) {
log.Printf("WARN: %s does not exist, skipping Markdown examples for %s docs\n", mdFile, generator)
continue
}
if err != nil {
return err
}
applyDescriptionAndExamples(cmd, string(content))
}
return nil
}

// applyDescriptionAndExamples fills in cmd.Long and cmd.Example with the
// "Description" and "Examples" H2 sections in mdString (if present).
func applyDescriptionAndExamples(cmd *cobra.Command, mdString string) {
sections := getSections(mdString)
var (
anchors []string
md string
)
if sections["description"] != "" {
md, anchors = cleanupMarkDown(sections["description"])
cmd.Long = md
anchors = append(anchors, md)
}
if sections["examples"] != "" {
md, anchors = cleanupMarkDown(sections["examples"])
cmd.Example = md
anchors = append(anchors, md)
}
if len(anchors) > 0 {
if cmd.Annotations == nil {
cmd.Annotations = make(map[string]string)
}
cmd.Annotations["anchors"] = strings.Join(anchors, ",")
}
}

func fileExists(f string) bool {
info, err := os.Stat(f)
if os.IsNotExist(err) {
Expand Down
74 changes: 74 additions & 0 deletions clidocstool_man.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2016 cli-docs-tool authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package clidocstool

import (
"fmt"
"log"
"os"
"strconv"
"time"

"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)

// GenManTree generates a man page for the command and all descendants.
// If SOURCE_DATE_EPOCH is set, in order to allow reproducible package
// builds, we explicitly set the build time to SOURCE_DATE_EPOCH.
func (c *Client) GenManTree(cmd *cobra.Command) error {
if err := c.loadLongDescription(cmd, "man"); err != nil {
return err
}

if epoch := os.Getenv("SOURCE_DATE_EPOCH"); c.manHeader != nil && epoch != "" {
unixEpoch, err := strconv.ParseInt(epoch, 10, 64)
if err != nil {
return fmt.Errorf("invalid SOURCE_DATE_EPOCH: %v", err)
}
now := time.Unix(unixEpoch, 0)
c.manHeader.Date = &now
}

return c.genManTreeCustom(cmd)
}

func (c *Client) genManTreeCustom(cmd *cobra.Command) error {
for _, sc := range cmd.Commands() {
if err := c.genManTreeCustom(sc); err != nil {
return err
}
}

// always disable the addition of [flags] to the usage
cmd.DisableFlagsInUseLine = true

// always disable "spf13/cobra" auto gen tag
cmd.DisableAutoGenTag = true

// Skip the root command altogether, to prevent generating a useless
// md file for plugins.
if c.plugin && !cmd.HasParent() {
return nil
}

log.Printf("INFO: Generating Man for %q", cmd.CommandPath())

return doc.GenManTreeFromOpts(cmd, doc.GenManTreeOptions{
Header: c.manHeader,
Path: c.target,
CommandSeparator: "-",
})
}
67 changes: 67 additions & 0 deletions clidocstool_man_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2021 cli-docs-tool authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package clidocstool

import (
"os"
"path"
"path/filepath"
"strconv"
"testing"
"time"

"github.com/spf13/cobra/doc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

//nolint:errcheck
func TestGenManTree(t *testing.T) {
tmpdir := t.TempDir()

epoch, err := time.Parse("2006-Jan-02", "2020-Jan-10")
require.NoError(t, err)

os.Setenv("SOURCE_DATE_EPOCH", strconv.FormatInt(epoch.Unix(), 10))
defer os.Unsetenv("SOURCE_DATE_EPOCH")

require.NoError(t, copyFile(path.Join("fixtures", "buildx_stop.pre.md"), path.Join(tmpdir, "buildx_stop.md")))

c, err := New(Options{
Root: buildxCmd,
SourceDir: tmpdir,
Plugin: true,
ManHeader: &doc.GenManHeader{
Title: "DOCKER",
Section: "1",
Source: "Docker Community",
Manual: "Docker User Manuals",
},
})
require.NoError(t, err)
require.NoError(t, c.GenManTree(buildxCmd))

for _, tt := range []string{"docker-buildx.1", "docker-buildx-build.1", "docker-buildx-stop.1"} {
tt := tt
t.Run(tt, func(t *testing.T) {
bres, err := os.ReadFile(filepath.Join(tmpdir, tt))
require.NoError(t, err)

bexc, err := os.ReadFile(path.Join("fixtures", tt))
require.NoError(t, err)
assert.Equal(t, string(bexc), string(bres))
})
}
}
9 changes: 8 additions & 1 deletion clidocstool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

"github.com/docker/cli-docs-tool/annotation"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -184,11 +185,17 @@ func TestGenAllTree(t *testing.T) {
Root: buildxCmd,
SourceDir: tmpdir,
Plugin: true,
ManHeader: &doc.GenManHeader{
Title: "DOCKER",
Section: "1",
Source: "Docker Community",
Manual: "Docker User Manuals",
},
})
require.NoError(t, err)
require.NoError(t, c.GenAllTree())

for _, tt := range []string{"buildx.md", "buildx_build.md", "buildx_stop.md", "docker_buildx.yaml", "docker_buildx_build.yaml", "docker_buildx_stop.yaml"} {
for _, tt := range []string{"buildx.md", "buildx_build.md", "buildx_stop.md", "docker_buildx.yaml", "docker_buildx_build.yaml", "docker_buildx_stop.yaml", "docker-buildx.1", "docker-buildx-build.1", "docker-buildx-stop.1"} {
tt := tt
t.Run(tt, func(t *testing.T) {
bres, err := os.ReadFile(filepath.Join(tmpdir, tt))
Expand Down
59 changes: 1 addition & 58 deletions clidocstool_yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ type cmdDoc struct {
// it is undefined which help output will be in the file `cmd-sub-third.1`.
func (c *Client) GenYamlTree(cmd *cobra.Command) error {
emptyStr := func(s string) string { return "" }
if err := c.loadLongDescription(cmd); err != nil {
if err := c.loadLongDescription(cmd, "yaml"); err != nil {
return err
}
return c.genYamlTreeCustom(cmd, emptyStr)
Expand Down Expand Up @@ -369,63 +369,6 @@ func hasSeeAlso(cmd *cobra.Command) bool {
return false
}

// loadLongDescription gets long descriptions and examples from markdown.
func (c *Client) loadLongDescription(parentCmd *cobra.Command) error {
for _, cmd := range parentCmd.Commands() {
if cmd.HasSubCommands() {
if err := c.loadLongDescription(cmd); err != nil {
return err
}
}
name := cmd.CommandPath()
if i := strings.Index(name, " "); i >= 0 {
// remove root command / binary name
name = name[i+1:]
}
if name == "" {
continue
}
mdFile := strings.ReplaceAll(name, " ", "_") + ".md"
sourcePath := filepath.Join(c.source, mdFile)
content, err := os.ReadFile(sourcePath)
if os.IsNotExist(err) {
log.Printf("WARN: %s does not exist, skipping Markdown examples for YAML doc\n", mdFile)
continue
}
if err != nil {
return err
}
applyDescriptionAndExamples(cmd, string(content))
}
return nil
}

// applyDescriptionAndExamples fills in cmd.Long and cmd.Example with the
// "Description" and "Examples" H2 sections in mdString (if present).
func applyDescriptionAndExamples(cmd *cobra.Command, mdString string) {
sections := getSections(mdString)
var (
anchors []string
md string
)
if sections["description"] != "" {
md, anchors = cleanupMarkDown(sections["description"])
cmd.Long = md
anchors = append(anchors, md)
}
if sections["examples"] != "" {
md, anchors = cleanupMarkDown(sections["examples"])
cmd.Example = md
anchors = append(anchors, md)
}
if len(anchors) > 0 {
if cmd.Annotations == nil {
cmd.Annotations = make(map[string]string)
}
cmd.Annotations["anchors"] = strings.Join(anchors, ",")
}
}

type byName []*cobra.Command

func (s byName) Len() int { return len(s) }
Expand Down
1 change: 1 addition & 0 deletions example/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/docker/cli-docs-tool/example
go 1.16

require (
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/docker/buildx v0.6.3
github.com/docker/cli v20.10.7+incompatible
github.com/docker/cli-docs-tool v0.0.0
Expand Down
4 changes: 4 additions & 0 deletions example/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
Expand Down Expand Up @@ -746,6 +748,8 @@ github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYe
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
Expand Down
Loading

0 comments on commit c9aa829

Please sign in to comment.