mirror of
https://github.com/ipfs/kubo.git
synced 2025-06-28 00:39:31 +08:00
Merge pull request #2785 from ipfs/feature/auto-synopsis
Add synopsis autogenerator
This commit is contained in:
@ -78,7 +78,6 @@ const longHelpFormat = `USAGE
|
||||
{{.Indent}}{{template "usage" .}}
|
||||
|
||||
{{if .Synopsis}}SYNOPSIS
|
||||
|
||||
{{.Synopsis}}
|
||||
|
||||
{{end}}{{if .Arguments}}ARGUMENTS
|
||||
@ -163,6 +162,9 @@ func LongHelp(rootName string, root *cmds.Command, path []string, out io.Writer)
|
||||
if len(fields.Subcommands) == 0 {
|
||||
fields.Subcommands = strings.Join(subcommandText(cmd, rootName, path), "\n")
|
||||
}
|
||||
if len(fields.Synopsis) == 0 {
|
||||
fields.Synopsis = generateSynopsis(cmd, pathStr)
|
||||
}
|
||||
|
||||
// trim the extra newlines (see TrimNewlines doc)
|
||||
fields.TrimNewlines()
|
||||
@ -206,6 +208,9 @@ func ShortHelp(rootName string, root *cmds.Command, path []string, out io.Writer
|
||||
if len(fields.Subcommands) == 0 {
|
||||
fields.Subcommands = strings.Join(subcommandText(cmd, rootName, path), "\n")
|
||||
}
|
||||
if len(fields.Synopsis) == 0 {
|
||||
fields.Synopsis = generateSynopsis(cmd, pathStr)
|
||||
}
|
||||
|
||||
// trim the extra newlines (see TrimNewlines doc)
|
||||
fields.TrimNewlines()
|
||||
@ -216,6 +221,54 @@ func ShortHelp(rootName string, root *cmds.Command, path []string, out io.Writer
|
||||
return shortHelpTemplate.Execute(out, fields)
|
||||
}
|
||||
|
||||
func generateSynopsis(cmd *cmds.Command, path string) string {
|
||||
res := path
|
||||
for _, opt := range cmd.Options {
|
||||
valopt, ok := cmd.Helptext.SynopsisOptionsValues[opt.Names()[0]]
|
||||
if !ok {
|
||||
valopt = opt.Names()[0]
|
||||
}
|
||||
sopt := ""
|
||||
for i, n := range opt.Names() {
|
||||
pre := "-"
|
||||
if len(n) > 1 {
|
||||
pre = "--"
|
||||
}
|
||||
if opt.Type() == cmds.Bool && opt.DefaultVal() == true {
|
||||
pre = "--"
|
||||
sopt = fmt.Sprintf("%s%s=false", pre, n)
|
||||
break
|
||||
} else {
|
||||
if i == 0 {
|
||||
if opt.Type() == cmds.Bool {
|
||||
sopt = fmt.Sprintf("%s%s", pre, n)
|
||||
} else {
|
||||
sopt = fmt.Sprintf("%s%s=<%s>", pre, n, valopt)
|
||||
}
|
||||
} else {
|
||||
sopt = fmt.Sprintf("%s | %s%s", sopt, pre, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
res = fmt.Sprintf("%s [%s]", res, sopt)
|
||||
}
|
||||
if len(cmd.Arguments) > 0 {
|
||||
res = fmt.Sprintf("%s [--]", res)
|
||||
}
|
||||
for _, arg := range cmd.Arguments {
|
||||
sarg := fmt.Sprintf("<%s>", arg.Name)
|
||||
if arg.Variadic {
|
||||
sarg = sarg + "..."
|
||||
}
|
||||
|
||||
if !arg.Required {
|
||||
sarg = fmt.Sprintf("[%s]", sarg)
|
||||
}
|
||||
res = fmt.Sprintf("%s %s", res, sarg)
|
||||
}
|
||||
return strings.Trim(res, " ")
|
||||
}
|
||||
|
||||
func argumentText(cmd *cmds.Command) []string {
|
||||
lines := make([]string, len(cmd.Arguments))
|
||||
|
||||
|
45
commands/cli/helptext_test.go
Normal file
45
commands/cli/helptext_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
cmds "github.com/ipfs/go-ipfs/commands"
|
||||
)
|
||||
|
||||
func TestSynopsisGenerator(t *testing.T) {
|
||||
command := &cmds.Command{
|
||||
Arguments: []cmds.Argument{
|
||||
cmds.StringArg("required", true, false, ""),
|
||||
cmds.StringArg("variadic", false, true, ""),
|
||||
},
|
||||
Options: []cmds.Option{
|
||||
cmds.StringOption("opt", "o", "Option"),
|
||||
},
|
||||
Helptext: cmds.HelpText{
|
||||
SynopsisOptionsValues: map[string]string{
|
||||
"opt": "OPTION",
|
||||
},
|
||||
},
|
||||
}
|
||||
syn := generateSynopsis(command, "cmd")
|
||||
t.Logf("Synopsis is: %s", syn)
|
||||
if !strings.HasPrefix(syn, "cmd ") {
|
||||
t.Fatal("Synopsis should start with command name")
|
||||
}
|
||||
if !strings.Contains(syn, "[--opt=<OPTION> | -o]") {
|
||||
t.Fatal("Synopsis should contain option descriptor")
|
||||
}
|
||||
if !strings.Contains(syn, "<required>") {
|
||||
t.Fatal("Synopsis should contain required argument")
|
||||
}
|
||||
if !strings.Contains(syn, "<variadic>...") {
|
||||
t.Fatal("Synopsis should contain variadic argument")
|
||||
}
|
||||
if !strings.Contains(syn, "[<variadic>...]") {
|
||||
t.Fatal("Synopsis should contain optional argument")
|
||||
}
|
||||
if !strings.Contains(syn, "[--]") {
|
||||
t.Fatal("Synopsis should contain options finalizer")
|
||||
}
|
||||
}
|
@ -36,9 +36,9 @@ type MarshalerMap map[EncodingType]Marshaler
|
||||
// text follows formats similar to man pages, but not exactly the same.
|
||||
type HelpText struct {
|
||||
// required
|
||||
Tagline string // used in <cmd usage>
|
||||
ShortDescription string // used in DESCRIPTION
|
||||
Synopsis string // showcasing the cmd
|
||||
Tagline string // used in <cmd usage>
|
||||
ShortDescription string // used in DESCRIPTION
|
||||
SynopsisOptionsValues map[string]string // mappings for synopsis generator
|
||||
|
||||
// optional - whole section overrides
|
||||
Usage string // overrides USAGE section
|
||||
@ -46,6 +46,7 @@ type HelpText struct {
|
||||
Options string // overrides OPTIONS section
|
||||
Arguments string // overrides ARGUMENTS section
|
||||
Subcommands string // overrides SUBCOMMANDS section
|
||||
Synopsis string // overrides SYNOPSIS field
|
||||
}
|
||||
|
||||
// Command is a runnable command, with input arguments and options (flags).
|
||||
|
@ -189,7 +189,7 @@ const (
|
||||
|
||||
// options that are used by this package
|
||||
var OptionEncodingType = StringOption(EncLong, EncShort, "The encoding type the output should be encoded with (json, xml, or text)")
|
||||
var OptionRecursivePath = BoolOption(RecLong, RecShort, "Add directory paths recursively")
|
||||
var OptionRecursivePath = BoolOption(RecLong, RecShort, "Add directory paths recursively").Default(false)
|
||||
var OptionStreamChannels = BoolOption(ChanOpt, "Stream channel output")
|
||||
var OptionTimeout = StringOption(TimeoutOpt, "set a global timeout on the command")
|
||||
|
||||
|
@ -22,7 +22,7 @@ Lists running and recently run commands.
|
||||
res.SetOutput(req.InvocContext().ReqLog.Report())
|
||||
},
|
||||
Options: []cmds.Option{
|
||||
cmds.BoolOption("v", "verbose", "Print extra information.").Default(false),
|
||||
cmds.BoolOption("verbose", "v", "Print extra information.").Default(false),
|
||||
},
|
||||
Subcommands: map[string]*cmds.Command{
|
||||
"clear": clearInactiveCmd,
|
||||
|
@ -40,7 +40,7 @@ operations.
|
||||
`,
|
||||
},
|
||||
Options: []cmds.Option{
|
||||
cmds.BoolOption("f", "flush", "Flush target and ancestors after write. Default: true."),
|
||||
cmds.BoolOption("flush", "f", "Flush target and ancestors after write. Default: true."),
|
||||
},
|
||||
Subcommands: map[string]*cmds.Command{
|
||||
"read": FilesReadCmd,
|
||||
@ -397,8 +397,8 @@ Examples:
|
||||
cmds.StringArg("path", true, false, "Path to file to be read."),
|
||||
},
|
||||
Options: []cmds.Option{
|
||||
cmds.IntOption("o", "offset", "Byte offset to begin reading from."),
|
||||
cmds.IntOption("n", "count", "Maximum number of bytes to read."),
|
||||
cmds.IntOption("offset", "o", "Byte offset to begin reading from."),
|
||||
cmds.IntOption("count", "n", "Maximum number of bytes to read."),
|
||||
},
|
||||
Run: func(req cmds.Request, res cmds.Response) {
|
||||
n, err := req.InvocContext().GetNode()
|
||||
@ -565,10 +565,10 @@ stat' on the file or any of its ancestors.
|
||||
cmds.FileArg("data", true, false, "Data to write.").EnableStdin(),
|
||||
},
|
||||
Options: []cmds.Option{
|
||||
cmds.IntOption("o", "offset", "Byte offset to begin writing at."),
|
||||
cmds.BoolOption("e", "create", "Create the file if it does not exist."),
|
||||
cmds.BoolOption("t", "truncate", "Truncate the file to size zero before writing."),
|
||||
cmds.IntOption("n", "count", "Maximum number of bytes to read."),
|
||||
cmds.IntOption("offset", "o", "Byte offset to begin writing at."),
|
||||
cmds.BoolOption("create", "e", "Create the file if it does not exist."),
|
||||
cmds.BoolOption("truncate", "t", "Truncate the file to size zero before writing."),
|
||||
cmds.IntOption("count", "n", "Maximum number of bytes to read."),
|
||||
},
|
||||
Run: func(req cmds.Request, res cmds.Response) {
|
||||
path, err := checkPath(req.Arguments()[0])
|
||||
@ -678,7 +678,7 @@ Examples:
|
||||
cmds.StringArg("path", true, false, "Path to dir to make."),
|
||||
},
|
||||
Options: []cmds.Option{
|
||||
cmds.BoolOption("p", "parents", "No error if existing, make parent directories as needed."),
|
||||
cmds.BoolOption("parents", "p", "No error if existing, make parent directories as needed."),
|
||||
},
|
||||
Run: func(req cmds.Request, res cmds.Response) {
|
||||
n, err := req.InvocContext().GetNode()
|
||||
@ -758,7 +758,7 @@ Remove files or directories.
|
||||
cmds.StringArg("path", true, true, "File to remove."),
|
||||
},
|
||||
Options: []cmds.Option{
|
||||
cmds.BoolOption("r", "recursive", "Recursively remove directories."),
|
||||
cmds.BoolOption("recursive", "r", "Recursively remove directories."),
|
||||
},
|
||||
Run: func(req cmds.Request, res cmds.Response) {
|
||||
nd, err := req.InvocContext().GetNode()
|
||||
|
@ -16,9 +16,6 @@ import (
|
||||
var MountCmd = &cmds.Command{
|
||||
Helptext: cmds.HelpText{
|
||||
Tagline: "Mounts IPFS to the filesystem (read-only).",
|
||||
Synopsis: `
|
||||
ipfs mount [-f <ipfs mount path>] [-n <ipns mount path>]
|
||||
`,
|
||||
ShortDescription: `
|
||||
Mount ipfs at a read-only mountpoint on the OS (default: /ipfs and /ipns).
|
||||
All ipfs objects will be accessible under that directory. Note that the
|
||||
|
@ -251,7 +251,7 @@ to a file containing 'bar', and returns the hash of the new object.
|
||||
cmds.StringArg("ref", true, false, "IPFS object to add link to."),
|
||||
},
|
||||
Options: []cmds.Option{
|
||||
cmds.BoolOption("p", "create", "Create intermediary nodes.").Default(false),
|
||||
cmds.BoolOption("create", "p", "Create intermediary nodes.").Default(false),
|
||||
},
|
||||
Run: func(req cmds.Request, res cmds.Response) {
|
||||
nd, err := req.InvocContext().GetNode()
|
||||
|
@ -28,8 +28,7 @@ type PingResult struct {
|
||||
|
||||
var PingCmd = &cmds.Command{
|
||||
Helptext: cmds.HelpText{
|
||||
Tagline: "Send echo request packets to IPFS hosts.",
|
||||
Synopsis: "ipfs ping <peerId> [--count <int>| -n]",
|
||||
Tagline: "Send echo request packets to IPFS hosts.",
|
||||
ShortDescription: `
|
||||
'ipfs ping' is a tool to test sending data to other nodes. It finds nodes
|
||||
via the routing system, sends pings, waits for pongs, and prints out round-
|
||||
|
@ -19,10 +19,8 @@ const (
|
||||
|
||||
var Root = &cmds.Command{
|
||||
Helptext: cmds.HelpText{
|
||||
Tagline: "Global p2p merkle-dag filesystem.",
|
||||
Synopsis: `
|
||||
ipfs [<flags>] <command> [<arg>] ...
|
||||
`,
|
||||
Tagline: "Global p2p merkle-dag filesystem.",
|
||||
Synopsis: "ipfs [--config=<config> | -c] [--debug=<debug> | -D] [--help=<help>] [-h=<h>] [--local=<local> | -L] [--api=<api>] <command> ...",
|
||||
Subcommands: `
|
||||
BASIC COMMANDS
|
||||
init Initialize ipfs local configuration
|
||||
|
@ -18,8 +18,7 @@ import (
|
||||
|
||||
var StatsCmd = &cmds.Command{
|
||||
Helptext: cmds.HelpText{
|
||||
Tagline: "Query ipfs statistics.",
|
||||
Synopsis: "ipfs stats <command>",
|
||||
Tagline: "Query ipfs statistics.",
|
||||
ShortDescription: `'ipfs stats' is a set of commands to help look at statistics
|
||||
for your ipfs node.
|
||||
`,
|
||||
@ -37,9 +36,6 @@ for your ipfs node.`,
|
||||
var statBwCmd = &cmds.Command{
|
||||
Helptext: cmds.HelpText{
|
||||
Tagline: "Print ipfs bandwidth information.",
|
||||
Synopsis: `ipfs stats bw [--peer <peerId> | -p] [--proto <protocol> | -t] [--poll]
|
||||
[--interval <timeInterval> | -i]
|
||||
`,
|
||||
ShortDescription: `'ipfs stats bw' prints bandwidth information for the ipfs daemon.
|
||||
It displays: TotalIn, TotalOut, RateIn, RateOut.
|
||||
`,
|
||||
|
@ -36,7 +36,6 @@ type LsOutput struct {
|
||||
var LsCmd = &cmds.Command{
|
||||
Helptext: cmds.HelpText{
|
||||
Tagline: "List directory contents for Unix filesystem objects.",
|
||||
Synopsis: "ipfs file ls <path>",
|
||||
ShortDescription: `
|
||||
Displays the contents of an IPFS or IPNS object(s) at the given path.
|
||||
|
||||
|
@ -5,13 +5,12 @@ import cmds "github.com/ipfs/go-ipfs/commands"
|
||||
var UnixFSCmd = &cmds.Command{
|
||||
Helptext: cmds.HelpText{
|
||||
Tagline: "Interact with ipfs objects representing Unix filesystems.",
|
||||
Synopsis: "ipfs file <command>",
|
||||
ShortDescription: `
|
||||
'ipfs file' provides a familiar interface to file systems represented
|
||||
by IPFS objects, which hides IPFS implementation details like layout
|
||||
objects (e.g. fanout and chunking).
|
||||
`,
|
||||
LongDescription: `
|
||||
LongDescription: `
|
||||
'ipfs file' provides a familiar interface to file systems represented
|
||||
by IPFS objects, which hides IPFS implementation details like layout
|
||||
objects (e.g. fanout and chunking).
|
||||
|
@ -35,7 +35,7 @@ test_expect_success "ipfs help succeeds" '
|
||||
|
||||
test_expect_success "ipfs help output looks good" '
|
||||
egrep -i "^Usage" help.txt >/dev/null &&
|
||||
egrep "ipfs .* <command>" help.txt >/dev/null ||
|
||||
egrep "ipfs <command>" help.txt >/dev/null ||
|
||||
test_fsh cat help.txt
|
||||
'
|
||||
|
||||
|
@ -8,19 +8,6 @@ test_description="Test add and cat commands"
|
||||
|
||||
. lib/test-lib.sh
|
||||
|
||||
client_err_add() {
|
||||
printf "$@\n\n"
|
||||
echo 'USAGE
|
||||
ipfs add <path>... - Add a file to ipfs.
|
||||
|
||||
Adds contents of <path> to ipfs. Use -r to add directories.
|
||||
Note that directories are added recursively, to form the ipfs
|
||||
MerkleDAG.
|
||||
|
||||
Use '"'"'ipfs add --help'"'"' for more information about this command.
|
||||
'
|
||||
}
|
||||
|
||||
test_add_cat_file() {
|
||||
test_expect_success "ipfs add succeeds" '
|
||||
echo "Hello Worlds!" >mountdir/hello.txt &&
|
||||
@ -176,9 +163,10 @@ test_add_named_pipe() {
|
||||
test_expect_success "useful error message when adding a named pipe" '
|
||||
mkfifo named-pipe &&
|
||||
test_expect_code 1 ipfs add named-pipe 2>actual &&
|
||||
client_err_add "Error: Unrecognized file type for named-pipe: $(generic_stat named-pipe)" >expected &&
|
||||
rm named-pipe &&
|
||||
test_cmp expected actual
|
||||
grep "Error: Unrecognized file type for named-pipe: $(generic_stat named-pipe)" actual &&
|
||||
grep USAGE actual &&
|
||||
grep "ipfs add" actual
|
||||
'
|
||||
|
||||
test_expect_success "useful error message when recursively adding a named pipe" '
|
||||
|
Reference in New Issue
Block a user