// Copyright 2020 Dolthub, Inc. // // 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 cli import ( "bytes" "fmt" "strings" "text/template" "github.com/dolthub/dolt/go/libraries/utils/argparser" ) type commandDocumentForMarkdown struct { Command string ShortDesc string Synopsis string Description string Options string } var cmdMdDocTempl = "## `{{.Command}}`\n\n" + "{{.ShortDesc}}\n\n" + "**Synopsis**\n\n" + "{{.Synopsis}}\n\n" + "**Description**\n\n" + "{{.Description}}\n\n" + "**Arguments and options**\n\n" + "{{.Options}}\n\n" func (cmdDoc CommandDocumentation) CmdDocToMd() (string, error) { return cmdDoc.executeTemplate(cmdMdDocTempl, false) } var globalCmdMdDocTempl = "## Global Arguments\n" + "{{.ShortDesc}}\n" + "{{.Synopsis}}\n\n" + "Specific dolt options:\n\n" + "{{.Options}}\n" func (cmdDoc CommandDocumentation) GlobalCmdDocToMd() (string, error) { return cmdDoc.executeTemplate(globalCmdMdDocTempl, true) } func (cmdDoc CommandDocumentation) executeTemplate(cmdTempl string, includeValDesc bool) (string, error) { // Accumulate the options and args in a string options := "" if len(cmdDoc.ArgParser.Supported) > 0 || len(cmdDoc.ArgParser.ArgListHelp) > 0 { // Iterate across arguments and template them for _, kvTuple := range cmdDoc.ArgParser.ArgListHelp { arg, desc := kvTuple[0], kvTuple[1] templatedDesc, err := templateDocStringHelper(desc, MarkdownFormat) if err != nil { return "", err } argStruct := argument{arg, templatedDesc} outputStr, err := templateArgument(argStruct) if err != nil { return "", err } options += outputStr } // Iterate across supported options, templating each one of them for _, supOpt := range cmdDoc.ArgParser.Supported { templatedDesc, err := templateDocStringHelper(supOpt.Desc, MarkdownFormat) if err != nil { return "", err } argStruct := supported{supOpt.Abbrev, supOpt.Name, "", templatedDesc} if includeValDesc { argStruct.ValDesc = supOpt.ValDesc } outputStr, err := templateSupported(argStruct) if err != nil { return "", err } options += outputStr } } else { options = `No options for this command.` } cmdMdDoc, cmdMdDocErr := cmdDoc.cmdDocToCmdDocMd(options) if cmdMdDocErr != nil { return "", cmdMdDocErr } templ, templErr := template.New("shortDesc").Parse(cmdTempl) if templErr != nil { return "", templErr } var templBuffer bytes.Buffer if err := templ.Execute(&templBuffer, cmdMdDoc); err != nil { return "", err } ret := strings.Replace(templBuffer.String(), "HEAD~", "HEAD\\~", -1) return ret, nil } // A struct that represents all the data structures required to create the documentation for a command. type CommandDocumentation struct { // The command/sub-command string passed to a command by the caller CommandStr string // The short description of the command ShortDesc string // The long description of the command LongDesc string // The synopsis, an array of strings showing how to use the command Synopsis []string // A structure that ArgParser *argparser.ArgParser } func (cmdDoc CommandDocumentation) cmdDocToCmdDocMd(options string) (commandDocumentForMarkdown, error) { longDesc, longDescErr := cmdDoc.GetLongDesc(MarkdownFormat) if longDescErr != nil { return commandDocumentForMarkdown{}, longDescErr } synopsis, synopsisErr := cmdDoc.GetSynopsis(SynopsisMarkdownFormat) if synopsisErr != nil { return commandDocumentForMarkdown{}, synopsisErr } return commandDocumentForMarkdown{ Command: cmdDoc.CommandStr, ShortDesc: cmdDoc.GetShortDesc(), Synopsis: transformSynopsisToMarkdown(cmdDoc.CommandStr, synopsis), Description: longDesc, Options: options, }, nil } // NewCommandDocumentation returns a |CommandDocumentation| for the content and arg parser given. // Does not include a command string, which must be filled in separately. func NewCommandDocumentation(cmdDoc CommandDocumentationContent, argParser *argparser.ArgParser) *CommandDocumentation { return &CommandDocumentation{ ShortDesc: cmdDoc.ShortDesc, LongDesc: cmdDoc.LongDesc, Synopsis: cmdDoc.Synopsis, ArgParser: argParser, } } // CommandDocsForCommandString returns a |CommandDocumentation| for the command string, doc contents, and arg // parser given. func CommandDocsForCommandString(command string, cmdDoc CommandDocumentationContent, argParser *argparser.ArgParser) *CommandDocumentation { return &CommandDocumentation{ CommandStr: command, ShortDesc: cmdDoc.ShortDesc, LongDesc: cmdDoc.LongDesc, Synopsis: cmdDoc.Synopsis, ArgParser: argParser, } } // Returns the ShortDesc field of the receiver CommandDocumentation with the passed DocFormat injected into the template func (cmdDoc CommandDocumentation) GetShortDesc() string { return cmdDoc.ShortDesc } // Returns the LongDesc field of the receiver CommandDocumentation with the passed DocFormat injected into the template func (cmdDoc CommandDocumentation) GetLongDesc(format docFormat) (string, error) { return templateDocStringHelper(cmdDoc.LongDesc, format) } func templateDocStringHelper(docString string, docFormat docFormat) (string, error) { templ, err := template.New("description").Parse(docString) if err != nil { return "", err } var templBuffer bytes.Buffer if err := templ.Execute(&templBuffer, docFormat); err != nil { return "", err } return templBuffer.String(), nil } // Returns the synopsis iterating over each element and injecting the supplied DocFormat func (cmdDoc CommandDocumentation) GetSynopsis(format docFormat) ([]string, error) { lines := cmdDoc.Synopsis for i, line := range lines { formatted, err := templateDocStringHelper(line, format) if err != nil { return []string{}, err } lines[i] = formatted } return lines, nil } type docFormat struct { LessThan string GreaterThan string EmphasisLeft string EmphasisRight string } // mdx format var MarkdownFormat = docFormat{"`<", ">`", "`", "`"} // Shell help output format var CliFormat = docFormat{"<", ">", "", ""} // Synopsis is an mdx format, but already inside a code block var SynopsisMarkdownFormat = docFormat{"<", ">", "`", "`"} // Format that deletes all template options var EmptyFormat = docFormat{"", "", "", ""} func transformSynopsisToMarkdown(commandStr string, synopsis []string) string { if len(synopsis) == 0 { return "" } synopsisStr := fmt.Sprintf("%s %s\n", commandStr, synopsis[0]) if len(synopsis) > 1 { temp := make([]string, len(synopsis)-1) for i, el := range synopsis[1:] { temp[i] = fmt.Sprintf("%s %s\n", commandStr, el) } synopsisStr += strings.Join(temp, "") } markdown := "```bash\n%s```" return fmt.Sprintf(markdown, synopsisStr) } type argument struct { Name string Description string } func templateArgument(supportedArg argument) (string, error) { var formatString string if supportedArg.Description == "" { formatString = "`<{{.Name}}>`\n\n" } else { formatString = "`<{{.Name}}>`: {{.Description}}\n\n" } templ, err := template.New("argString").Parse(formatString) if err != nil { return "", err } var templBuffer bytes.Buffer if err := templ.Execute(&templBuffer, supportedArg); err != nil { return "", err } ret := templBuffer.String() return ret, nil } type supported struct { Abbreviation string Name string ValDesc string Description string } func templateSupported(supported supported) (string, error) { abbr := "" if supported.Abbreviation != "" { abbr = "-{{.Abbreviation}}" if supported.ValDesc != "" { abbr += " <{{.ValDesc}}>" } abbr = "`" + abbr + "`, " } name := "--{{.Name}}" if supported.ValDesc != "" { name += "=<{{.ValDesc}}>" } name = "`" + name + "`" desc := "" if supported.Description != "" { desc = ":\n{{.Description}}" } formatString := fmt.Sprintf("%s%s%s\n\n", abbr, name, desc) templ, err := template.New("argString").Parse(formatString) if err != nil { return "", err } var templBuffer bytes.Buffer if err := templ.Execute(&templBuffer, supported); err != nil { return "", err } ret := templBuffer.String() return ret, nil }