Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: add new install script, commands #1556

Merged
merged 10 commits into from
Feb 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions cli/commands/completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package commands

import (
"fmt"
"os"

"github.com/hasura/graphql-engine/cli"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

const completionCmdExample = `# Bash
# Linux
# Add Bash completion file using:
$ sudo hasura completion bash --file=/etc/bash.completion.d/hasura
# Mac
# Install bash-completion using homebrew:
$ brew install bash-completion
# Add to your ~/.bash_profile:
if [ -f $(brew --prefix)/etc/bash_completion ]; then
. $(brew --prefix)/etc/bash_completion
fi
# Add the completion file:
$ sudo hasura completion bash --file=$(brew --prefix)/etc/bash_completion.d/hasura
# Windows (Git Bash)
# open git bash
$ mkdir -p ~/.bash_completion.d
# Add the completion file:
$ cd ~ && hasura completion bash --file=bash_completion.d/hasura
# Add the following to ~/.bash_profile
if [ -f ~/.bash_completion.d/hasura ]; then
. ~/.bash_completion.d/hasura
fi
# restart git bash

# Zsh (using oh-my-zsh)
$ mkdir -p $HOME/.oh-my-zsh/completions
$ hasura completion zsh --file=$HOME/.oh-my-zsh/completions/_hasura

# Reload the shell for the changes to take effect!`

// NewCompletionCmd return the completion command.
func NewCompletionCmd(ec *cli.ExecutionContext) *cobra.Command {
opts := &completionOptions{
EC: ec,
}
completionCmd := &cobra.Command{
Use: "completion [shell]",
Short: "Generate auto completion code",
Args: cobra.ExactArgs(1),
Long: "Output shell completion code for the specified shell (bash or zsh)",
SilenceUsage: true,
Example: completionCmdExample,
PreRunE: func(cmd *cobra.Command, args []string) error {
ec.Viper = viper.New()
return ec.Prepare()
},
RunE: func(cmd *cobra.Command, args []string) error {
opts.Shell = args[0]
opts.Cmd = cmd
return opts.run()
},
}

completionCmd.Flags().StringVar(&opts.File, "file", "", "file to which output has to be written")
completionCmd.MarkFlagFilename("file")
return completionCmd
}

type completionOptions struct {
EC *cli.ExecutionContext

Shell string
File string
Cmd *cobra.Command
}

func (o *completionOptions) run() error {
var err error
switch o.Shell {
case "bash":
if o.File != "" {
err = o.Cmd.Root().GenBashCompletionFile(o.File)
} else {
err = o.Cmd.Root().GenBashCompletion(os.Stdout)
}
case "zsh":
if o.File != "" {
err = o.Cmd.Root().GenZshCompletionFile(o.File)
} else {
err = o.Cmd.Root().GenZshCompletion(os.Stdout)
}
default:
err = fmt.Errorf("Unknown shell: %s. Use bash or zsh", o.Shell)
}
if err != nil {
return err
}
return nil
}
216 changes: 215 additions & 1 deletion cli/commands/docs.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package commands

import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"

"github.com/hasura/graphql-engine/cli"
"github.com/hasura/graphql-engine/cli/assets"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
Expand Down Expand Up @@ -32,7 +39,7 @@ func NewDocsCmd(ec *cli.ExecutionContext) *cobra.Command {
case "md":
err = doc.GenMarkdownTree(rootCmd, docDirectory)
case "rest":
err = doc.GenReSTTree(rootCmd, docDirectory)
err = genReSTTreeCustom(rootCmd, docDirectory, "Hasura CLI: ", func(s string) string { return "" }, sphinxLinkHandler)
case "yaml":
err = doc.GenYamlTree(rootCmd, docDirectory)
default:
Expand All @@ -51,3 +58,210 @@ func NewDocsCmd(ec *cli.ExecutionContext) *cobra.Command {
f.StringVar(&docDirectory, "directory", "docs", "directory where docs should be generated")
return docsCmd
}

func sphinxLinkHandler(name, ref string) string {
return fmt.Sprintf(":ref:`%s <%s>`", name, ref)
}

// taken from https://github.com/spf13/cobra/blob/master/doc/rest_docs.go
func printOptionsReST(buf *bytes.Buffer, cmd *cobra.Command, name string) error {
flags := cmd.NonInheritedFlags()
flags.SetOutput(buf)
if flags.HasFlags() {
buf.WriteString("Options\n")
buf.WriteString("~~~~~~~\n\n::\n\n")
flags.PrintDefaults()
buf.WriteString("\n")
}

parentFlags := cmd.InheritedFlags()
parentFlags.SetOutput(buf)
if parentFlags.HasFlags() {
buf.WriteString("Options inherited from parent commands\n")
buf.WriteString("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n::\n\n")
parentFlags.PrintDefaults()
buf.WriteString("\n")
}
return nil
}

// linkHandler for default ReST hyperlink markup
func defaultLinkHandler(name, ref string) string {
return fmt.Sprintf("`%s <%s.rst>`_", name, ref)
}

// genReST creates reStructured Text output.
func genReST(cmd *cobra.Command, w io.Writer, titlePrefix string) error {
return genReSTCustom(cmd, w, titlePrefix, defaultLinkHandler)
}

// genReSTCustom creates custom reStructured Text output.
func genReSTCustom(cmd *cobra.Command, w io.Writer, titlePrefix string, linkHandler func(string, string) string) error {
cmd.InitDefaultHelpCmd()
cmd.InitDefaultHelpFlag()

buf := new(bytes.Buffer)
name := cmd.CommandPath()
ref := strings.Replace(name, " ", "_", -1)
cliDocPath := "manifests/docs/" + ref + ".rst"
short := cmd.Short
long := cmd.Long
if len(long) == 0 {
long = short
}
fileInfo, er := assets.Asset(cliDocPath)
var info string
if er != nil || string(fileInfo) == "" {
info = short
} else {
info = string(fileInfo)
}

buf.WriteString(".. _" + ref + ":\n\n")

buf.WriteString(titlePrefix + name + "\n")
buf.WriteString(strings.Repeat("-", len(titlePrefix+name)) + "\n\n")
buf.WriteString(info + "\n\n")

buf.WriteString("Synopsis\n")
buf.WriteString("~~~~~~~~\n\n")
buf.WriteString("\n" + long + "\n\n")

if cmd.Runnable() {
buf.WriteString(fmt.Sprintf("::\n\n %s\n\n", cmd.UseLine()))
}

if len(cmd.Aliases) > 0 {
buf.WriteString("Alias: " + strings.Join(cmd.Aliases, ", ") + "\n\n")
}

if len(cmd.Example) > 0 {
buf.WriteString("Examples\n")
buf.WriteString("~~~~~~~~\n\n")
buf.WriteString(fmt.Sprintf("::\n\n%s\n\n", indentString(cmd.Example, " ")))
}

if err := printOptionsReST(buf, cmd, name); err != nil {
return err
}
if hasSeeAlso(cmd) {
buf.WriteString("SEE ALSO\n")
buf.WriteString("~~~~~~~~\n\n")
if cmd.HasParent() {
parent := cmd.Parent()
pname := parent.CommandPath()
ref = strings.Replace(pname, " ", "_", -1)
buf.WriteString(fmt.Sprintf("* %s \t - %s\n", linkHandler(pname, ref), parent.Short))
cmd.VisitParents(func(c *cobra.Command) {
if c.DisableAutoGenTag {
cmd.DisableAutoGenTag = c.DisableAutoGenTag
}
})
}

children := cmd.Commands()
sort.Sort(byName(children))

for _, child := range children {
if !child.IsAvailableCommand() || child.IsAdditionalHelpTopicCommand() {
continue
}
cname := name + " " + child.Name()
ref = strings.Replace(cname, " ", "_", -1)
buf.WriteString(fmt.Sprintf("* %s \t - %s\n", linkHandler(cname, ref), child.Short))
}
buf.WriteString("\n")
}
if !cmd.DisableAutoGenTag {
buf.WriteString("*Auto generated by spf13/cobra*\n")
}
_, err := buf.WriteTo(w)
return err
}

// genReSTTree will generate a ReST page for this command and all
// descendants in the directory given.
// This function may not work correctly if your command names have `-` in them.
// If you have `cmd` with two subcmds, `sub` and `sub-third`,
// and `sub` has a subcommand called `third`, it is undefined which
// help output will be in the file `cmd-sub-third.1`.
func genReSTTree(cmd *cobra.Command, dir, titlePrefix string) error {
emptyStr := func(s string) string { return "" }
return genReSTTreeCustom(cmd, dir, titlePrefix, emptyStr, defaultLinkHandler)
}

// genReSTTreeCustom is the the same as genReSTTree, but
// with custom filePrepender and linkHandler.
func genReSTTreeCustom(cmd *cobra.Command, dir, titlePrefix string, filePrepender func(string) string, linkHandler func(string, string) string) error {
for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
continue
}
if err := genReSTTreeCustom(c, dir, titlePrefix, filePrepender, linkHandler); err != nil {
return err
}
}

basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".rst"
filename := filepath.Join(dir, basename)
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()

if _, err := io.WriteString(f, filePrepender(filename)); err != nil {
return err
}
if err := genReSTCustom(cmd, f, titlePrefix, linkHandler); err != nil {
return err
}
return nil
}

// adapted from: https://github.com/kr/text/blob/main/indent.go
func indentString(s, p string) string {
var res []byte
b := []byte(s)
prefix := []byte(p)
bol := true
for _, c := range b {
if bol && c != '\n' {
res = append(res, prefix...)
}
res = append(res, c)
bol = c == '\n'
}
return string(res)
}

// Test to see if we have a reason to print See Also information in docs
// Basically this is a test for a parent commend or a subcommand which is
// both not deprecated and not the autogenerated help command.
func hasSeeAlso(cmd *cobra.Command) bool {
if cmd.HasParent() {
return true
}
for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
continue
}
return true
}
return false
}

// Temporary workaround for yaml lib generating incorrect yaml with long strings
// that do not contain \n.
func forceMultiLine(s string) string {
if len(s) > 60 && !strings.Contains(s, "\n") {
s = s + "\n"
}
return s
}

type byName []*cobra.Command

func (s byName) Len() int { return len(s) }
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
Loading