github.com/hashicorp/hcl/v2@v2.20.0/cmd/hclfmt/main.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package main 5 6 import ( 7 "bytes" 8 "errors" 9 "flag" 10 "fmt" 11 "io/ioutil" 12 "os" 13 "strings" 14 15 "github.com/hashicorp/hcl/v2" 16 "github.com/hashicorp/hcl/v2/hclparse" 17 "github.com/hashicorp/hcl/v2/hclwrite" 18 "golang.org/x/crypto/ssh/terminal" 19 ) 20 21 const versionStr = "0.0.1-dev" 22 23 var ( 24 check = flag.Bool("check", false, "perform a syntax check on the given files and produce diagnostics") 25 reqNoChange = flag.Bool("require-no-change", false, "return a non-zero status if any files are changed during formatting") 26 overwrite = flag.Bool("w", false, "overwrite source files instead of writing to stdout") 27 showVersion = flag.Bool("version", false, "show the version number and immediately exit") 28 ) 29 30 var parser = hclparse.NewParser() 31 var diagWr hcl.DiagnosticWriter // initialized in init 32 var checkErrs = false 33 var changed []string 34 35 func init() { 36 color := terminal.IsTerminal(int(os.Stderr.Fd())) 37 w, _, err := terminal.GetSize(int(os.Stdout.Fd())) 38 if err != nil { 39 w = 80 40 } 41 diagWr = hcl.NewDiagnosticTextWriter(os.Stderr, parser.Files(), uint(w), color) 42 } 43 44 func main() { 45 err := realmain() 46 47 if err != nil { 48 fmt.Fprintln(os.Stderr, err.Error()) 49 os.Exit(1) 50 } 51 } 52 53 func realmain() error { 54 flag.Usage = usage 55 flag.Parse() 56 57 if *showVersion { 58 fmt.Println(versionStr) 59 return nil 60 } 61 62 err := processFiles() 63 if err != nil { 64 return err 65 } 66 67 if checkErrs { 68 return errors.New("one or more files contained errors") 69 } 70 71 if *reqNoChange { 72 if len(changed) != 0 { 73 return fmt.Errorf("file(s) were changed: %s", strings.Join(changed, ", ")) 74 } 75 } 76 77 return nil 78 } 79 80 func processFiles() error { 81 if flag.NArg() == 0 { 82 if *overwrite { 83 return errors.New("error: cannot use -w without source filenames") 84 } 85 86 return processFile("<stdin>", os.Stdin) 87 } 88 89 for i := 0; i < flag.NArg(); i++ { 90 path := flag.Arg(i) 91 switch dir, err := os.Stat(path); { 92 case err != nil: 93 return err 94 case dir.IsDir(): 95 // This tool can't walk a whole directory because it doesn't 96 // know what file naming schemes will be used by different 97 // HCL-embedding applications, so it'll leave that sort of 98 // functionality for apps themselves to implement. 99 return fmt.Errorf("can't format directory %s", path) 100 default: 101 if err := processFile(path, nil); err != nil { 102 return err 103 } 104 } 105 } 106 107 return nil 108 } 109 110 func processFile(fn string, in *os.File) error { 111 var err error 112 var hasLocalChanges bool = false 113 if in == nil { 114 in, err = os.Open(fn) 115 if err != nil { 116 return fmt.Errorf("failed to open %s: %s", fn, err) 117 } 118 } 119 120 inSrc, err := ioutil.ReadAll(in) 121 if err != nil { 122 return fmt.Errorf("failed to read %s: %s", fn, err) 123 } 124 125 if *check { 126 _, diags := parser.ParseHCL(inSrc, fn) 127 diagWr.WriteDiagnostics(diags) 128 if diags.HasErrors() { 129 checkErrs = true 130 return nil 131 } 132 } 133 134 outSrc := hclwrite.Format(inSrc) 135 136 if !bytes.Equal(inSrc, outSrc) { 137 changed = append(changed, fn) 138 hasLocalChanges = true 139 } 140 141 if *overwrite { 142 if hasLocalChanges { 143 return ioutil.WriteFile(fn, outSrc, 0644) 144 } else { 145 return nil 146 } 147 } 148 149 _, err = os.Stdout.Write(outSrc) 150 return err 151 } 152 153 func usage() { 154 fmt.Fprintf(os.Stderr, "usage: hclfmt [flags] [path ...]\n") 155 flag.PrintDefaults() 156 os.Exit(2) 157 }