github.com/amplia-iiot/yutil@v1.0.1-0.20231229120411-5d96a4c5a136/cmd/replace.go (about)

     1  /*
     2  Copyright (c) 2023 Adrian Haasler GarcĂ­a <dev@ahaasler.com>
     3  
     4  Permission is hereby granted, free of charge, to any person obtaining a copy
     5  of this software and associated documentation files (the "Software"), to deal
     6  in the Software without restriction, including without limitation the rights
     7  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8  copies of the Software, and to permit persons to whom the Software is
     9  furnished to do so, subject to the following conditions:
    10  
    11  The above copyright notice and this permission notice shall be included in all
    12  copies or substantial portions of the Software.
    13  
    14  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    20  SOFTWARE.
    21  */
    22  
    23  package cmd
    24  
    25  import (
    26  	"fmt"
    27  
    28  	"github.com/amplia-iiot/yutil/internal/io"
    29  	"github.com/amplia-iiot/yutil/pkg/replace"
    30  	"github.com/spf13/cobra"
    31  )
    32  
    33  type replaceOptions struct {
    34  	golang           bool
    35  	jinja2           bool
    36  	directory        string
    37  	node             string
    38  	include          []string
    39  	exclude          []string
    40  	replacementFiles []string
    41  	includeEnv       bool
    42  	extensions       []string
    43  }
    44  
    45  func (o replaceOptions) engine() replace.Engine {
    46  	if o.jinja2 {
    47  		return replace.Jinja2
    48  	}
    49  	return replace.Golang
    50  }
    51  
    52  var rOptions replaceOptions
    53  
    54  // replaceCmd represents the replace command
    55  var replaceCmd = &cobra.Command{
    56  	Use:   "replace",
    57  	Short: "Replace files using a template engine",
    58  	Long: `Replace files with a template engine, using one or
    59  more replacement files that will be merged. Files should be ordered
    60  in ascending level of importance in the hierarchy. A yaml
    61  node in the last file replaces values in any previous file. Stdin is
    62  treated as first replacement file.
    63  
    64  By default all files in the current directory and subdirectories are passed
    65  through the template engine. Use include and exclude to filter files.
    66  If no engine is picked the default golang template with slim-sprig functions
    67  will be used.
    68  
    69  The extension/s is/are used for including those files by default (unless
    70  include flag is used) and renaming replaced files accordingly.
    71  
    72  For example:
    73  
    74  yutil replace -r base.yml -r changes.yml -d directory
    75  cat base.yml | yutil replace
    76  yutil replace -r config.yml -n root_node --jinja2
    77  yutil replace -r config.yml -e .go -e .gotempl --env
    78  yutil replace -r config.yml --include 'directory/*.conf'
    79  yutil replace -r config.yml --jinja2 -d directory --exclude '*/secret/*'
    80  echo "this is not a yaml" | yutil --no-input replace -r base.yml -r changes.yml
    81  `,
    82  	RunE: func(cmd *cobra.Command, args []string) error {
    83  		engine := rOptions.engine()
    84  		extensions := rOptions.extensions
    85  		include := rOptions.include
    86  		switch engine {
    87  		case replace.Golang:
    88  			if len(extensions) == 0 {
    89  				extensions = append(extensions, ".tmpl")
    90  			}
    91  		case replace.Jinja2:
    92  			if len(extensions) == 0 {
    93  				extensions = append(extensions, ".j2")
    94  			}
    95  		}
    96  		if len(include) == 0 {
    97  			for _, ext := range extensions {
    98  				include = append(include, fmt.Sprintf("*%s.*", ext), fmt.Sprintf("*%s", ext))
    99  			}
   100  		}
   101  		if !io.Exists(rOptions.directory) {
   102  			return fmt.Errorf("directory %s does not exist", rOptions.directory)
   103  		}
   104  		for _, f := range rOptions.replacementFiles {
   105  			if !io.Exists(f) {
   106  				return fmt.Errorf("replacement file %s does not exist", f)
   107  			}
   108  		}
   109  		err := replace.Replace(
   110  			engine,
   111  			replace.WithDirectory(rOptions.directory),
   112  			replace.WithReplacementFiles(rOptions.replacementFiles...),
   113  			replace.WithRootNode(rOptions.node),
   114  			replace.WithExtension(extensions...),
   115  			replace.WithInclude(include...),
   116  			replace.WithExclude(rOptions.exclude...),
   117  			replace.WithIncludeEnvironmentInReplacements(rOptions.includeEnv),
   118  			replace.WithIncludeStdinInReplacements(canAccessStdin()),
   119  		)
   120  		if err != nil {
   121  			return err
   122  		}
   123  		return nil
   124  	},
   125  }
   126  
   127  func init() {
   128  	rootCmd.AddCommand(replaceCmd)
   129  
   130  	replaceCmd.Flags().BoolVar(&rOptions.golang, "golang", false, "use golang template engine (default), automatically sets include and extension config for .tmpl files unless overriden")
   131  	replaceCmd.Flags().BoolVar(&rOptions.jinja2, "jinja2", false, "use jinja2 template engine, automatically sets include and extension config for .j2 files unless overriden")
   132  	replaceCmd.MarkFlagsMutuallyExclusive("golang", "jinja2")
   133  	replaceCmd.Flags().StringVarP(&rOptions.directory, "directory", "d", ".", "pick root directory to search for files to be replaced (defaults to current directory)")
   134  	replaceCmd.Flags().StringSliceVarP(&rOptions.replacementFiles, "replacements", "r", []string{}, "replacement files (multiple files will be merged)")
   135  	replaceCmd.Flags().StringVarP(&rOptions.node, "node", "n", "", "only include replacements from inside this node")
   136  	replaceCmd.Flags().BoolVar(&rOptions.includeEnv, "env", false, "include environment variables as input for the template engine (available inside the 'env' node)")
   137  	replaceCmd.Flags().StringSliceVarP(&rOptions.extensions, "extension", "e", []string{}, "define the extension/s of the files to replace and then remove in the file name when saving (normally you should include the dot), automatically sets default include config unless overriden (*<ext> and *<ext>.*)")
   138  	replaceCmd.Flags().StringSliceVar(&rOptions.include, "include", []string{}, "include files that match the filter/s")
   139  	replaceCmd.Flags().StringSliceVar(&rOptions.exclude, "exclude", []string{}, "exclude files that match the filter/s (takes precedence over include)")
   140  	onViperInitialize(func() {
   141  		bindViperC(replaceCmd, "golang", "replace.golang")
   142  		bindViperC(replaceCmd, "jinja2", "replace.jinja2")
   143  		bindViperC(replaceCmd, "directory", "replace.directory")
   144  		bindViperC(replaceCmd, "replacements", "replace.replacements")
   145  		bindViperC(replaceCmd, "node", "replace.node")
   146  		bindViperC(replaceCmd, "env", "replace.env")
   147  		bindViperC(replaceCmd, "extension", "replace.extension")
   148  		bindViperC(replaceCmd, "include", "replace.include")
   149  		bindViperC(replaceCmd, "exclude", "replace.exclude")
   150  	})
   151  }