github.com/wuhuizuo/gomplate@v3.5.0+incompatible/cmd/gomplate/main.go (about)

     1  /*
     2  The gomplate command
     3  
     4  */
     5  package main
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"os"
    11  	"os/exec"
    12  	"os/signal"
    13  
    14  	"github.com/hairyhenderson/gomplate"
    15  	"github.com/hairyhenderson/gomplate/env"
    16  	"github.com/hairyhenderson/gomplate/version"
    17  	"github.com/spf13/cobra"
    18  )
    19  
    20  var (
    21  	printVer bool
    22  	verbose  bool
    23  	opts     gomplate.Config
    24  )
    25  
    26  // nolint: gocyclo
    27  func validateOpts(cmd *cobra.Command, args []string) error {
    28  	if cmd.Flag("in").Changed && cmd.Flag("file").Changed {
    29  		return errors.New("--in and --file may not be used together")
    30  	}
    31  
    32  	if len(opts.InputFiles) != len(opts.OutputFiles) {
    33  		return fmt.Errorf("must provide same number of --out (%d) as --file (%d) options", len(opts.OutputFiles), len(opts.InputFiles))
    34  	}
    35  
    36  	if cmd.Flag("input-dir").Changed && (cmd.Flag("in").Changed || cmd.Flag("file").Changed) {
    37  		return errors.New("--input-dir can not be used together with --in or --file")
    38  	}
    39  
    40  	if cmd.Flag("output-dir").Changed {
    41  		if cmd.Flag("out").Changed {
    42  			return errors.New("--output-dir can not be used together with --out")
    43  		}
    44  		if !cmd.Flag("input-dir").Changed {
    45  			return errors.New("--input-dir must be set when --output-dir is set")
    46  		}
    47  	}
    48  
    49  	if cmd.Flag("output-map").Changed {
    50  		if cmd.Flag("out").Changed || cmd.Flag("output-dir").Changed {
    51  			return errors.New("--output-map can not be used together with --out or --output-dir")
    52  		}
    53  		if !cmd.Flag("input-dir").Changed {
    54  			return errors.New("--input-dir must be set when --output-map is set")
    55  		}
    56  	}
    57  	return nil
    58  }
    59  
    60  func printVersion(name string) {
    61  	fmt.Printf("%s version %s\n", name, version.Version)
    62  }
    63  
    64  // postRunExec - if templating succeeds, the command following a '--' will be executed
    65  func postRunExec(cmd *cobra.Command, args []string) error {
    66  	if len(args) > 0 {
    67  		name := args[0]
    68  		args = args[1:]
    69  		// nolint: gosec
    70  		c := exec.Command(name, args...)
    71  		c.Stdin = os.Stdin
    72  		c.Stderr = os.Stderr
    73  		c.Stdout = os.Stdout
    74  
    75  		// make sure all signals are propagated
    76  		sigs := make(chan os.Signal, 1)
    77  		signal.Notify(sigs)
    78  		go func() {
    79  			// Pass signals to the sub-process
    80  			sig := <-sigs
    81  			if c.Process != nil {
    82  				// nolint: gosec
    83  				_ = c.Process.Signal(sig)
    84  			}
    85  		}()
    86  
    87  		return c.Run()
    88  	}
    89  	return nil
    90  }
    91  
    92  // optionalExecArgs - implements cobra.PositionalArgs. Allows extra args following
    93  // a '--', but not otherwise.
    94  func optionalExecArgs(cmd *cobra.Command, args []string) error {
    95  	if cmd.ArgsLenAtDash() == 0 {
    96  		return nil
    97  	}
    98  	return cobra.NoArgs(cmd, args)
    99  }
   100  
   101  func newGomplateCmd() *cobra.Command {
   102  	rootCmd := &cobra.Command{
   103  		Use:     "gomplate",
   104  		Short:   "Process text files with Go templates",
   105  		PreRunE: validateOpts,
   106  		RunE: func(cmd *cobra.Command, args []string) error {
   107  			if printVer {
   108  				printVersion(cmd.Name())
   109  				return nil
   110  			}
   111  			if verbose {
   112  				// nolint: errcheck
   113  				fmt.Fprintf(os.Stderr, "%s version %s, build %s (%v)\nconfig is:\n%s\n\n",
   114  					cmd.Name(), version.Version, version.GitCommit, version.BuildDate,
   115  					&opts)
   116  			}
   117  
   118  			err := gomplate.RunTemplates(&opts)
   119  			cmd.SilenceErrors = true
   120  			cmd.SilenceUsage = true
   121  			if verbose {
   122  				// nolint: errcheck
   123  				fmt.Fprintf(os.Stderr, "rendered %d template(s) with %d error(s) in %v\n",
   124  					gomplate.Metrics.TemplatesProcessed, gomplate.Metrics.Errors, gomplate.Metrics.TotalRenderDuration)
   125  			}
   126  			return err
   127  		},
   128  		PostRunE: postRunExec,
   129  		Args:     optionalExecArgs,
   130  	}
   131  	return rootCmd
   132  }
   133  
   134  func initFlags(command *cobra.Command) {
   135  	command.Flags().SortFlags = false
   136  
   137  	command.Flags().StringArrayVarP(&opts.DataSources, "datasource", "d", nil, "`datasource` in alias=URL form. Specify multiple times to add multiple sources.")
   138  	command.Flags().StringArrayVarP(&opts.DataSourceHeaders, "datasource-header", "H", nil, "HTTP `header` field in 'alias=Name: value' form to be provided on HTTP-based data sources. Multiples can be set.")
   139  
   140  	command.Flags().StringArrayVarP(&opts.Contexts, "context", "c", nil, "pre-load a `datasource` into the context, in alias=URL form. Use the special alias `.` to set the root context.")
   141  
   142  	command.Flags().StringArrayVarP(&opts.InputFiles, "file", "f", []string{"-"}, "Template `file` to process. Omit to use standard input, or use --in or --input-dir")
   143  	command.Flags().StringVarP(&opts.Input, "in", "i", "", "Template `string` to process (alternative to --file and --input-dir)")
   144  	command.Flags().StringVar(&opts.InputDir, "input-dir", "", "`directory` which is examined recursively for templates (alternative to --file and --in)")
   145  
   146  	command.Flags().StringArrayVar(&opts.ExcludeGlob, "exclude", []string{}, "glob of files to not parse")
   147  
   148  	command.Flags().StringArrayVarP(&opts.OutputFiles, "out", "o", []string{"-"}, "output `file` name. Omit to use standard output.")
   149  	command.Flags().StringArrayVarP(&opts.Templates, "template", "t", []string{}, "Additional template file(s)")
   150  	command.Flags().StringVar(&opts.OutputDir, "output-dir", ".", "`directory` to store the processed templates. Only used for --input-dir")
   151  	command.Flags().StringVar(&opts.OutputMap, "output-map", "", "Template `string` to map the input file to an output path")
   152  	command.Flags().StringVar(&opts.OutMode, "chmod", "", "set the mode for output file(s). Omit to inherit from input file(s)")
   153  
   154  	ldDefault := env.Getenv("GOMPLATE_LEFT_DELIM", "{{")
   155  	rdDefault := env.Getenv("GOMPLATE_RIGHT_DELIM", "}}")
   156  	command.Flags().StringVar(&opts.LDelim, "left-delim", ldDefault, "override the default left-`delimiter` [$GOMPLATE_LEFT_DELIM]")
   157  	command.Flags().StringVar(&opts.RDelim, "right-delim", rdDefault, "override the default right-`delimiter` [$GOMPLATE_RIGHT_DELIM]")
   158  
   159  	command.Flags().BoolVarP(&verbose, "verbose", "V", false, "output extra information about what gomplate is doing")
   160  
   161  	command.Flags().BoolVarP(&printVer, "version", "v", false, "print the version")
   162  }
   163  
   164  func main() {
   165  	command := newGomplateCmd()
   166  	initFlags(command)
   167  	if err := command.Execute(); err != nil {
   168  		// nolint: errcheck
   169  		fmt.Fprintln(os.Stderr, err)
   170  		os.Exit(1)
   171  	}
   172  }