github.com/Minish144/prototool-arm64@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  }