Skip to content

Commit

Permalink
Add DocGen command (#1688)
Browse files Browse the repository at this point in the history
* Added all needed pieces to build feature.

* Added additional logic.

* Update usage and long help for command.

* Add tests for docgen, but still need to verify generated contents.

* Finish tests for docgen command.

* Lint code.

* Changelog entry.

* Fix typo in test.

* Add test for invalid target directory.

* Add tests for invalid flag values.

* Rename rest to rst to improve clarity.

---------

Co-authored-by: Ira Miller <[email protected]>
  • Loading branch information
Taztingo and iramiller authored Sep 27, 2023
1 parent 7eb06e5 commit 28948e8
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
Management of holds is internal, but there are queries for looking up holds on accounts.
Holds are also reflected in the `x/bank` module's `SpendableBalances` query.
* Add new MaxSupply param to marker module and deprecate MaxTotalSupply. [#1292](https://github.com/provenance-io/provenance/issues/1292).
* Add hidden docgen command to output documentation in different formats. [#1468](https://github.com/provenance-io/provenance/issues/1468).

### Improvements

Expand Down
103 changes: 103 additions & 0 deletions cmd/provenanced/cmd/docgen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package cmd

import (
"fmt"
"os"

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

"github.com/cosmos/cosmos-sdk/version"
)

var docGenCmdStart = fmt.Sprintf("%s docgen", version.AppName)

const (
FlagMarkdown = "markdown"
FlagYaml = "yaml"
FlagRst = "rst"
FlagManpage = "manpage"
)

func GetDocGenCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "docgen <target directory> (--markdown) (--yaml) (--rst) (--manpages) [flags]",
Short: "Generates cli documentation for the Provenance Blockchain.",
Long: `Generates cli documentation for the Provenance Blockchain.
Various documentation formats can be generated, including markdown, YAML, RST, and man pages.
To ensure the command's success, you must specify at least one format.
A successful command will not only generate files in the selected formats but also create the target directory if it doesn't already exist.`,
Example: fmt.Sprintf("%s '/tmp' --yaml --markdown", docGenCmdStart),
Hidden: true,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
markdown, err := cmd.Flags().GetBool(FlagMarkdown)
if err != nil {
return err
}
yaml, err := cmd.Flags().GetBool(FlagYaml)
if err != nil {
return err
}
rst, err := cmd.Flags().GetBool(FlagRst)
if err != nil {
return err
}
manpage, err := cmd.Flags().GetBool(FlagManpage)
if err != nil {
return err
}

if !markdown && !yaml && !rst && !manpage {
return fmt.Errorf("at least one doc type must be specified")
}

dir := args[0]
if !exists(dir) {
err = os.Mkdir(dir, 0755)
if err != nil {
return err
}
}

if markdown {
err = doc.GenMarkdownTree(cmd.Root(), dir)
if err != nil {
return err
}
}
if yaml {
err = doc.GenYamlTree(cmd.Root(), dir)
if err != nil {
return err
}
}
if rst {
err = doc.GenReSTTree(cmd.Root(), dir)
if err != nil {
return err
}
}
if manpage {
err = doc.GenManTree(cmd.Root(), nil, dir)
if err != nil {
return err
}
}

return nil
},
}

cmd.Flags().Bool(FlagMarkdown, false, "Generate documentation in the format of markdown pages.")
cmd.Flags().Bool(FlagYaml, false, "Generate documentation in the format of yaml.")
cmd.Flags().Bool(FlagRst, false, "Generate documentation in the format of rst.")
cmd.Flags().Bool(FlagManpage, false, "Generate documentation in the format of manpages.")

return cmd
}

func exists(dir string) bool {
_, err := os.Stat(dir)
return err == nil
}
186 changes: 186 additions & 0 deletions cmd/provenanced/cmd/docgen_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package cmd_test

import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"testing"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/server"
sdksim "github.com/cosmos/cosmos-sdk/simapp"
genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil"
provenancecmd "github.com/provenance-io/provenance/cmd/provenanced/cmd"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/libs/log"
)

