blob: 01a8d413e11390fa47d6f84a39a5d41cc591e901 [file] [log] [blame]
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)
}