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  }