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