github.com/Zenithar/prototool@v1.3.0/internal/cmd/cmd.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 // Package cmd contains the logic to setup Prototool with github.com/spf13/cobra. 22 // 23 // The packages internal/cmd/prototool, internal/gen/gen-prototool-bash-completion, 24 // internal/gen/gen-prototool-manpages and internal/gen/gen-prototool-zsh-completion 25 // are main packages that call into this package, and this package calls into 26 // internal/exec to execute the logic. 27 // 28 // This package also contains integration testing for Prototool. 29 package cmd 30 31 import ( 32 "fmt" 33 "io" 34 "os" 35 "runtime" 36 "time" 37 38 "github.com/spf13/cobra" 39 "github.com/spf13/cobra/doc" 40 "github.com/uber/prototool/internal/exec" 41 ) 42 43 // when generating man pages, the current date is used 44 // this means every time we run make gen, a diff is created 45 // this gets extremely annoying and isn't very useful so we make it static here 46 // we could also not check in the man pages, but for now we have them checked in 47 var genManTime = time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC) 48 49 // Do runs the command logic. 50 func Do(args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) int { 51 return do(false, args, stdin, stdout, stderr) 52 } 53 54 func do(develMode bool, args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) int { 55 return runRootCommand(develMode, args, stdin, stdout, stderr, (*cobra.Command).Execute) 56 } 57 58 // GenBashCompletion generates a bash completion file to the writer. 59 func GenBashCompletion(stdin io.Reader, stdout io.Writer, stderr io.Writer) int { 60 return runRootCommandOutput(false, []string{}, stdin, stdout, stderr, (*cobra.Command).GenBashCompletion) 61 } 62 63 // GenZshCompletion generates a zsh completion file to the writer. 64 func GenZshCompletion(stdin io.Reader, stdout io.Writer, stderr io.Writer) int { 65 return runRootCommandOutput(false, []string{}, stdin, stdout, stderr, (*cobra.Command).GenZshCompletion) 66 } 67 68 // GenManpages generates the manpages to the given directory. 69 func GenManpages(args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) int { 70 return runRootCommand(false, args, stdin, stdout, stderr, func(cmd *cobra.Command) error { 71 if len(args) != 1 { 72 return fmt.Errorf("usage: %s dirPath", os.Args[0]) 73 } 74 return doc.GenManTree(cmd, &doc.GenManHeader{ 75 Date: &genManTime, 76 // Otherwise we get an annoying "Auto generated by spf13/cobra" 77 // Maybe we want that, but I think it's better to just have this 78 Source: "Prototool", 79 }, args[0]) 80 }) 81 } 82 83 // develMode turns on sub-commands and potentially flags that we do not expose during the build of the prototool binary 84 func runRootCommandOutput(develMode bool, args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer, f func(*cobra.Command, io.Writer) error) int { 85 return runRootCommand(develMode, args, stdin, stdout, stderr, func(cmd *cobra.Command) error { return f(cmd, stdout) }) 86 } 87 88 // develMode turns on sub-commands and potentially flags that we do not expose during the build of the prototool binary 89 func runRootCommand(develMode bool, args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer, f func(*cobra.Command) error) (exitCode int) { 90 if err := checkOS(); err != nil { 91 return printAndGetErrorExitCode(err, stdout) 92 } 93 if err := f(getRootCommand(&exitCode, develMode, args, stdin, stdout, stderr)); err != nil { 94 return printAndGetErrorExitCode(err, stdout) 95 } 96 return exitCode 97 } 98 99 func getRootCommand(exitCodeAddr *int, develMode bool, args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) *cobra.Command { 100 flags := &flags{} 101 102 rootCmd := &cobra.Command{Use: "prototool"} 103 rootCmd.AddCommand(allCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 104 rootCmd.AddCommand(compileCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 105 rootCmd.AddCommand(createCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 106 rootCmd.AddCommand(filesCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 107 rootCmd.AddCommand(formatCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 108 rootCmd.AddCommand(generateCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 109 rootCmd.AddCommand(grpcCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 110 configCmd := &cobra.Command{Use: "config"} 111 configCmd.AddCommand(configInitCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 112 rootCmd.AddCommand(configCmd) 113 rootCmd.AddCommand(lintCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 114 rootCmd.AddCommand(versionCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 115 116 // flags bound to rootCmd are global flags 117 flags.bindDebug(rootCmd.PersistentFlags()) 118 119 if develMode { 120 rootCmd.AddCommand(binaryToJSONCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 121 rootCmd.AddCommand(cleanCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 122 rootCmd.AddCommand(descriptorProtoCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 123 rootCmd.AddCommand(downloadCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 124 rootCmd.AddCommand(fieldDescriptorProtoCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 125 rootCmd.AddCommand(jsonToBinaryCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 126 rootCmd.AddCommand(listAllLintGroupsCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 127 rootCmd.AddCommand(listLintGroupCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 128 rootCmd.AddCommand(serviceDescriptorProtoCmdTemplate.Build(exitCodeAddr, stdin, stdout, stderr, flags)) 129 130 // we may or may not want to expose these to users 131 // but will not build them into the binary for v1.0 132 flags.bindCachePath(rootCmd.PersistentFlags()) 133 flags.bindPrintFields(rootCmd.PersistentFlags()) 134 } 135 136 rootCmd.SetArgs(args) 137 rootCmd.SetOutput(stdout) 138 139 return rootCmd 140 } 141 142 func checkOS() error { 143 switch runtime.GOOS { 144 case "darwin", "linux": 145 return nil 146 default: 147 return fmt.Errorf("%s is not a supported operating system", runtime.GOOS) 148 } 149 } 150 151 func printAndGetErrorExitCode(err error, stdout io.Writer) int { 152 if errString := err.Error(); errString != "" { 153 _, _ = fmt.Fprintln(stdout, errString) 154 } 155 if exitError, ok := err.(*exec.ExitError); ok { 156 return exitError.Code 157 } 158 return 1 159 }