diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 42d88c119..1ebe088b9 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -641,10 +641,18 @@ "ImportPath": "github.com/hashicorp/hcl/hcl/ast", "Rev": "71c7409f1abba841e528a80556ed2c67671744c3" }, + { + "ImportPath": "github.com/hashicorp/hcl/hcl/fmtcmd", + "Rev": "71c7409f1abba841e528a80556ed2c67671744c3" + }, { "ImportPath": "github.com/hashicorp/hcl/hcl/parser", "Rev": "71c7409f1abba841e528a80556ed2c67671744c3" }, + { + "ImportPath": "github.com/hashicorp/hcl/hcl/printer", + "Rev": "71c7409f1abba841e528a80556ed2c67671744c3" + }, { "ImportPath": "github.com/hashicorp/hcl/hcl/scanner", "Rev": "71c7409f1abba841e528a80556ed2c67671744c3" diff --git a/command/fmt.go b/command/fmt.go new file mode 100644 index 000000000..e4e9aad2f --- /dev/null +++ b/command/fmt.go @@ -0,0 +1,75 @@ +package command + +import ( + "flag" + "fmt" + "strings" + + "github.com/hashicorp/hcl/hcl/fmtcmd" + "github.com/mitchellh/cli" +) + +const ( + fileExtension = "tf" +) + +// FmtCommand is a Command implementation that rewrites Terraform config +// files to a canonical format and style. +type FmtCommand struct { + Meta + opts fmtcmd.Options +} + +func (c *FmtCommand) Run(args []string) int { + args = c.Meta.process(args, false) + + cmdFlags := flag.NewFlagSet("fmt", flag.ContinueOnError) + cmdFlags.BoolVar(&c.opts.List, "list", false, "list") + cmdFlags.BoolVar(&c.opts.Write, "write", false, "write") + cmdFlags.BoolVar(&c.opts.Diff, "diff", false, "diff") + cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } + + if err := cmdFlags.Parse(args); err != nil { + return 1 + } + + args = cmdFlags.Args() + if len(args) > 0 { + c.Ui.Error("The fmt command expects no arguments.") + cmdFlags.Usage() + return 1 + } + + dir := "." + output := &cli.UiWriter{Ui: c.Ui} + err := fmtcmd.Run([]string{dir}, []string{fileExtension}, nil, output, c.opts) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error running fmt: %s", err)) + return 2 + } + + return 0 +} + +func (c *FmtCommand) Help() string { + helpText := ` +Usage: terraform fmt [options] + + Rewrites all Terraform configuration files in the current working + directory to a canonical format. + +Options: + + -list List files whose formatting differs + + -write Write result to source file instead of STDOUT + + -diff Display diffs instead of rewriting files + +` + return strings.TrimSpace(helpText) +} + +func (c *FmtCommand) Synopsis() string { + return "Rewrites config files to canonical format" +} diff --git a/command/fmt_test.go b/command/fmt_test.go new file mode 100644 index 000000000..3d284e466 --- /dev/null +++ b/command/fmt_test.go @@ -0,0 +1,121 @@ +package command + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/mitchellh/cli" +) + +func TestFmt_errorReporting(t *testing.T) { + tempDir, err := fmtFixtureWriteDir() + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(tempDir) + + ui := new(cli.MockUi) + c := &FmtCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + } + + dummy_file := filepath.Join(tempDir, "doesnotexist") + args := []string{dummy_file} + if code := c.Run(args); code != 2 { + t.Fatalf("wrong exit code. errors: \n%s", ui.ErrorWriter.String()) + } + + expected := fmt.Sprintf("Error running fmt: stat %s: no such file or directory", dummy_file) + if actual := ui.ErrorWriter.String(); !strings.Contains(actual, expected) { + t.Fatalf("expected:\n%s\n\nto include: %q", actual, expected) + } +} + +func TestFmt_tooManyArgs(t *testing.T) { + ui := new(cli.MockUi) + c := &FmtCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + } + + args := []string{"bad"} + if code := c.Run(args); code != 1 { + t.Fatalf("wrong exit code. errors: \n%s", ui.ErrorWriter.String()) + } + + expected := "The fmt command expects no arguments." + if actual := ui.ErrorWriter.String(); !strings.Contains(actual, expected) { + t.Fatalf("expected:\n%s\n\nto include: %q", actual, expected) + } +} + +func TestFmt_workingDirectory(t *testing.T) { + tempDir, err := fmtFixtureWriteDir() + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(tempDir) + + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("err: %s", err) + } + err = os.Chdir(tempDir) + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Chdir(cwd) + + ui := new(cli.MockUi) + c := &FmtCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + } + + args := []string{} + if code := c.Run(args); code != 0 { + t.Fatalf("wrong exit code. errors: \n%s", ui.ErrorWriter.String()) + } + + expected := fmtFixture.golden + if actual := ui.OutputWriter.Bytes(); !bytes.Equal(actual, expected) { + t.Fatalf("got: %q\nexpected: %q", actual, expected) + } +} + +var fmtFixture = struct { + filename string + input, golden []byte +}{ + "main.tf", + []byte(` foo = "bar" +`), + []byte(`foo = "bar" +`), +} + +func fmtFixtureWriteDir() (string, error) { + dir, err := ioutil.TempDir("", "tf") + if err != nil { + return "", err + } + + err = ioutil.WriteFile(filepath.Join(dir, fmtFixture.filename), fmtFixture.input, 0644) + if err != nil { + os.RemoveAll(dir) + return "", err + } + + return dir, nil +} diff --git a/commands.go b/commands.go index 45783d3a5..000200213 100644 --- a/commands.go +++ b/commands.go @@ -50,6 +50,12 @@ func init() { }, nil }, + "fmt": func() (cli.Command, error) { + return &command.FmtCommand{ + Meta: meta, + }, nil + }, + "get": func() (cli.Command, error) { return &command.GetCommand{ Meta: meta, diff --git a/vendor/github.com/hashicorp/hcl/hcl/fmtcmd/fmtcmd.go b/vendor/github.com/hashicorp/hcl/hcl/fmtcmd/fmtcmd.go new file mode 100644 index 000000000..afc1e4eb1 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl/fmtcmd/fmtcmd.go @@ -0,0 +1,164 @@ +// Derivative work from: +// - https://golang.org/src/cmd/gofmt/gofmt.go +// - https://github.com/fatih/hclfmt + +package fmtcmd + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/hashicorp/hcl/hcl/printer" +) + +var ( + ErrWriteStdin = errors.New("cannot use write option with standard input") +) + +type Options struct { + List bool // list files whose formatting differs + Write bool // write result to (source) file instead of stdout + Diff bool // display diffs instead of rewriting files +} + +func isValidFile(f os.FileInfo, extensions []string) bool { + if !f.IsDir() && !strings.HasPrefix(f.Name(), ".") { + for _, ext := range extensions { + if strings.HasSuffix(f.Name(), "."+ext) { + return true + } + } + } + + return false +} + +// If in == nil, the source is the contents of the file with the given filename. +func processFile(filename string, in io.Reader, out io.Writer, stdin bool, opts Options) error { + if in == nil { + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() + in = f + } + + src, err := ioutil.ReadAll(in) + if err != nil { + return err + } + + res, err := printer.Format(src) + if err != nil { + return err + } + // Files should end with newlines + res = append(res, []byte("\n")...) + + if !bytes.Equal(src, res) { + // formatting has changed + if opts.List { + fmt.Fprintln(out, filename) + } + if opts.Write { + err = ioutil.WriteFile(filename, res, 0644) + if err != nil { + return err + } + } + if opts.Diff { + data, err := diff(src, res) + if err != nil { + return fmt.Errorf("computing diff: %s", err) + } + fmt.Fprintf(out, "diff a/%s b/%s\n", filename, filename) + out.Write(data) + } + } + + if !opts.List && !opts.Write && !opts.Diff { + _, err = out.Write(res) + } + + return err +} + +func walkDir(path string, extensions []string, stdout io.Writer, opts Options) error { + visitFile := func(path string, f os.FileInfo, err error) error { + if err == nil && isValidFile(f, extensions) { + err = processFile(path, nil, stdout, false, opts) + } + return err + } + + return filepath.Walk(path, visitFile) +} + +func Run( + paths, extensions []string, + stdin io.Reader, + stdout io.Writer, + opts Options, +) error { + if len(paths) == 0 { + if opts.Write { + return ErrWriteStdin + } + if err := processFile("", stdin, stdout, true, opts); err != nil { + return err + } + return nil + } + + for _, path := range paths { + switch dir, err := os.Stat(path); { + case err != nil: + return err + case dir.IsDir(): + if err := walkDir(path, extensions, stdout, opts); err != nil { + return err + } + default: + if err := processFile(path, nil, stdout, false, opts); err != nil { + return err + } + } + } + + return nil +} + +func diff(b1, b2 []byte) (data []byte, err error) { + f1, err := ioutil.TempFile("", "") + if err != nil { + return + } + defer os.Remove(f1.Name()) + defer f1.Close() + + f2, err := ioutil.TempFile("", "") + if err != nil { + return + } + defer os.Remove(f2.Name()) + defer f2.Close() + + f1.Write(b1) + f2.Write(b2) + + data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput() + if len(data) > 0 { + // diff exits with a non-zero status when the files don't match. + // Ignore that failure as long as we get output. + err = nil + } + return +} diff --git a/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go b/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go new file mode 100644 index 000000000..a98495c76 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go @@ -0,0 +1,575 @@ +package printer + +import ( + "bytes" + "fmt" + "sort" + + "github.com/hashicorp/hcl/hcl/ast" + "github.com/hashicorp/hcl/hcl/token" +) + +const ( + blank = byte(' ') + newline = byte('\n') + tab = byte('\t') + infinity = 1 << 30 // offset or line +) + +var ( + unindent = []byte("\uE123") // in the private use space +) + +type printer struct { + cfg Config + prev token.Pos + + comments []*ast.CommentGroup // may be nil, contains all comments + standaloneComments []*ast.CommentGroup // contains all standalone comments (not assigned to any node) + + enableTrace bool + indentTrace int +} + +type ByPosition []*ast.CommentGroup + +func (b ByPosition) Len() int { return len(b) } +func (b ByPosition) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b ByPosition) Less(i, j int) bool { return b[i].Pos().Before(b[j].Pos()) } + +// collectComments comments all standalone comments which are not lead or line +// comment +func (p *printer) collectComments(node ast.Node) { + // first collect all comments. This is already stored in + // ast.File.(comments) + ast.Walk(node, func(nn ast.Node) (ast.Node, bool) { + switch t := nn.(type) { + case *ast.File: + p.comments = t.Comments + return nn, false + } + return nn, true + }) + + standaloneComments := make(map[token.Pos]*ast.CommentGroup, 0) + for _, c := range p.comments { + standaloneComments[c.Pos()] = c + } + + // next remove all lead and line comments from the overall comment map. + // This will give us comments which are standalone, comments which are not + // assigned to any kind of node. + ast.Walk(node, func(nn ast.Node) (ast.Node, bool) { + switch t := nn.(type) { + case *ast.LiteralType: + if t.LineComment != nil { + for _, comment := range t.LineComment.List { + if _, ok := standaloneComments[comment.Pos()]; ok { + delete(standaloneComments, comment.Pos()) + } + } + } + case *ast.ObjectItem: + if t.LeadComment != nil { + for _, comment := range t.LeadComment.List { + if _, ok := standaloneComments[comment.Pos()]; ok { + delete(standaloneComments, comment.Pos()) + } + } + } + + if t.LineComment != nil { + for _, comment := range t.LineComment.List { + if _, ok := standaloneComments[comment.Pos()]; ok { + delete(standaloneComments, comment.Pos()) + } + } + } + } + + return nn, true + }) + + for _, c := range standaloneComments { + p.standaloneComments = append(p.standaloneComments, c) + } + + sort.Sort(ByPosition(p.standaloneComments)) + +} + +// output prints creates b printable HCL output and returns it. +func (p *printer) output(n interface{}) []byte { + var buf bytes.Buffer + + switch t := n.(type) { + case *ast.File: + return p.output(t.Node) + case *ast.ObjectList: + var index int + var nextItem token.Pos + var commented bool + for { + // TODO(arslan): refactor below comment printing, we have the same in objectType + for _, c := range p.standaloneComments { + for _, comment := range c.List { + if index != len(t.Items) { + nextItem = t.Items[index].Pos() + } else { + nextItem = token.Pos{Offset: infinity, Line: infinity} + } + + if comment.Pos().After(p.prev) && comment.Pos().Before(nextItem) { + // if we hit the end add newlines so we can print the comment + if index == len(t.Items) { + buf.Write([]byte{newline, newline}) + } + + buf.WriteString(comment.Text) + + buf.WriteByte(newline) + if index != len(t.Items) { + buf.WriteByte(newline) + } + } + } + } + + if index == len(t.Items) { + break + } + + buf.Write(p.output(t.Items[index])) + if !commented && index != len(t.Items)-1 { + buf.Write([]byte{newline, newline}) + } + index++ + } + case *ast.ObjectKey: + buf.WriteString(t.Token.Text) + case *ast.ObjectItem: + p.prev = t.Pos() + buf.Write(p.objectItem(t)) + case *ast.LiteralType: + buf.Write(p.literalType(t)) + case *ast.ListType: + buf.Write(p.list(t)) + case *ast.ObjectType: + buf.Write(p.objectType(t)) + default: + fmt.Printf(" unknown type: %T\n", n) + } + + return buf.Bytes() +} + +func (p *printer) literalType(lit *ast.LiteralType) []byte { + result := []byte(lit.Token.Text) + if lit.Token.Type == token.HEREDOC { + // Clear the trailing newline from heredocs + if result[len(result)-1] == '\n' { + result = result[:len(result)-1] + } + + // Poison lines 2+ so that we don't indent them + result = p.heredocIndent(result) + } + + return result +} + +// objectItem returns the printable HCL form of an object item. An object type +// starts with one/multiple keys and has a value. The value might be of any +// type. +func (p *printer) objectItem(o *ast.ObjectItem) []byte { + defer un(trace(p, fmt.Sprintf("ObjectItem: %s", o.Keys[0].Token.Text))) + var buf bytes.Buffer + + if o.LeadComment != nil { + for _, comment := range o.LeadComment.List { + buf.WriteString(comment.Text) + buf.WriteByte(newline) + } + } + + for i, k := range o.Keys { + buf.WriteString(k.Token.Text) + buf.WriteByte(blank) + + // reach end of key + if o.Assign.IsValid() && i == len(o.Keys)-1 && len(o.Keys) == 1 { + buf.WriteString("=") + buf.WriteByte(blank) + } + } + + buf.Write(p.output(o.Val)) + + if o.Val.Pos().Line == o.Keys[0].Pos().Line && o.LineComment != nil { + buf.WriteByte(blank) + for _, comment := range o.LineComment.List { + buf.WriteString(comment.Text) + } + } + + return buf.Bytes() +} + +// objectType returns the printable HCL form of an object type. An object type +// begins with a brace and ends with a brace. +func (p *printer) objectType(o *ast.ObjectType) []byte { + defer un(trace(p, "ObjectType")) + var buf bytes.Buffer + buf.WriteString("{") + buf.WriteByte(newline) + + var index int + var nextItem token.Pos + var commented bool + for { + // Print stand alone comments + for _, c := range p.standaloneComments { + for _, comment := range c.List { + // if we hit the end, last item should be the brace + if index != len(o.List.Items) { + nextItem = o.List.Items[index].Pos() + } else { + nextItem = o.Rbrace + } + + if comment.Pos().After(p.prev) && comment.Pos().Before(nextItem) { + // add newline if it's between other printed nodes + if index > 0 { + commented = true + buf.WriteByte(newline) + } + + buf.Write(p.indent([]byte(comment.Text))) + buf.WriteByte(newline) + if index != len(o.List.Items) { + buf.WriteByte(newline) // do not print on the end + } + } + } + } + + if index == len(o.List.Items) { + p.prev = o.Rbrace + break + } + + // check if we have adjacent one liner items. If yes we'll going to align + // the comments. + var aligned []*ast.ObjectItem + for _, item := range o.List.Items[index:] { + // we don't group one line lists + if len(o.List.Items) == 1 { + break + } + + // one means a oneliner with out any lead comment + // two means a oneliner with lead comment + // anything else might be something else + cur := lines(string(p.objectItem(item))) + if cur > 2 { + break + } + + curPos := item.Pos() + + nextPos := token.Pos{} + if index != len(o.List.Items)-1 { + nextPos = o.List.Items[index+1].Pos() + } + + prevPos := token.Pos{} + if index != 0 { + prevPos = o.List.Items[index-1].Pos() + } + + // fmt.Println("DEBUG ----------------") + // fmt.Printf("prev = %+v prevPos: %s\n", prev, prevPos) + // fmt.Printf("cur = %+v curPos: %s\n", cur, curPos) + // fmt.Printf("next = %+v nextPos: %s\n", next, nextPos) + + if curPos.Line+1 == nextPos.Line { + aligned = append(aligned, item) + index++ + continue + } + + if curPos.Line-1 == prevPos.Line { + aligned = append(aligned, item) + index++ + + // finish if we have a new line or comment next. This happens + // if the next item is not adjacent + if curPos.Line+1 != nextPos.Line { + break + } + continue + } + + break + } + + // put newlines if the items are between other non aligned items. + // newlines are also added if there is a standalone comment already, so + // check it too + if !commented && index != len(aligned) { + buf.WriteByte(newline) + } + + if len(aligned) >= 1 { + p.prev = aligned[len(aligned)-1].Pos() + + items := p.alignedItems(aligned) + buf.Write(p.indent(items)) + } else { + p.prev = o.List.Items[index].Pos() + + buf.Write(p.indent(p.objectItem(o.List.Items[index]))) + index++ + } + + buf.WriteByte(newline) + } + + buf.WriteString("}") + return buf.Bytes() +} + +func (p *printer) alignedItems(items []*ast.ObjectItem) []byte { + var buf bytes.Buffer + + // find the longest key and value length, needed for alignment + var longestKeyLen int // longest key length + var longestValLen int // longest value length + for _, item := range items { + key := len(item.Keys[0].Token.Text) + val := len(p.output(item.Val)) + + if key > longestKeyLen { + longestKeyLen = key + } + + if val > longestValLen { + longestValLen = val + } + } + + for i, item := range items { + if item.LeadComment != nil { + for _, comment := range item.LeadComment.List { + buf.WriteString(comment.Text) + buf.WriteByte(newline) + } + } + + for i, k := range item.Keys { + keyLen := len(k.Token.Text) + buf.WriteString(k.Token.Text) + for i := 0; i < longestKeyLen-keyLen+1; i++ { + buf.WriteByte(blank) + } + + // reach end of key + if i == len(item.Keys)-1 && len(item.Keys) == 1 { + buf.WriteString("=") + buf.WriteByte(blank) + } + } + + val := p.output(item.Val) + valLen := len(val) + buf.Write(val) + + if item.Val.Pos().Line == item.Keys[0].Pos().Line && item.LineComment != nil { + for i := 0; i < longestValLen-valLen+1; i++ { + buf.WriteByte(blank) + } + + for _, comment := range item.LineComment.List { + buf.WriteString(comment.Text) + } + } + + // do not print for the last item + if i != len(items)-1 { + buf.WriteByte(newline) + } + } + + return buf.Bytes() +} + +// list returns the printable HCL form of an list type. +func (p *printer) list(l *ast.ListType) []byte { + var buf bytes.Buffer + buf.WriteString("[") + + var longestLine int + for _, item := range l.List { + // for now we assume that the list only contains literal types + if lit, ok := item.(*ast.LiteralType); ok { + lineLen := len(lit.Token.Text) + if lineLen > longestLine { + longestLine = lineLen + } + } + } + + insertSpaceBeforeItem := false + for i, item := range l.List { + if item.Pos().Line != l.Lbrack.Line { + // multiline list, add newline before we add each item + buf.WriteByte(newline) + insertSpaceBeforeItem = false + // also indent each line + val := p.output(item) + curLen := len(val) + buf.Write(p.indent(val)) + buf.WriteString(",") + + if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil { + // if the next item doesn't have any comments, do not align + buf.WriteByte(blank) // align one space + for i := 0; i < longestLine-curLen; i++ { + buf.WriteByte(blank) + } + + for _, comment := range lit.LineComment.List { + buf.WriteString(comment.Text) + } + } + + if i == len(l.List)-1 { + buf.WriteByte(newline) + } + } else { + if insertSpaceBeforeItem { + buf.WriteByte(blank) + insertSpaceBeforeItem = false + } + buf.Write(p.output(item)) + if i != len(l.List)-1 { + buf.WriteString(",") + insertSpaceBeforeItem = true + } + } + + } + + buf.WriteString("]") + return buf.Bytes() +} + +// indent indents the lines of the given buffer for each non-empty line +func (p *printer) indent(buf []byte) []byte { + var prefix []byte + if p.cfg.SpacesWidth != 0 { + for i := 0; i < p.cfg.SpacesWidth; i++ { + prefix = append(prefix, blank) + } + } else { + prefix = []byte{tab} + } + + var res []byte + bol := true + for _, c := range buf { + if bol && c != '\n' { + res = append(res, prefix...) + } + + res = append(res, c) + bol = c == '\n' + } + return res +} + +// unindent removes all the indentation from the tombstoned lines +func (p *printer) unindent(buf []byte) []byte { + var res []byte + for i := 0; i < len(buf); i++ { + skip := len(buf)-i <= len(unindent) + if !skip { + skip = !bytes.Equal(unindent, buf[i:i+len(unindent)]) + } + if skip { + res = append(res, buf[i]) + continue + } + + // We have a marker. we have to backtrace here and clean out + // any whitespace ahead of our tombstone up to a \n + for j := len(res) - 1; j >= 0; j-- { + if res[j] == '\n' { + break + } + + res = res[:j] + } + + // Skip the entire unindent marker + i += len(unindent) - 1 + } + + return res +} + +// heredocIndent marks all the 2nd and further lines as unindentable +func (p *printer) heredocIndent(buf []byte) []byte { + var res []byte + bol := false + for _, c := range buf { + if bol && c != '\n' { + res = append(res, unindent...) + } + res = append(res, c) + bol = c == '\n' + } + return res +} + +func lines(txt string) int { + endline := 1 + for i := 0; i < len(txt); i++ { + if txt[i] == '\n' { + endline++ + } + } + return endline +} + +// ---------------------------------------------------------------------------- +// Tracing support + +func (p *printer) printTrace(a ...interface{}) { + if !p.enableTrace { + return + } + + const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " + const n = len(dots) + i := 2 * p.indentTrace + for i > n { + fmt.Print(dots) + i -= n + } + // i <= n + fmt.Print(dots[0:i]) + fmt.Println(a...) +} + +func trace(p *printer, msg string) *printer { + p.printTrace(msg, "(") + p.indentTrace++ + return p +} + +// Usage pattern: defer un(trace(p, "...")) +func un(p *printer) { + p.indentTrace-- + p.printTrace(")") +} diff --git a/vendor/github.com/hashicorp/hcl/hcl/printer/printer.go b/vendor/github.com/hashicorp/hcl/hcl/printer/printer.go new file mode 100644 index 000000000..fb9df58d4 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl/printer/printer.go @@ -0,0 +1,64 @@ +// Package printer implements printing of AST nodes to HCL format. +package printer + +import ( + "bytes" + "io" + "text/tabwriter" + + "github.com/hashicorp/hcl/hcl/ast" + "github.com/hashicorp/hcl/hcl/parser" +) + +var DefaultConfig = Config{ + SpacesWidth: 2, +} + +// A Config node controls the output of Fprint. +type Config struct { + SpacesWidth int // if set, it will use spaces instead of tabs for alignment +} + +func (c *Config) Fprint(output io.Writer, node ast.Node) error { + p := &printer{ + cfg: *c, + comments: make([]*ast.CommentGroup, 0), + standaloneComments: make([]*ast.CommentGroup, 0), + // enableTrace: true, + } + + p.collectComments(node) + + if _, err := output.Write(p.unindent(p.output(node))); err != nil { + return err + } + + // flush tabwriter, if any + var err error + if tw, _ := output.(*tabwriter.Writer); tw != nil { + err = tw.Flush() + } + + return err +} + +// Fprint "pretty-prints" an HCL node to output +// It calls Config.Fprint with default settings. +func Fprint(output io.Writer, node ast.Node) error { + return DefaultConfig.Fprint(output, node) +} + +// Format formats src HCL and returns the result. +func Format(src []byte) ([]byte, error) { + node, err := parser.Parse(src) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + if err := DefaultConfig.Fprint(&buf, node); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/website/source/docs/commands/fmt.html.markdown b/website/source/docs/commands/fmt.html.markdown new file mode 100644 index 000000000..383f58d1d --- /dev/null +++ b/website/source/docs/commands/fmt.html.markdown @@ -0,0 +1,24 @@ +--- +layout: "docs" +page_title: "Command: fmt" +sidebar_current: "docs-commands-fmt" +description: |- + The `terraform fmt` command is used to rewrite Terraform configuration files to a canonical format and style. +--- + +# Command: fmt + +The `terraform fmt` command is used to rewrite Terraform configuration files +to a canonical format and style. + +## Usage + +Usage: `terraform fmt [options] [DIR]` + +`fmt` scans the current directory for configuration files. + +The command-line flags are all optional. The list of available flags are: + +* `-list=false` - List files whose formatting differs +* `-write=false` - Write result to source file instead of STDOUT +* `-diff=false` - Display diffs instead of rewriting files diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index a000ec6c8..e0576adcc 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -67,6 +67,10 @@ destroy + > + fmt + + > get