func TestDocGen(t *testing.T) {
tests := []struct {
name string
target string
createTarget bool
flags []string
err string
extensions []string
}{
{
name: "failure - no flags specified",
target: "tmp",
createTarget: true,
err: "at least one doc type must be specified",
},
{
name: "failure - unsupported flag format",
target: "tmp",
flags: []string{"--bad"},
createTarget: true,
err: "unknown flag: --bad",
},
{
name: "failure - invalid target directory",
target: "/tmp/tmp2/tmp3",
flags: []string{"--yaml"},
createTarget: false,
err: "mkdir %s: no such file or directory",
},
{
name: "failure - bad yaml value",
target: "tmp",
createTarget: true,
flags: []string{"--yaml=xyz"},
err: "invalid argument \"xyz\" for \"--yaml\" flag: strconv.ParseBool: parsing \"xyz\": invalid syntax",
},
{
name: "failure - bad rst value",
target: "tmp",
createTarget: true,
flags: []string{"--rst=xyz"},
err: "invalid argument \"xyz\" for \"--rst\" flag: strconv.ParseBool: parsing \"xyz\": invalid syntax",
},
{
name: "failure - bad markdown value",
target: "tmp",
createTarget: true,
flags: []string{"--markdown=xyz"},
err: "invalid argument \"xyz\" for \"--markdown\" flag: strconv.ParseBool: parsing \"xyz\": invalid syntax",
},
{
name: "failure - bad manpage value",
target: "tmp",
createTarget: true,
flags: []string{"--manpage=xyz"},
err: "invalid argument \"xyz\" for \"--manpage\" flag: strconv.ParseBool: parsing \"xyz\": invalid syntax",
},
{
name: "success - yaml is generated",
target: "tmp",
createTarget: true,
flags: []string{"--yaml"},
extensions: []string{".yaml"},
},
{
name: "success - rst is generated",
target: "tmp",
createTarget: true,
flags: []string{"--rst"},
extensions: []string{".rst"},
},
{
name: "success - manpage is generated",
target: "tmp",
createTarget: true,
flags: []string{"--manpage"},
extensions: []string{".1"},
},
{
name: "success - markdown is generated",
target: "tmp",
createTarget: true,
flags: []string{"--markdown"},
extensions: []string{".md"},
},
{
name: "success - multiple types supported",
target: "tmp",
createTarget: true,
flags: []string{"--markdown", "--yaml"},
extensions: []string{".md", ".yaml"},
},
{
name: "success - generates a new directory",
target: "tmp2",
createTarget: false,
flags: []string{"--yaml"},
extensions: []string{".md", ".yaml"},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
home := t.TempDir()

targetPath := filepath.Join(home, tc.target)
if tc.createTarget {
require.NoError(t, os.Mkdir(targetPath, 0755), "Mkdir successfully created directory")
}

logger := log.NewNopLogger()
cfg, err := genutiltest.CreateDefaultTendermintConfig(home)
require.NoError(t, err, "Created default tendermint config")

appCodec := sdksim.MakeTestEncodingConfig().Codec
err = genutiltest.ExecInitCmd(testMbm, home, appCodec)
require.NoError(t, err, "Executed init command")

serverCtx := server.NewContext(viper.New(), cfg, logger)
clientCtx := client.Context{}.WithCodec(appCodec).WithHomeDir(home)

ctx := context.Background()
ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx)
ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx)

cmd := provenancecmd.GetDocGenCmd()
args := append([]string{targetPath}, tc.flags...)
cmd.SetArgs(args)

if len(tc.err) > 0 {
err := cmd.ExecuteContext(ctx)
require.Error(t, err, "should throw an error")
expected := tc.err
if strings.Contains(expected, "%s") {
expected = fmt.Sprintf(expected, targetPath)
}
require.Equal(t, expected, err.Error(), "should return the correct error")
files, err := os.ReadDir(targetPath)
if err != nil {
require.Equal(t, 0, len(files), "should not generate files when failed")
}
} else {
err := cmd.ExecuteContext(ctx)
require.NoError(t, err, "should not return an error")

files, err := os.ReadDir(targetPath)
require.NoError(t, err, "ReadDir should not return an error")
require.NotZero(t, len(files), "should generate files when successful")

for _, file := range files {
ext := filepath.Ext(file.Name())

contains := false
for _, extension := range tc.extensions {
contains = contains || ext == extension
}
require.True(t, contains, "should generate files with correct extension")
}
}

if _, err := os.Stat(targetPath); err != nil {
require.NoError(t, os.RemoveAll(targetPath), "RemoveAll should be able to remove the temporary target directory")
}
})
}
}
1 change: 1 addition & 0 deletions cmd/provenanced/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) {
AddMetaAddressCmd(),
snapshot.Cmd(newApp),
GetPreUpgradeCmd(),
GetDocGenCmd(),
)

fixDebugPubkeyRawTypeFlag(rootCmd)
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ require (
github.com/cosmos/gorocksdb v1.2.0 // indirect
github.com/cosmos/iavl v0.19.6 // indirect
github.com/cosmos/ledger-cosmos-go v0.12.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/creachadair/taskgroup v0.3.2 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
Expand Down Expand Up @@ -144,6 +145,7 @@ require (
github.com/prometheus/procfs v0.9.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rs/cors v1.8.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ github.com/cosmos/ledger-cosmos-go v0.12.2 h1:/XYaBlE2BJxtvpkHiBm97gFGSGmYGKunKy
github.com/cosmos/ledger-cosmos-go v0.12.2/go.mod h1:ZcqYgnfNJ6lAXe4HPtWgarNEY+B74i+2/8MhZw4ziiI=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
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.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM=
github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk=
Expand Down Expand Up @@ -1031,6 +1032,7 @@ github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
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/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
Expand Down

0 comments on commit 28948e8

Please sign in to comment.