Skip to content

Commit

Permalink
Options to show detailed help for script options
Browse files Browse the repository at this point in the history
including the full descriptions of the options, the possible values,
and the default values.

To show the detailed help of a script:

    dp2 help COMMAND --verbose

To show the detailed help of a single option:

    dp2 help COMMAND OPTION
  • Loading branch information
bertfrees committed Feb 5, 2019
1 parent 218fcf6 commit bef26d0
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 16 deletions.
86 changes: 72 additions & 14 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,28 @@ Usage: {{.Parent.Name}} [GLOBAL_OPTIONS] {{.Name}}{{if .MandatoryFlags}} REQUIRE
{{range .NonMandatoryFlags }} {{flagAligner .FlagStringPrefix}} {{.ShortDesc}}
{{end}}{{end}}
List of global options: {{.Parent.Name}} help -g
Detailed help: {{.Parent.Name}} help --verbose {{.Name}}
{{if .Flags}}Detailed help for a single option: {{.Parent.Name}} help {{.Name}} OPTION
{{end}}
`

COMMAND_DETAILED_HELP_TEMPLATE = `
Usage: {{.Parent.Name}} [GLOBAL_OPTIONS] {{.Name}}{{if .MandatoryFlags}} REQUIRED_OPTIONS{{end}}{{if .MandatoryFlags}} [OPTIONS]{{end}} {{ .Arity.Description}}
{{.Description}}
{{if .MandatoryFlags}}Required options:
{{range .MandatoryFlags }} {{.FlagStringPrefix}}
{{indent .LongDesc}}
{{end}}{{end}}{{if .NonMandatoryFlags}}{{if .MandatoryFlags}}Other options{{end}}{{if not .MandatoryFlags}}Options{{end}}:
{{range .NonMandatoryFlags }} {{.FlagStringPrefix}}
{{indent .LongDesc}}
{{end}}{{end}}
List of global options: {{.Parent.Name}} help -g
`

GLOBAL_OPTIONS_TEMPLATE = `
Expand Down Expand Up @@ -128,17 +150,22 @@ func NewCli(name string, link *PipelineLink) (cli *Cli, err error) {
func (c *Cli) setHelp() {
globals := false
admin := false
details := false
cmd := c.Parser.SetHelp("help", "Help description", func(help string, args ...string) error {
return printHelp(*c, globals, admin, args...)
return printHelp(*c, globals, admin, details, args...)
})
cmd.AddSwitch("globals", "g", "Show global options", func(string, string) error {
globals = true
return nil
})
cmd.AddSwitch("admin", "a", "showadmin options", func(string, string) error {
cmd.AddSwitch("admin", "a", "Show admin options", func(string, string) error {
admin = true
return nil
})
cmd.AddSwitch("verbose", "", "Show detailed help", func(string, string) error {
details = true
return nil
})
}

//Adds the configuration global options to the parser
Expand Down Expand Up @@ -237,13 +264,12 @@ func (c *Cli) Printf(format string, vals ...interface{}) {
}

//prints the help
func printHelp(cli Cli, globals, admin bool, args ...string) error {
func printHelp(cli Cli, globals, admin, details bool, args ...string) error {
if globals {
funcMap := template.FuncMap{
"flagAligner": aligner(flagsToStrings(cli.Flags())),
}
tmpl := template.Must(template.New("globals").Funcs(funcMap).Parse(GLOBAL_OPTIONS_TEMPLATE))
tmpl.Execute(os.Stdout, cli)
template.Must(template.New("globals").Funcs(funcMap).Parse(GLOBAL_OPTIONS_TEMPLATE)).Execute(os.Stdout, cli)

} else if len(args) == 0 {
funcMap := template.FuncMap{
Expand All @@ -253,23 +279,43 @@ func printHelp(cli Cli, globals, admin bool, args ...string) error {
if admin {
tmplName = ADMIN_HELP_TEMPLATE
}
tmpl := template.Must(template.New("mainHelp").Funcs(funcMap).Parse(tmplName))
tmpl.Execute(os.Stdout, cli)
template.Must(template.New("mainHelp").Funcs(funcMap).Parse(tmplName)).Execute(os.Stdout, cli)

} else {
if len(args) > 1 {
return fmt.Errorf("help: only one parameter is accepted. %v found (%v)", len(args), strings.Join(args, ","))
if len(args) > 2 {
return fmt.Errorf("help: only one or two parameters accepted. %v found (%v)", len(args), strings.Join(args, ","))
}
cmd, ok := cli.Parser.Commands[args[0]]
if !ok {
return fmt.Errorf("help: command %v not found ", args[0])
}
funcMap := template.FuncMap{
"flagAligner": aligner(flagsToStrings(cmd.Flags())),
if len(args) == 1 {
funcMap := template.FuncMap{
"flagAligner": aligner(flagsToStrings(cmd.Flags())),
"indent": func(s string) string {
margin := " "
return margin + indent(s, margin)
},
}
tmpl := COMMAND_HELP_TEMPLATE
if details {
tmpl = COMMAND_DETAILED_HELP_TEMPLATE
}
template.Must(template.New("commandHelp").Funcs(funcMap).Parse(tmpl)).Execute(os.Stdout, cmd)
} else {
for _, flag := range cmd.Flags() {
if flag.Long == args[1] {
help := flag.FlagStringPrefix()
if !flag.Mandatory {
help = "[" + help + "]"
}
help = "\nUsage: " + cli.Parser.Name + " " + cmd.Name + " " + help + " ...\n\n" + flag.LongDesc + "\n\n"
fmt.Fprintf(os.Stdout, help)
return nil
}
}
return fmt.Errorf("help: %v option %v not found ", cmd.Name, args[1])
}
tmpl := template.Must(template.New("commandHelp").Funcs(funcMap).Parse(COMMAND_HELP_TEMPLATE))
//cmdFlag := commmandFlag{*cmd, cli.Name}
tmpl.Execute(os.Stdout, cmd)
}
return nil
}
Expand Down Expand Up @@ -300,3 +346,15 @@ func realLen(s string) int {
func uncolor(s string) string {
return regexp.MustCompile(`\x1b\[[0-9;]*m`).ReplaceAllString(s, "")
}

// hanging indent
func indent(s string, indent string) string {
parts := strings.Split(s, "\n")
result := parts[0]
for _, part := range parts[1:] {
result += "\n"
result += indent
result += part
}
return result
}
4 changes: 2 additions & 2 deletions cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,11 @@ func TestPrintHelpErrors(t *testing.T) {
}
cli.AddScripts([]pipeline.Script{SCRIPT}, link)
//more than one parameter fail
err = printHelp(*cli, false, false, "one", "two")
err = printHelp(*cli, false, false, false, "one", "two")
if err == nil {
t.Error("Expected error (more than one param) is nil")
}
err = printHelp(*cli, false, false, "one")
err = printHelp(*cli, false, false, false, "one")
if err == nil {
t.Error("Expected error (unknown command) is nil")
}
Expand Down
71 changes: 71 additions & 0 deletions cli/scripts.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,15 @@ func scriptToCommand(script pipeline.Script, cli *Cli, link *PipelineLink) (req
shortDesc += " [...]"
}
shortDesc = blackterm.MarkdownString(shortDesc)
longDesc += ("\n\nPossible values: " + optionTypeToDetailedHelp(option.Type))
if ! option.Required {
longDesc += "\n\nDefault value: "
if option.Default == "" {
longDesc += "(empty)"
} else {
longDesc += "`" + option.Default + "`"
}
}
// FIXME: assumes markdown without html
longDesc = blackterm.MarkdownString(longDesc)
command.AddOption(
Expand Down Expand Up @@ -325,6 +334,68 @@ func underline(s string) string {
return chalk.Underline.TextStyle(s)
}

func optionTypeToDetailedHelp(optionType pipeline.DataType) string {
help := ""
switch t := optionType.(type) {
case pipeline.AnyFileURI:
if t.Documentation != "" {
help += t.Documentation
} else {
help += "A _FILE_"
}
case pipeline.AnyDirURI:
if t.Documentation != "" {
help += t.Documentation
} else {
help += "A _DIRECTORY_"
}
case pipeline.XsBoolean:
if t.Documentation != "" {
help += t.Documentation
} else {
help += "`true` or `false`"
}
case pipeline.XsInteger:
if t.Documentation != "" {
help += t.Documentation
} else {
help += "An _INTEGER_"
}
case pipeline.Choice:
help += "One of the following:\n"
for _, value := range t.Values {
help += "\n- "
help += indent(optionTypeToDetailedHelp(value), " ")
}
case pipeline.Value:
help += ("`" + t.Value + "`")
if t.Documentation != "" {
help += (": " + t.Documentation)
}
case pipeline.Pattern:
if t.Documentation != "" {
help += t.Documentation
} else {
help += ("A string that matches the pattern:\n" + t.Pattern)
}
case pipeline.XsAnyURI:
if t.Documentation != "" {
help += t.Documentation
} else {
help += "A _URI_"
}
case pipeline.XsString:
if t.Documentation != "" {
help += t.Documentation
} else {
help += "A _STRING_"
}
default:
help += "A _STRING_"
}
return help
}

func (c *ScriptCommand) addDataOption() {
c.AddOption("data", "d", "Zip file containing the files to convert", "", "", func(name, path string) error {
file, err := os.Open(path)
Expand Down

0 comments on commit bef26d0

Please sign in to comment.