github.com/andresbott/yamlfmt@v0.1.0/app/cli/cli.go (about)

     1  // ADOBE CONFIDENTIAL
     2  // ___________________
     3  //
     4  // Copyright 2022 Adobe
     5  // All Rights Reserved.
     6  //
     7  // NOTICE: All information contained herein is, and remains
     8  // the property of Adobe and its suppliers, if any. The intellectual
     9  // and technical concepts contained herein are proprietary to Adobe
    10  // and its suppliers and are protected by all applicable intellectual
    11  // property laws, including trade secret and copyright laws.
    12  // Dissemination of this information or reproduction of this material
    13  // is strictly forbidden unless prior written permission is obtained
    14  // from Adobe.
    15  
    16  package cli
    17  
    18  import (
    19  	"bytes"
    20  	"crypto/sha256"
    21  	"fmt"
    22  	"io"
    23  	"log"
    24  	"os"
    25  	"path/filepath"
    26  	"runtime"
    27  
    28  	"github.com/andresbott/yamlfmt/internal/filematch"
    29  	"github.com/andresbott/yamlfmt/internal/yamlfmt"
    30  	"github.com/google/go-cmp/cmp"
    31  	"github.com/spf13/cobra"
    32  )
    33  
    34  // Execute is the entry point for the command line
    35  func Execute() {
    36  	if err := rootCmd().Execute(); err != nil {
    37  		fmt.Println(err)
    38  		os.Exit(1)
    39  	}
    40  }
    41  
    42  var (
    43  	ShaVer    string // sha1 revision used to build the program
    44  	BuildTime string // when the executable was built
    45  	Version   = "development"
    46  )
    47  
    48  const name = "yamlfmt"
    49  
    50  func rootCmd() *cobra.Command {
    51  	var dry bool
    52  	var verbose bool
    53  	var quiet bool
    54  	var version bool
    55  
    56  	cmd := &cobra.Command{
    57  		Use: name + " <glob>",
    58  		Long: name + " format yaml files to a opinionated defaults, it is inspired ing go fmt. \n" +
    59  			"The input accepts any regular glob patterns plus some convenience aliases:" +
    60  			"\n- \"./\" is an alias for: \"./*.yaml\" and \"./*.yml\"" +
    61  			"\n- \"./..\" is an alias for: \"./**/*.yaml\" and \"./**/*.yml\" to search recursively" +
    62  			"\n- any existing directory will be searched for \"./*.yaml\" and \"./*.yml\"",
    63  
    64  		Short: "format yaml files",
    65  		RunE: func(cmd *cobra.Command, args []string) error {
    66  
    67  			if version {
    68  				fmt.Printf("Version: %s\n", Version)
    69  				fmt.Printf("Build date: %s\n", BuildTime)
    70  				fmt.Printf("Commit sha: %s\n", ShaVer)
    71  				fmt.Printf("Compiler: %s\n", runtime.Version())
    72  
    73  				return nil
    74  			}
    75  
    76  			pattern := ""
    77  			if len(args) > 0 {
    78  				pattern = args[0]
    79  
    80  			}
    81  
    82  			files, err := filematch.FindFiles(pattern)
    83  			if err != nil {
    84  				return fmt.Errorf("unable to find files: %v", err)
    85  			}
    86  
    87  			p := printer{
    88  				quiet:   quiet,
    89  				verbose: verbose,
    90  			}
    91  
    92  			if len(files) == 0 {
    93  				p.print("no files found")
    94  				return nil
    95  			}
    96  
    97  			if dry {
    98  				p.print("(running in dry run mode, no changes will be written)")
    99  			}
   100  
   101  			for _, f := range files {
   102  
   103  				abs, err := filepath.Abs(f)
   104  				if err != nil {
   105  					return fmt.Errorf("unable to get absolute path for: %s,  %s", f, err.Error())
   106  				}
   107  				fInfo, err := os.Stat(abs)
   108  				if err != nil {
   109  					return fmt.Errorf("unable to stat file: %s,  %s", f, err.Error())
   110  				}
   111  				if fInfo.IsDir() {
   112  					return fmt.Errorf("%s is a directoru:  %s", f, err.Error())
   113  				}
   114  
   115  				maxFileSize := int64(50 * 1024 * 1024)
   116  				if fInfo.Size() > maxFileSize {
   117  					return fmt.Errorf("file %s is bigger than 50MB", f)
   118  				}
   119  
   120  				inBytes, err := os.ReadFile(abs)
   121  				if err != nil {
   122  					return fmt.Errorf("unable to read file: %s", f)
   123  				}
   124  
   125  				inHash := fileHash(inBytes)
   126  				got, err := yamlfmt.Format(inBytes)
   127  				if err != nil {
   128  					return fmt.Errorf("unable to format file %s:%s", f, err.Error())
   129  				}
   130  				outHash := fileHash(got)
   131  
   132  				if inHash != outHash {
   133  					p.print(f)
   134  					diff := cmp.Diff(string(inBytes), string(got))
   135  					p.printVerbose(diff)
   136  
   137  					if !dry {
   138  						err = os.WriteFile(abs, got, fInfo.Mode())
   139  						if err != nil {
   140  							return fmt.Errorf("unable to write file: %s", f)
   141  						}
   142  					}
   143  				}
   144  			}
   145  			return nil
   146  		},
   147  	}
   148  
   149  	cmd.Flags().BoolVarP(&dry, "dry-run", "d", false, "do not persists changes")
   150  	cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "print the diff for every file to change")
   151  	cmd.Flags().BoolVarP(&quiet, "quiet", "q", false, "dont print any output, takes precedence over verbose")
   152  	cmd.Flags().BoolVar(&version, "version", false, "print version and build information")
   153  
   154  	return cmd
   155  }
   156  
   157  func fileHash(in []byte) string {
   158  	r := bytes.NewReader(in)
   159  
   160  	h := sha256.New()
   161  	if _, err := io.Copy(h, r); err != nil {
   162  		log.Fatal(err)
   163  	}
   164  	return fmt.Sprintf("%x", h.Sum(nil))
   165  }