6c4344623b
The main HCL package is more visible this way, and so it's easier than having to pick it out from dozens of other package directories.
149 lines
3.0 KiB
Go
149 lines
3.0 KiB
Go
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)
|
|
}
|