diff --git a/cli/blocks_test.go b/cli/blocks_test.go new file mode 100644 index 00000000..0a4801a1 --- /dev/null +++ b/cli/blocks_test.go @@ -0,0 +1,237 @@ +package cli + +import ( + "fmt" + "strings" + "testing" + + c "github.com/gookit/color" + "github.com/katbyte/terrafmt/lib/common" + "github.com/kylelemons/godebug/diff" + "github.com/spf13/afero" +) + +type block struct { + endLine int + text string +} + +var testcases = []struct { + name string + sourcefile string + lineCount int + expectedBlocks []block +}{ + { + name: "Go no change", + sourcefile: "testdata/no_diffs.go", + lineCount: 29, + expectedBlocks: []block{ + { + endLine: 12, + text: `resource "aws_s3_bucket" "simple" { + bucket = "tf-test-bucket-simple" +}`, + }, + { + endLine: 20, + text: `resource "aws_s3_bucket" "with-parameters" { + bucket = "tf-test-bucket-with-parameters-%d" +}`, + }, + { + endLine: 28, + text: `resource "aws_s3_bucket" "with-parameters-and-append" { + bucket = "tf-test-bucket-parameters-and-append-%d" +}`, + }, + }, + }, + { + name: "Go formatting", + sourcefile: "testdata/has_diffs.go", + lineCount: 39, + expectedBlocks: []block{ + { + endLine: 13, + text: `resource "aws_s3_bucket" "extra-lines" { + + bucket = "tf-test-bucket-extra-lines" +}`, + }, + { + endLine: 22, + text: `resource "aws_s3_bucket" "no-errors" { + bucket = "tf-test-bucket-no-errors-%d" +}`, + }, + { + endLine: 30, + text: `resource "aws_s3_bucket" "extra-space" { + bucket = "tf-test-bucket-extra-space-%d" +}`, + }, + { + endLine: 38, + text: `resource "aws_s3_bucket" "end-line" { + bucket = "tf-test-bucket-end-line-%d" +}`, + }, + }, + }, + { + name: "Go fmt verbs", + sourcefile: "testdata/fmt_compat.go", + lineCount: 33, + expectedBlocks: []block{ + { + endLine: 14, + text: `resource "aws_s3_bucket" "no-errors" { + bucket = "tf-test-bucket-no-errors-%d" + + %s +}`, + }, + { + endLine: 22, + text: `resource "aws_s3_bucket" "absolutely-nothing" { + bucket = "tf-test-bucket-absolutely-nothing" +}`, + }, + { + endLine: 32, + text: `resource "aws_s3_bucket" "extra-space" { + bucket = "tf-test-bucket-extra-space-%d" + + %s +}`, + }, + }, + }, + { + name: "Markdown no change", + sourcefile: "testdata/no_diffs.md", + lineCount: 25, + expectedBlocks: []block{ + { + endLine: 7, + text: `resource "aws_s3_bucket" "one" { + bucket = "tf-test-bucket-one" +}`, + }, + { + endLine: 13, + text: `resource "aws_s3_bucket" "two" { + bucket = "tf-test-bucket-two" +}`, + }, + { + endLine: 19, + text: `resource "aws_s3_bucket" "three" { + bucket = "tf-test-bucket-three" +}`, + }, + }, + }, + { + name: "Markdown formatting", + sourcefile: "testdata/has_diffs.md", + lineCount: 27, + expectedBlocks: []block{ + { + endLine: 8, + text: `resource "aws_s3_bucket" "extra-lines" { + + bucket = "tf-test-bucket-extra-lines" +}`, + }, + { + endLine: 14, + text: `resource "aws_s3_bucket" "no-errors" { + bucket = "tf-test-bucket-no-errors" +}`, + }, + { + endLine: 20, + text: `resource "aws_s3_bucket" "extra-space" { + bucket = "tf-test-bucket-extra-space" +}`, + }, + { + endLine: 27, + text: `resource "aws_s3_bucket" "end-line" { + bucket = "tf-test-bucket-end-line" +} + `, + }, + }, + }, +} + +func TestCmdBlocksDefault(t *testing.T) { + t.Parallel() + + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + + fs := afero.NewReadOnlyFs(afero.NewOsFs()) + + expectedBuilder := strings.Builder{} + for i, block := range testcase.expectedBlocks { + fmt.Fprint(&expectedBuilder, c.Sprintf("\n####### B%d @ #%d\n", i+1, block.endLine)) + fmt.Fprint(&expectedBuilder, block.text, "\n") + } + expected := expectedBuilder.String() + + var outB strings.Builder + var errB strings.Builder + log := common.CreateLogger(&errB) + err := findBlocksInFile(fs, log, testcase.sourcefile, false, nil, &outB, &errB) + actualStdOut := outB.String() + actualStdErr := errB.String() + + if err != nil { + t.Fatalf("Got an error when none was expected: %v", err) + } + + if actualStdOut != expected { + t.Errorf("Output does not match expected:\n%s", diff.Diff(actualStdOut, expected)) + } + + if actualStdErr != "" { + t.Errorf("Got error output:\n%s", actualStdErr) + } + }) + } +} + +func TestCmdBlocksVerbose(t *testing.T) { + t.Parallel() + + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + + fs := afero.NewReadOnlyFs(afero.NewOsFs()) + + var outB strings.Builder + var errB strings.Builder + log := common.CreateLogger(&errB) + err := findBlocksInFile(fs, log, testcase.sourcefile, true, nil, &outB, &errB) + actualStdErr := errB.String() + if err != nil { + t.Fatalf("Case %q: Got an error when none was expected: %v", testcase.name, err) + } + + expectedSummaryLine := c.String(fmt.Sprintf("Finished processing %d lines %d blocks!", testcase.lineCount, len(testcase.expectedBlocks))) + + summaryLine := strings.TrimSpace(actualStdErr) + if summaryLine != expectedSummaryLine { + t.Errorf("Case %q: Unexpected summary:\nexpected %s\ngot %s", testcase.name, expectedSummaryLine, summaryLine) + } + }) + } +} diff --git a/cli/cmds.go b/cli/cmds.go index e6d6316f..6f09884b 100644 --- a/cli/cmds.go +++ b/cli/cmds.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "fmt" + "io" "os" "os/exec" "path/filepath" @@ -17,6 +18,8 @@ import ( "github.com/katbyte/terrafmt/lib/format" "github.com/katbyte/terrafmt/lib/upgrade012" "github.com/katbyte/terrafmt/lib/version" + "github.com/sirupsen/logrus" + "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -39,78 +42,44 @@ func Make() *cobra.Command { Short: "formats terraform blocks in a directory, file, or stdin", Args: cobra.RangeArgs(0, 1), RunE: func(cmd *cobra.Command, args []string) error { + log := common.CreateLogger(cmd.ErrOrStderr()) + path := "" if len(args) == 1 { path = args[0] } - common.Log.Debugf("terrafmt %s", path) + log.Debugf("terrafmt %s", path) - pattern, _ := cmd.Flags().GetString("pattern") - filenames, err := allFiles(path, pattern) - fixFinishLines, _ := cmd.Flags().GetBool("fix-finish-lines") + fs := afero.NewOsFs() + pattern, _ := cmd.Flags().GetString("pattern") + filenames, err := allFiles(fs, path, pattern) if err != nil { return err } + fmtCompat := viper.GetBool("fmtcompat") + fixFinishLines, _ := cmd.Flags().GetBool("fix-finish-lines") + verbose := viper.GetBool("verbose") var errs *multierror.Error - var exitStatus int + var hasProcessingErrors bool for _, filename := range filenames { - blocksFormatted := 0 - - br := blocks.Reader{ - LineRead: blocks.ReaderPassthrough, - BlockRead: func(br *blocks.Reader, i int, b string) error { - var fb string - var err error - if viper.GetBool("fmtcompat") { - fb, err = format.FmtVerbBlock(b, filename) - } else { - fb, err = format.Block(b, filename) - } - - if err != nil { - return err - } - - _, err = br.Writer.Write([]byte(fb)) - - if err == nil && fb != b { - blocksFormatted++ - } - - return err - }, - FixFinishLines: fixFinishLines, - } - err := br.DoTheThing(filename) - - fc := "magenta" - if blocksFormatted > 0 { - fc = "lightMagenta" - } - - if viper.GetBool("verbose") { - // nolint staticcheck - fmt.Fprintf(os.Stderr, c.Sprintf("<%s>%s: %d lines & formatted %d/%d blocks!\n", fc, br.FileName, br.LineCount, blocksFormatted, br.BlockCount)) - } + br, err := formatFile(fs, log, filename, fmtCompat, fixFinishLines, verbose, cmd.InOrStdin(), cmd.OutOrStdout(), cmd.ErrOrStderr()) if err != nil { errs = multierror.Append(errs, err) } if br.ErrorBlocks > 0 { - exitStatus = 1 + hasProcessingErrors = true } } - if errs != nil { return errs } - - if exitStatus != 0 { - os.Exit(exitStatus) + if hasProcessingErrors { + os.Exit(1) } return nil @@ -127,50 +96,23 @@ func Make() *cobra.Command { Short: "formats terraform blocks to 0.12 format in a single file or on stdin", Args: cobra.RangeArgs(0, 1), RunE: func(cmd *cobra.Command, args []string) error { + log := common.CreateLogger(cmd.ErrOrStderr()) + filename := "" if len(args) == 1 { filename = args[0] } - common.Log.Debugf("terrafmt upgrade012 %s", filename) - - blocksFormatted := 0 - br := blocks.Reader{ - LineRead: blocks.ReaderPassthrough, - BlockRead: func(br *blocks.Reader, i int, b string) error { - var fb string - var err error - if viper.GetBool("fmtcompat") { - fb, err = upgrade012.Upgrade12VerbBlock(b) - } else { - fb, err = upgrade012.Block(b) - } - - if err != nil { - return err - } + log.Debugf("terrafmt upgrade012 %s", filename) - if _, err = br.Writer.Write([]byte(fb)); err == nil && fb != b { - blocksFormatted++ - } + fmtverbs := viper.GetBool("fmtcompat") + verbose := viper.GetBool("verbose") - return nil - }, - } - err := br.DoTheThing(filename) + fs := afero.NewOsFs() + br, err := upgrade012File(fs, log, filename, fmtverbs, verbose, cmd.InOrStdin(), cmd.OutOrStdout(), cmd.ErrOrStderr()) if err != nil { return err } - fc := "magenta" - if blocksFormatted > 0 { - fc = "lightMagenta" - } - - if viper.GetBool("verbose") { - // nolint staticcheck - fmt.Fprintf(os.Stderr, c.Sprintf("<%s>%s: %d lines & formatted %d/%d blocks!\n", fc, br.FileName, br.LineCount, blocksFormatted, br.BlockCount)) - } - if br.ErrorBlocks > 0 { os.Exit(-1) } @@ -185,102 +127,48 @@ func Make() *cobra.Command { Short: "formats terraform blocks in a directory, file, or stdin and shows the difference", Args: cobra.RangeArgs(0, 1), RunE: func(cmd *cobra.Command, args []string) error { + log := common.CreateLogger(cmd.ErrOrStderr()) + path := "" if len(args) == 1 { path = args[0] } - common.Log.Debugf("terrafmt fmt %s", path) + log.Debugf("terrafmt fmt %s", path) - pattern, _ := cmd.Flags().GetString("pattern") - filenames, err := allFiles(path, pattern) + fs := afero.NewOsFs() + pattern, _ := cmd.Flags().GetString("pattern") + filenames, err := allFiles(fs, path, pattern) if err != nil { return err } var errs *multierror.Error - var exitStatus int var hasDiff bool + var hasProcessingErrors bool for _, filename := range filenames { - blocksWithDiff := 0 - br := blocks.Reader{ - ReadOnly: true, - LineRead: blocks.ReaderPassthrough, - BlockRead: func(br *blocks.Reader, i int, b string) error { - var fb string - var err error - if viper.GetBool("fmtcompat") { - fb, err = format.FmtVerbBlock(b, filename) - } else { - fb, err = format.Block(b, filename) - } - if err != nil { - return err - } - - if fb == b { - return nil - } - blocksWithDiff++ - - // nolint staticcheck - fmt.Fprintf(os.Stdout, c.Sprintf("%s:%d\n", br.FileName, br.LineCount-br.BlockCurrentLine)) - - if !viper.GetBool("quiet") { - d := diff.LineDiff(b, fb) - scanner := bufio.NewScanner(strings.NewReader(d)) - for scanner.Scan() { - l := scanner.Text() - if strings.HasPrefix(l, "+") { - fmt.Fprint(os.Stdout, c.Sprintf("%s\n", l)) - } else if strings.HasPrefix(l, "-") { - fmt.Fprint(os.Stdout, c.Sprintf("%s\n", l)) - } else { - fmt.Fprint(os.Stdout, l+"\n") - } - } - } - - return nil - }, - } - - err := br.DoTheThing(filename) + br, fileDiff, err := diffFile(fs, log, filename, viper.GetBool("fmtcompat"), viper.GetBool("verbose"), cmd.InOrStdin(), cmd.OutOrStdout(), cmd.ErrOrStderr()) if err != nil { errs = multierror.Append(errs, err) continue } - - if blocksWithDiff > 0 { - hasDiff = true - } - - fc := "magenta" - if hasDiff { - fc = "lightMagenta" - } - - if viper.GetBool("verbose") { - // nolint staticcheck - fmt.Fprintf(os.Stderr, c.Sprintf("<%s>%s: %d lines & %d/%d blocks need formatting.\n", fc, br.FileName, br.LineCount, blocksWithDiff, br.BlockCount)) - } - if br.ErrorBlocks > 0 { - exitStatus = 1 + hasProcessingErrors = true + } + if fileDiff { + hasDiff = true } } - if errs != nil { return errs } if viper.GetBool("check") && hasDiff { - exitStatus = 1 + os.Exit(1) } - - if exitStatus != 0 { - os.Exit(exitStatus) + if hasProcessingErrors { + os.Exit(1) } return nil @@ -297,34 +185,16 @@ func Make() *cobra.Command { //options: no header (######), format (json? xml? etc), only should block x? Args: cobra.RangeArgs(0, 1), RunE: func(cmd *cobra.Command, args []string) error { + log := common.CreateLogger(cmd.ErrOrStderr()) + filename := "" if len(args) == 1 { filename = args[0] } - common.Log.Debugf("terrafmt blocks %s", filename) - - br := blocks.Reader{ - ReadOnly: true, - LineRead: blocks.ReaderIgnore, - BlockRead: func(br *blocks.Reader, i int, b string) error { - // nolint staticcheck - fmt.Fprintf(os.Stdout, c.Sprintf("\n####### B%d @ #%d\n", br.BlockCount, br.LineCount)) - fmt.Fprint(os.Stdout, b) - return nil - }, - } - - err := br.DoTheThing(filename) - - if err != nil { - return err - } - - //blocks - // nolint staticcheck - fmt.Fprintf(os.Stderr, c.Sprintf("\nFinished processing %d lines %d blocks!\n", br.LineCount, br.BlockCount)) + log.Debugf("terrafmt blocks %s", filename) - return nil + fs := afero.NewOsFs() + return findBlocksInFile(fs, log, filename, viper.GetBool("verbose"), cmd.InOrStdin(), cmd.OutOrStdout(), cmd.ErrOrStderr()) }, }) @@ -359,12 +229,12 @@ func Make() *cobra.Command { return root } -func allFiles(path string, pattern string) ([]string, error) { +func allFiles(fs afero.Fs, path string, pattern string) ([]string, error) { if path == "" { return []string{""}, nil } - info, err := os.Stat(path) + info, err := fs.Stat(path) if err != nil { return nil, fmt.Errorf("error reading path (%s): %s", path, err) @@ -376,8 +246,7 @@ func allFiles(path string, pattern string) ([]string, error) { var filenames []string - err = filepath.Walk( - path, + err = afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -408,13 +277,15 @@ func allFiles(path string, pattern string) ([]string, error) { ) if err != nil { - return nil, fmt.Errorf("error walking path (%s): %s", path, err) + return nil, fmt.Errorf("error walking path (%s): %w", path, err) } return filenames, nil } func versionCmd(cmd *cobra.Command, args []string) { + log := common.CreateLogger(cmd.ErrOrStderr()) + // nolint errcheck fmt.Println("terrafmt v" + version.Version + "-" + version.GitCommit) @@ -424,10 +295,184 @@ func versionCmd(cmd *cobra.Command, args []string) { tfCmd.Stdout = stdout tfCmd.Stderr = stderr if err := tfCmd.Run(); err != nil { - common.Log.Warnf("Error running terraform: %s", err) + log.Warnf("Error running terraform: %s", err) return } terraformVersion := strings.SplitN(stdout.String(), "\n", 2)[0] // nolint errcheck fmt.Println(" + " + terraformVersion) } + +func findBlocksInFile(fs afero.Fs, log *logrus.Logger, filename string, verbose bool, stdin io.Reader, stdout, stderr io.Writer) error { + br := blocks.Reader{ + Log: log, + ReadOnly: true, + LineRead: blocks.ReaderIgnore, + BlockRead: func(br *blocks.Reader, i int, b string) error { + outW := stdout + fmt.Fprint(outW, c.Sprintf("\n####### B%d @ #%d\n", br.BlockCount, br.LineCount)) + fmt.Fprint(outW, b) + return nil + }, + } + + err := br.DoTheThing(fs, filename, stdin, stdout) + if err != nil { + return err + } + + if verbose { + fmt.Fprint(stderr, c.Sprintf("\nFinished processing %d lines %d blocks!\n", br.LineCount, br.BlockCount)) + } + + return nil +} + +func diffFile(fs afero.Fs, log *logrus.Logger, filename string, fmtverbs, verbose bool, stdin io.Reader, stdout, stderr io.Writer) (*blocks.Reader, bool, error) { + blocksWithDiff := 0 + br := blocks.Reader{ + Log: log, + ReadOnly: true, + LineRead: blocks.ReaderPassthrough, + BlockRead: func(br *blocks.Reader, i int, b string) error { + var fb string + var err error + if fmtverbs { + fb, err = format.FmtVerbBlock(log, b, filename) + } else { + fb, err = format.Block(log, b, filename) + } + if err != nil { + return err + } + + if fb == b { + return nil + } + blocksWithDiff++ + + outW := stdout + + fmt.Fprint(outW, c.Sprintf("%s:%d\n", br.FileName, br.LineCount-br.BlockCurrentLine)) + + if !viper.GetBool("quiet") { + d := diff.LineDiff(b, fb) + scanner := bufio.NewScanner(strings.NewReader(d)) + for scanner.Scan() { + l := scanner.Text() + if strings.HasPrefix(l, "+") { + fmt.Fprint(outW, c.Sprintf("%s\n", l)) + } else if strings.HasPrefix(l, "-") { + fmt.Fprint(outW, c.Sprintf("%s\n", l)) + } else { + fmt.Fprint(outW, l+"\n") + } + } + } + + return nil + }, + } + + err := br.DoTheThing(fs, filename, stdin, stdout) + if err != nil { + return nil, false, err + } + + hasDiff := (blocksWithDiff > 0) + + fc := "magenta" + if hasDiff { + fc = "lightMagenta" + } + + if verbose { + fmt.Fprint(stderr, c.Sprintf("<%s>%s: %d lines & %d/%d blocks need formatting.\n", fc, br.FileName, br.LineCount, blocksWithDiff, br.BlockCount)) + } + + return &br, hasDiff, nil +} + +func formatFile(fs afero.Fs, log *logrus.Logger, filename string, fmtverbs, fixFinishLines, verbose bool, stdin io.Reader, stdout, stderr io.Writer) (*blocks.Reader, error) { + blocksFormatted := 0 + + br := blocks.Reader{ + Log: log, + LineRead: blocks.ReaderPassthrough, + BlockRead: func(br *blocks.Reader, i int, b string) error { + var fb string + var err error + if fmtverbs { + fb, err = format.FmtVerbBlock(log, b, filename) + } else { + fb, err = format.Block(log, b, filename) + } + if err != nil { + return err + } + + _, err = br.Writer.Write([]byte(fb)) + + if err == nil && fb != b { + blocksFormatted++ + } + + return err + }, + FixFinishLines: fixFinishLines, + } + err := br.DoTheThing(fs, filename, stdin, stdout) + + fc := "magenta" + if blocksFormatted > 0 { + fc = "lightMagenta" + } + + if verbose { + fmt.Fprint(stderr, c.Sprintf("<%s>%s: %d lines & formatted %d/%d blocks!\n", fc, br.FileName, br.LineCount, blocksFormatted, br.BlockCount)) + } + + return &br, err +} + +func upgrade012File(fs afero.Fs, log *logrus.Logger, filename string, fmtverbs, verbose bool, stdin io.Reader, stdout, stderr io.Writer) (*blocks.Reader, error) { + blocksFormatted := 0 + br := blocks.Reader{ + Log: log, + LineRead: blocks.ReaderPassthrough, + BlockRead: func(br *blocks.Reader, i int, b string) error { + var fb string + var err error + if fmtverbs { + fb, err = upgrade012.Upgrade12VerbBlock(log, b) + } else { + fb, err = upgrade012.Block(log, b) + } + + if err != nil { + return err + } + + if _, err = br.Writer.Write([]byte(fb)); err == nil && fb != b { + blocksFormatted++ + } + + return nil + }, + } + err := br.DoTheThing(fs, filename, stdin, stdout) + if err != nil { + return &br, err + } + + fc := "magenta" + if blocksFormatted > 0 { + fc = "lightMagenta" + } + + if verbose { + fmt.Fprint(stderr, c.Sprintf("<%s>%s: %d lines & formatted %d/%d blocks!\n", fc, br.FileName, br.LineCount, blocksFormatted, br.BlockCount)) + } + + return &br, err +} diff --git a/cli/cmds_test.go b/cli/cmds_test.go new file mode 100644 index 00000000..4c9dc449 --- /dev/null +++ b/cli/cmds_test.go @@ -0,0 +1,41 @@ +package cli + +import ( + "regexp" + "testing" +) + +var logMsgRegexp *regexp.Regexp + +func init() { + logMsgRegexp = regexp.MustCompile(".*msg=\"([^\"]+)\".*\n") +} + +func checkExpectedErrors(t *testing.T, errOutput string, expectedErrs []string) { + if expectedErrCount := len(expectedErrs); expectedErrCount > 0 { + allMatches := logMsgRegexp.FindAllStringSubmatch(errOutput, -1) + actualErrs := make([]string, 0, len(allMatches)) + for _, matches := range allMatches { + if len(matches) == 2 { + actualErrs = append(actualErrs, matches[1]) + } + } + if expectedErrCount != len(actualErrs) { + t.Errorf("Expected %d error messages\n%#v,\ngot %d\n%#v", expectedErrCount, expectedErrs, len(actualErrs), actualErrs) + } else { + for i := range actualErrs { + match, err := regexp.MatchString(regexp.QuoteMeta(expectedErrs[i]), actualErrs[i]) + if err != nil { + t.Fatalf("Error message %d: error parsing regexp: %s", i+1, err) + } + if !match { + t.Errorf("Error message %d does not have match,\nexpected %q,\ngot %q", i+1, expectedErrs[i], actualErrs[i]) + } + } + } + } else { + if errOutput != "" { + t.Errorf("Got unexpected error output:\n%s", errOutput) + } + } +} diff --git a/cli/diff_test.go b/cli/diff_test.go new file mode 100644 index 00000000..db9aee29 --- /dev/null +++ b/cli/diff_test.go @@ -0,0 +1,195 @@ +package cli + +import ( + "fmt" + "strings" + "testing" + + c "github.com/gookit/color" + "github.com/katbyte/terrafmt/lib/common" + "github.com/kylelemons/godebug/diff" + "github.com/spf13/afero" +) + +func TestCmdDiffDefault(t *testing.T) { + testcases := []struct { + name string + sourcefile string + resultfile string + noDiff bool + errMsg []string + fmtcompat bool + }{ + { + name: "Go no change", + sourcefile: "testdata/no_diffs.go", + noDiff: true, + }, + { + name: "Go formatting", + sourcefile: "testdata/has_diffs.go", + resultfile: "testdata/has_diffs_diff.go.txt", + }, + { + name: "Go fmt verbs", + sourcefile: "testdata/fmt_compat.go", + resultfile: "testdata/fmt_compat_diff_nofmtcompat.go.txt", + fmtcompat: false, + errMsg: []string{ + "block 1 @ testdata/fmt_compat.go:8 failed to process with: failed to parse hcl: testdata/fmt_compat.go:4,3-4:", + "block 3 @ testdata/fmt_compat.go:26 failed to process with: failed to parse hcl: testdata/fmt_compat.go:4,3-4:", + }, + }, + { + name: "Go fmt verbs --fmtcompat", + sourcefile: "testdata/fmt_compat.go", + resultfile: "testdata/fmt_compat_diff_fmtcompat.go.txt", + fmtcompat: true, + }, + { + name: "Markdown no change", + sourcefile: "testdata/no_diffs.md", + noDiff: true, + }, + { + name: "Markdown formatting", + sourcefile: "testdata/has_diffs.md", + resultfile: "testdata/has_diffs_diff.md.txt", + }, + } + + t.Parallel() + + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + + fs := afero.NewReadOnlyFs(afero.NewOsFs()) + + expected := "" + if !testcase.noDiff { + data, err := afero.ReadFile(fs, testcase.resultfile) + if err != nil { + t.Fatalf("Error reading test result file %q: %s", testcase.resultfile, err) + } + expected = c.String(string(data)) + } + + var outB strings.Builder + var errB strings.Builder + log := common.CreateLogger(&errB) + _, _, err := diffFile(fs, log, testcase.sourcefile, testcase.fmtcompat, false, nil, &outB, &errB) + actualStdOut := outB.String() + actualStdErr := errB.String() + + if err != nil { + t.Fatalf("Got an error when none was expected: %v", err) + } + + if actualStdOut != expected { + t.Errorf("Output does not match expected:\n%s", diff.Diff(actualStdOut, expected)) + } + + checkExpectedErrors(t, actualStdErr, testcase.errMsg) + }) + } +} + +func TestCmdDiffVerbose(t *testing.T) { + testcases := []struct { + name string + sourcefile string + noDiff bool + lineCount int + unformattedBlockCount int + totalBlockCount int + fmtcompat bool + }{ + { + name: "Go no change", + sourcefile: "testdata/no_diffs.go", + noDiff: true, + lineCount: 29, + totalBlockCount: 3, + }, + { + name: "Go formatting", + sourcefile: "testdata/has_diffs.go", + lineCount: 39, + unformattedBlockCount: 2, + totalBlockCount: 4, + }, + { + name: "Go fmt verbs", + sourcefile: "testdata/fmt_compat.go", + noDiff: true, // The only diff is in the block with the parsing error + lineCount: 33, + totalBlockCount: 3, + fmtcompat: false, + }, + { + name: "Go fmt verbs --fmtcompat", + sourcefile: "testdata/fmt_compat.go", + lineCount: 33, + unformattedBlockCount: 1, + totalBlockCount: 3, + fmtcompat: true, + }, + { + name: "Markdown no change", + sourcefile: "testdata/no_diffs.md", + noDiff: true, + lineCount: 25, + totalBlockCount: 3, + }, + { + name: "Markdown formatting", + sourcefile: "testdata/has_diffs.md", + lineCount: 27, + unformattedBlockCount: 3, + totalBlockCount: 4, + }, + } + + t.Parallel() + + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + + fs := afero.NewReadOnlyFs(afero.NewOsFs()) + + var outB strings.Builder + var errB strings.Builder + log := common.CreateLogger(&errB) + _, _, err := diffFile(fs, log, testcase.sourcefile, testcase.fmtcompat, true, nil, &outB, &errB) + actualStdErr := errB.String() + + if err != nil { + t.Fatalf("Got an error when none was expected: %v", err) + } + + filenameColor := "lightMagenta" + if testcase.noDiff { + filenameColor = "magenta" + } + expectedSummaryLine := c.String(fmt.Sprintf( + "<%s>%s: %d lines & %d/%d blocks need formatting.", + filenameColor, + testcase.sourcefile, + testcase.lineCount, + testcase.unformattedBlockCount, + testcase.totalBlockCount, + )) + + trimmedStdErr := strings.TrimSpace(actualStdErr) + lines := strings.Split(trimmedStdErr, "\n") + summaryLine := lines[len(lines)-1] + if summaryLine != expectedSummaryLine { + t.Errorf("Unexpected summary:\nexpected %s\ngot %s", expectedSummaryLine, summaryLine) + } + }) + } +} diff --git a/cli/fmt_test.go b/cli/fmt_test.go new file mode 100644 index 00000000..354658fa --- /dev/null +++ b/cli/fmt_test.go @@ -0,0 +1,467 @@ +package cli + +import ( + "fmt" + "strings" + "testing" + + c "github.com/gookit/color" + "github.com/katbyte/terrafmt/lib/common" + "github.com/kylelemons/godebug/diff" + "github.com/spf13/afero" +) + +func TestCmdFmtStdinDefault(t *testing.T) { + testcases := []struct { + name string + sourcefile string + resultfile string + noDiff bool + errMsg []string + fmtcompat bool + fixFinishLines bool + }{ + { + name: "Go no change", + sourcefile: "testdata/no_diffs.go", + noDiff: true, + }, + { + name: "Go formatting", + sourcefile: "testdata/has_diffs.go", + resultfile: "testdata/has_diffs_fmt.go", + }, + { + name: "Go formatting, fix finish line", + sourcefile: "testdata/has_diffs.go", + resultfile: "testdata/has_diffs_fmt_fix_finish.go", + fixFinishLines: true, + }, + { + name: "Go fmt verbs", + sourcefile: "testdata/fmt_compat.go", + noDiff: true, + fmtcompat: false, + errMsg: []string{ + "block 1 @ stdin:8 failed to process with: failed to parse hcl: :4,3-4:", + "block 3 @ stdin:26 failed to process with: failed to parse hcl: :4,3-4:", + }, + }, + { + name: "Go fmt verbs --fmtcompat", + sourcefile: "testdata/fmt_compat.go", + resultfile: "testdata/fmt_compat_fmtcompat.go", + fmtcompat: true, + }, + { + name: "Markdown no change", + sourcefile: "testdata/no_diffs.md", + noDiff: true, + }, + { + name: "Markdown formatting", + sourcefile: "testdata/has_diffs.md", + resultfile: "testdata/has_diffs_fmt.md", + }, + { + name: "Markdown formatting, fix finish line", + sourcefile: "testdata/has_diffs.md", + resultfile: "testdata/has_diffs_fmt.md", + fixFinishLines: true, + }, + } + + t.Parallel() + + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + + fs := afero.NewReadOnlyFs(afero.NewOsFs()) + + inR, err := fs.Open(testcase.sourcefile) + if err != nil { + t.Fatalf("Error opening test input file %q: %s", testcase.sourcefile, err) + } + defer inR.Close() + + resultfile := testcase.resultfile + if testcase.noDiff { + resultfile = testcase.sourcefile + } + data, err := afero.ReadFile(fs, resultfile) + if err != nil { + t.Fatalf("Error reading test result file %q: %s", resultfile, err) + } + expected := c.String(string(data)) + + var outB strings.Builder + var errB strings.Builder + log := common.CreateLogger(&errB) + _, err = formatFile(fs, log, "", testcase.fmtcompat, testcase.fixFinishLines, false, inR, &outB, &errB) + actualStdOut := outB.String() + actualStdErr := errB.String() + + if err != nil { + t.Fatalf("Case %q: Got an error when none was expected: %v", testcase.name, err) + } + + if actualStdOut != expected { + t.Errorf("Case %q: Output does not match expected:\n%s", testcase.name, diff.Diff(actualStdOut, expected)) + } + + checkExpectedErrors(t, actualStdErr, testcase.errMsg) + }) + } +} + +func TestCmdFmtStdinVerbose(t *testing.T) { + testcases := []struct { + name string + sourcefile string + noDiff bool + lineCount int + updatedBlockCount int + totalBlockCount int + fmtcompat bool + fixFinishLines bool + }{ + { + name: "Go no change", + sourcefile: "testdata/no_diffs.go", + noDiff: true, + lineCount: 29, + totalBlockCount: 3, + }, + { + name: "Go formatting", + sourcefile: "testdata/has_diffs.go", + lineCount: 39, + updatedBlockCount: 2, + totalBlockCount: 4, + }, + { + name: "Go formatting, fix finish line", + sourcefile: "testdata/has_diffs.go", + lineCount: 39, + updatedBlockCount: 2, + totalBlockCount: 4, + fixFinishLines: true, + }, + { + name: "Go fmt verbs", + sourcefile: "testdata/fmt_compat.go", + noDiff: true, + lineCount: 33, + totalBlockCount: 3, + fmtcompat: false, + }, + { + name: "Go fmt verbs --fmtcompat", + sourcefile: "testdata/fmt_compat.go", + lineCount: 33, + updatedBlockCount: 1, + totalBlockCount: 3, + fmtcompat: true, + }, + { + name: "Markdown no change", + sourcefile: "testdata/no_diffs.md", + noDiff: true, + lineCount: 25, + totalBlockCount: 3, + }, + { + name: "Markdown formatting", + sourcefile: "testdata/has_diffs.md", + lineCount: 27, + updatedBlockCount: 3, + totalBlockCount: 4, + }, + { + name: "Markdown formatting, fix finish line", + sourcefile: "testdata/has_diffs.md", + lineCount: 27, + updatedBlockCount: 3, + totalBlockCount: 4, + fixFinishLines: true, + }, + } + + t.Parallel() + + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + + fs := afero.NewReadOnlyFs(afero.NewOsFs()) + + inR, err := fs.Open(testcase.sourcefile) + if err != nil { + t.Fatalf("Error opening test input file %q: %s", testcase.sourcefile, err) + } + defer inR.Close() + + var outB strings.Builder + var errB strings.Builder + log := common.CreateLogger(&errB) + _, err = formatFile(fs, log, "", testcase.fmtcompat, testcase.fixFinishLines, true, inR, &outB, &errB) + actualStdErr := errB.String() + + if err != nil { + t.Fatalf("Case %q: Got an error when none was expected: %v", testcase.name, err) + } + + filenameColor := "lightMagenta" + if testcase.noDiff { + filenameColor = "magenta" + } + expectedSummaryLine := c.String(fmt.Sprintf( + "<%s>%s: %d lines & formatted %d/%d blocks!", + filenameColor, + "stdin", + testcase.lineCount, + testcase.updatedBlockCount, + testcase.totalBlockCount, + )) + + trimmedStdErr := strings.TrimSpace(actualStdErr) + lines := strings.Split(trimmedStdErr, "\n") + summaryLine := lines[len(lines)-1] + if summaryLine != expectedSummaryLine { + t.Errorf("Case %q: Unexpected summary:\nexpected %s\ngot %s", testcase.name, expectedSummaryLine, summaryLine) + } + }) + } +} + +func TestCmdFmtFileDefault(t *testing.T) { + testcases := []struct { + name string + sourcefile string + resultfile string + noDiff bool + errMsg []string + fmtcompat bool + fixFinishLines bool + }{ + { + name: "Go no change", + sourcefile: "testdata/no_diffs.go", + noDiff: true, + }, + { + name: "Go formatting", + sourcefile: "testdata/has_diffs.go", + resultfile: "testdata/has_diffs_fmt.go", + }, + { + name: "Go formatting, fix finish line", + sourcefile: "testdata/has_diffs.go", + resultfile: "testdata/has_diffs_fmt_fix_finish.go", + fixFinishLines: true, + }, + { + name: "Go fmt verbs", + sourcefile: "testdata/fmt_compat.go", + noDiff: true, + fmtcompat: false, + errMsg: []string{ + "block 1 @ testdata/fmt_compat.go:8 failed to process with: failed to parse hcl: testdata/fmt_compat.go:4,3-4:", + "block 3 @ testdata/fmt_compat.go:26 failed to process with: failed to parse hcl: testdata/fmt_compat.go:4,3-4:", + }, + }, + { + name: "Go fmt verbs --fmtcompat", + sourcefile: "testdata/fmt_compat.go", + resultfile: "testdata/fmt_compat_fmtcompat.go", + fmtcompat: true, + }, + { + name: "Markdown no change", + sourcefile: "testdata/no_diffs.md", + noDiff: true, + }, + { + name: "Markdown formatting", + sourcefile: "testdata/has_diffs.md", + resultfile: "testdata/has_diffs_fmt.md", + }, + { + name: "Markdown formatting, fix finish line", + sourcefile: "testdata/has_diffs.md", + resultfile: "testdata/has_diffs_fmt.md", + fixFinishLines: true, + }, + } + + t.Parallel() + + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + + fs := afero.NewCopyOnWriteFs( + afero.NewReadOnlyFs(afero.NewOsFs()), + afero.NewMemMapFs(), + ) + + resultfile := testcase.resultfile + if testcase.noDiff { + resultfile = testcase.sourcefile + } + data, err := afero.ReadFile(fs, resultfile) + if err != nil { + t.Fatalf("Error reading test result file %q: %s", resultfile, err) + } + expected := c.String(string(data)) + + var outB strings.Builder + var errB strings.Builder + log := common.CreateLogger(&errB) + _, err = formatFile(fs, log, testcase.sourcefile, testcase.fmtcompat, testcase.fixFinishLines, false, nil, &outB, &errB) + actualStdOut := outB.String() + actualStdErr := errB.String() + + if err != nil { + t.Fatalf("Case %q: Got an error when none was expected: %v", testcase.name, err) + } + + if actualStdOut != "" { + t.Errorf("Case %q: Stdout should not have output, got:\n%s", testcase.name, actualStdOut) + } + + data, err = afero.ReadFile(fs, testcase.sourcefile) + if err != nil { + t.Fatalf("Error reading results from file %q: %s", resultfile, err) + } + actualContent := c.String(string(data)) + if actualContent != expected { + t.Errorf("Case %q: File does not match expected:\n%s", testcase.name, diff.Diff(actualContent, expected)) + } + + checkExpectedErrors(t, actualStdErr, testcase.errMsg) + }) + } +} + +func TestCmdFmtFileVerbose(t *testing.T) { + testcases := []struct { + name string + sourcefile string + noDiff bool + lineCount int + updatedBlockCount int + totalBlockCount int + fmtcompat bool + fixFinishLines bool + }{ + { + name: "Go no change", + sourcefile: "testdata/no_diffs.go", + noDiff: true, + lineCount: 29, + totalBlockCount: 3, + }, + { + name: "Go formatting", + sourcefile: "testdata/has_diffs.go", + lineCount: 39, + updatedBlockCount: 2, + totalBlockCount: 4, + }, + { + name: "Go formatting, fix finish line", + sourcefile: "testdata/has_diffs.go", + lineCount: 39, + updatedBlockCount: 2, // This should technically be 3, but it's not counting the finish-line-only case + totalBlockCount: 4, + fixFinishLines: true, + }, + { + name: "Go fmt verbs", + sourcefile: "testdata/fmt_compat.go", + noDiff: true, + lineCount: 33, + totalBlockCount: 3, + fmtcompat: false, + }, + { + name: "Go fmt verbs --fmtcompat", + sourcefile: "testdata/fmt_compat.go", + lineCount: 33, + updatedBlockCount: 1, + totalBlockCount: 3, + fmtcompat: true, + }, + { + name: "Markdown no change", + sourcefile: "testdata/no_diffs.md", + noDiff: true, + lineCount: 25, + totalBlockCount: 3, + }, + { + name: "Markdown formatting", + sourcefile: "testdata/has_diffs.md", + lineCount: 27, + updatedBlockCount: 3, + totalBlockCount: 4, + }, + { + name: "Markdown formatting, fix finish line", + sourcefile: "testdata/has_diffs.md", + lineCount: 27, + updatedBlockCount: 3, + totalBlockCount: 4, + fixFinishLines: true, + }, + } + + t.Parallel() + + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + + fs := afero.NewCopyOnWriteFs( + afero.NewReadOnlyFs(afero.NewOsFs()), + afero.NewMemMapFs(), + ) + + var outB strings.Builder + var errB strings.Builder + log := common.CreateLogger(&errB) + _, err := formatFile(fs, log, testcase.sourcefile, testcase.fmtcompat, testcase.fixFinishLines, true, nil, &outB, &errB) + actualStdErr := errB.String() + + if err != nil { + t.Fatalf("Case %q: Got an error when none was expected: %v", testcase.name, err) + } + + filenameColor := "lightMagenta" + if testcase.noDiff { + filenameColor = "magenta" + } + expectedSummaryLine := c.String(fmt.Sprintf( + "<%s>%s: %d lines & formatted %d/%d blocks!", + filenameColor, + testcase.sourcefile, + testcase.lineCount, + testcase.updatedBlockCount, + testcase.totalBlockCount, + )) + + trimmedStdErr := strings.TrimSpace(actualStdErr) + lines := strings.Split(trimmedStdErr, "\n") + summaryLine := lines[len(lines)-1] + if summaryLine != expectedSummaryLine { + t.Errorf("Case %q: Unexpected summary:\nexpected %s\ngot %s", testcase.name, expectedSummaryLine, summaryLine) + } + }) + } +} diff --git a/cli/testdata/fmt_compat.go b/cli/testdata/fmt_compat.go new file mode 100644 index 00000000..1e9e691d --- /dev/null +++ b/cli/testdata/fmt_compat.go @@ -0,0 +1,33 @@ +package test3 + +import ( + "fmt" +) + +func testNoFormattingErrors(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "no-errors" { + bucket = "tf-test-bucket-no-errors-%d" + + %s +} +`, randInt) +} + +func testNoErrorsOrFmtVerbs(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "absolutely-nothing" { + bucket = "tf-test-bucket-absolutely-nothing" +} +`, randInt) +} + +func testExtraSpace(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "extra-space" { + bucket = "tf-test-bucket-extra-space-%d" + + %s +} +`, randInt) + testReturnSprintfSimple() +} diff --git a/cli/testdata/fmt_compat_diff_fmtcompat.go.txt b/cli/testdata/fmt_compat_diff_fmtcompat.go.txt new file mode 100644 index 00000000..576460a4 --- /dev/null +++ b/cli/testdata/fmt_compat_diff_fmtcompat.go.txt @@ -0,0 +1,7 @@ +testdata/fmt_compat.go:26 + resource "aws_s3_bucket" "extra-space" { +- bucket = "tf-test-bucket-extra-space-%d" ++ bucket = "tf-test-bucket-extra-space-%d" + + %s + } diff --git a/cli/testdata/fmt_compat_diff_nofmtcompat.go.txt b/cli/testdata/fmt_compat_diff_nofmtcompat.go.txt new file mode 100644 index 00000000..e69de29b diff --git a/cli/testdata/fmt_compat_fmtcompat.go b/cli/testdata/fmt_compat_fmtcompat.go new file mode 100644 index 00000000..e4fc11a3 --- /dev/null +++ b/cli/testdata/fmt_compat_fmtcompat.go @@ -0,0 +1,33 @@ +package test3 + +import ( + "fmt" +) + +func testNoFormattingErrors(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "no-errors" { + bucket = "tf-test-bucket-no-errors-%d" + + %s +} +`, randInt) +} + +func testNoErrorsOrFmtVerbs(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "absolutely-nothing" { + bucket = "tf-test-bucket-absolutely-nothing" +} +`, randInt) +} + +func testExtraSpace(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "extra-space" { + bucket = "tf-test-bucket-extra-space-%d" + + %s +} +`, randInt) + testReturnSprintfSimple() +} diff --git a/cli/testdata/fmt_compat_upgrade012.go b/cli/testdata/fmt_compat_upgrade012.go new file mode 100644 index 00000000..5008da43 --- /dev/null +++ b/cli/testdata/fmt_compat_upgrade012.go @@ -0,0 +1,31 @@ +package test3 + +import ( + "fmt" +) + +func testNoFormattingErrors(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "no-errors" { + bucket = "tf-test-bucket-no-errors-%d" + %s +} +`, randInt) +} + +func testNoErrorsOrFmtVerbs(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "absolutely-nothing" { + bucket = "tf-test-bucket-absolutely-nothing" +} +`, randInt) +} + +func testExtraSpace(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "extra-space" { + bucket = "tf-test-bucket-extra-space-%d" + %s +} +`, randInt) + testReturnSprintfSimple() +} diff --git a/cli/testdata/has_diffs.go b/cli/testdata/has_diffs.go new file mode 100644 index 00000000..869ba726 --- /dev/null +++ b/cli/testdata/has_diffs.go @@ -0,0 +1,39 @@ +package test2 + +import ( + "fmt" +) + +func testExtraLines() string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "extra-lines" { + + bucket = "tf-test-bucket-extra-lines" +} +`) +} + +// This is included to verify blocks with diffs and no diffs in the same file +func testNoFormattingErrors(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "no-errors" { + bucket = "tf-test-bucket-no-errors-%d" +} +`, randInt) +} + +func testExtraSpace(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "extra-space" { + bucket = "tf-test-bucket-extra-space-%d" +} +`, randInt) + testReturnSprintfSimple() +} + +func testFinishLineWhiteSpace(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "end-line" { + bucket = "tf-test-bucket-end-line-%d" +} + `, randInt) +} diff --git a/cli/testdata/has_diffs.md b/cli/testdata/has_diffs.md new file mode 100644 index 00000000..f3164006 --- /dev/null +++ b/cli/testdata/has_diffs.md @@ -0,0 +1,27 @@ +# Has Diffs + +```hcl +resource "aws_s3_bucket" "extra-lines" { + + bucket = "tf-test-bucket-extra-lines" +} +``` + +```hcl +resource "aws_s3_bucket" "no-errors" { + bucket = "tf-test-bucket-no-errors" +} +``` + +```hcl +resource "aws_s3_bucket" "extra-space" { + bucket = "tf-test-bucket-extra-space" +} +``` + +```hcl +resource "aws_s3_bucket" "end-line" { + bucket = "tf-test-bucket-end-line" +} + +``` diff --git a/cli/testdata/has_diffs_diff.go.txt b/cli/testdata/has_diffs_diff.go.txt new file mode 100644 index 00000000..f55b3d13 --- /dev/null +++ b/cli/testdata/has_diffs_diff.go.txt @@ -0,0 +1,11 @@ +testdata/has_diffs.go:8 + resource "aws_s3_bucket" "extra-lines" { +- ++ + bucket = "tf-test-bucket-extra-lines" + } +testdata/has_diffs.go:26 + resource "aws_s3_bucket" "extra-space" { +- bucket = "tf-test-bucket-extra-space-%d" ++ bucket = "tf-test-bucket-extra-space-%d" + } diff --git a/cli/testdata/has_diffs_diff.md.txt b/cli/testdata/has_diffs_diff.md.txt new file mode 100644 index 00000000..7124a1ac --- /dev/null +++ b/cli/testdata/has_diffs_diff.md.txt @@ -0,0 +1,17 @@ +testdata/has_diffs.md:3 + resource "aws_s3_bucket" "extra-lines" { +- ++ + bucket = "tf-test-bucket-extra-lines" + } +testdata/has_diffs.md:16 + resource "aws_s3_bucket" "extra-space" { +- bucket = "tf-test-bucket-extra-space" ++ bucket = "tf-test-bucket-extra-space" + } +testdata/has_diffs.md:22 + resource "aws_s3_bucket" "end-line" { + bucket = "tf-test-bucket-end-line" + } +- ++ diff --git a/cli/testdata/has_diffs_fmt.go b/cli/testdata/has_diffs_fmt.go new file mode 100644 index 00000000..8598f64d --- /dev/null +++ b/cli/testdata/has_diffs_fmt.go @@ -0,0 +1,39 @@ +package test2 + +import ( + "fmt" +) + +func testExtraLines() string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "extra-lines" { + + bucket = "tf-test-bucket-extra-lines" +} +`) +} + +// This is included to verify blocks with diffs and no diffs in the same file +func testNoFormattingErrors(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "no-errors" { + bucket = "tf-test-bucket-no-errors-%d" +} +`, randInt) +} + +func testExtraSpace(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "extra-space" { + bucket = "tf-test-bucket-extra-space-%d" +} +`, randInt) + testReturnSprintfSimple() +} + +func testFinishLineWhiteSpace(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "end-line" { + bucket = "tf-test-bucket-end-line-%d" +} + `, randInt) +} diff --git a/cli/testdata/has_diffs_fmt.md b/cli/testdata/has_diffs_fmt.md new file mode 100644 index 00000000..e0fa2c6a --- /dev/null +++ b/cli/testdata/has_diffs_fmt.md @@ -0,0 +1,27 @@ +# Has Diffs + +```hcl +resource "aws_s3_bucket" "extra-lines" { + + bucket = "tf-test-bucket-extra-lines" +} +``` + +```hcl +resource "aws_s3_bucket" "no-errors" { + bucket = "tf-test-bucket-no-errors" +} +``` + +```hcl +resource "aws_s3_bucket" "extra-space" { + bucket = "tf-test-bucket-extra-space" +} +``` + +```hcl +resource "aws_s3_bucket" "end-line" { + bucket = "tf-test-bucket-end-line" +} + +``` diff --git a/cli/testdata/has_diffs_fmt_fix_finish.go b/cli/testdata/has_diffs_fmt_fix_finish.go new file mode 100644 index 00000000..da2d8dc3 --- /dev/null +++ b/cli/testdata/has_diffs_fmt_fix_finish.go @@ -0,0 +1,39 @@ +package test2 + +import ( + "fmt" +) + +func testExtraLines() string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "extra-lines" { + + bucket = "tf-test-bucket-extra-lines" +} +`) +} + +// This is included to verify blocks with diffs and no diffs in the same file +func testNoFormattingErrors(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "no-errors" { + bucket = "tf-test-bucket-no-errors-%d" +} +`, randInt) +} + +func testExtraSpace(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "extra-space" { + bucket = "tf-test-bucket-extra-space-%d" +} +`, randInt) + testReturnSprintfSimple() +} + +func testFinishLineWhiteSpace(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "end-line" { + bucket = "tf-test-bucket-end-line-%d" +} +`, randInt) +} diff --git a/cli/testdata/has_diffs_upgrade012.go b/cli/testdata/has_diffs_upgrade012.go new file mode 100644 index 00000000..3001791c --- /dev/null +++ b/cli/testdata/has_diffs_upgrade012.go @@ -0,0 +1,38 @@ +package test2 + +import ( + "fmt" +) + +func testExtraLines() string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "extra-lines" { + bucket = "tf-test-bucket-extra-lines" +} +`) +} + +// This is included to verify blocks with diffs and no diffs in the same file +func testNoFormattingErrors(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "no-errors" { + bucket = "tf-test-bucket-no-errors-%d" +} +`, randInt) +} + +func testExtraSpace(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "extra-space" { + bucket = "tf-test-bucket-extra-space-%d" +} +`, randInt) + testReturnSprintfSimple() +} + +func testFinishLineWhiteSpace(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "end-line" { + bucket = "tf-test-bucket-end-line-%d" +} + `, randInt) +} diff --git a/cli/testdata/has_diffs_upgrade012.md b/cli/testdata/has_diffs_upgrade012.md new file mode 100644 index 00000000..58c98fb8 --- /dev/null +++ b/cli/testdata/has_diffs_upgrade012.md @@ -0,0 +1,25 @@ +# Has Diffs + +```hcl +resource "aws_s3_bucket" "extra-lines" { + bucket = "tf-test-bucket-extra-lines" +} +``` + +```hcl +resource "aws_s3_bucket" "no-errors" { + bucket = "tf-test-bucket-no-errors" +} +``` + +```hcl +resource "aws_s3_bucket" "extra-space" { + bucket = "tf-test-bucket-extra-space" +} +``` + +```hcl +resource "aws_s3_bucket" "end-line" { + bucket = "tf-test-bucket-end-line" +} +``` diff --git a/cli/testdata/no_diffs.go b/cli/testdata/no_diffs.go new file mode 100644 index 00000000..b0512b04 --- /dev/null +++ b/cli/testdata/no_diffs.go @@ -0,0 +1,29 @@ +package test1 + +import ( + "fmt" +) + +func testReturnSprintfSimple() string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "simple" { + bucket = "tf-test-bucket-simple" +} +`) +} + +func testReturnSprintfWithParameters(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "with-parameters" { + bucket = "tf-test-bucket-with-parameters-%d" +} +`, randInt) +} + +func testReturnSprintfWithParametersAndStringAppend(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "with-parameters-and-append" { + bucket = "tf-test-bucket-parameters-and-append-%d" +} +`, randInt) + testReturnSprintfSimple() +} diff --git a/cli/testdata/no_diffs.md b/cli/testdata/no_diffs.md new file mode 100644 index 00000000..5354804a --- /dev/null +++ b/cli/testdata/no_diffs.md @@ -0,0 +1,25 @@ +# No Diffs + +```terraform +resource "aws_s3_bucket" "one" { + bucket = "tf-test-bucket-one" +} +``` + +```hcl +resource "aws_s3_bucket" "two" { + bucket = "tf-test-bucket-two" +} +``` + +```tf +resource "aws_s3_bucket" "three" { + bucket = "tf-test-bucket-three" +} +``` + +``` +resource "aws_s3_bucket" "four" { + bucket = "tf-test-bucket-four" +} +``` diff --git a/cli/upgrade012_test.go b/cli/upgrade012_test.go new file mode 100644 index 00000000..9a4b4858 --- /dev/null +++ b/cli/upgrade012_test.go @@ -0,0 +1,405 @@ +package cli + +import ( + "fmt" + "strings" + "testing" + + c "github.com/gookit/color" + "github.com/katbyte/terrafmt/lib/common" + "github.com/kylelemons/godebug/diff" + "github.com/spf13/afero" +) + +func TestCmdUpgrade012StdinDefault(t *testing.T) { + testcases := []struct { + name string + sourcefile string + resultfile string + noDiff bool + errMsg []string + fmtcompat bool + }{ + { + name: "Go no change", + sourcefile: "testdata/no_diffs.go", + noDiff: true, + }, + { + name: "Go formatting", + sourcefile: "testdata/has_diffs.go", + resultfile: "testdata/has_diffs_upgrade012.go", // This has stricter formatting than `fmt` + }, + { + name: "Go fmt verbs", + sourcefile: "testdata/fmt_compat.go", + noDiff: true, + fmtcompat: false, + errMsg: []string{ + "block 1 @ stdin:8 failed to process with: cmd.Run() failed in terraform init with exit status 1:", + "block 3 @ stdin:26 failed to process with: cmd.Run() failed in terraform init with exit status 1:", + }, + }, + { + name: "Go fmt verbs --fmtcompat", + sourcefile: "testdata/fmt_compat.go", + resultfile: "testdata/fmt_compat_upgrade012.go", + fmtcompat: true, + }, + { + name: "Markdown no change", + sourcefile: "testdata/no_diffs.md", + noDiff: true, + }, + { + name: "Markdown formatting", + sourcefile: "testdata/has_diffs.md", + resultfile: "testdata/has_diffs_upgrade012.md", // This has stricter formatting than `fmt` + }, + } + + t.Parallel() + + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + + fs := afero.NewReadOnlyFs(afero.NewOsFs()) + + inR, err := fs.Open(testcase.sourcefile) + if err != nil { + t.Fatalf("Error opening test input file %q: %s", testcase.sourcefile, err) + } + + resultfile := testcase.resultfile + if testcase.noDiff { + resultfile = testcase.sourcefile + } + data, err := afero.ReadFile(fs, resultfile) + if err != nil { + t.Fatalf("Error reading test result file %q: %s", resultfile, err) + } + expected := c.String(string(data)) + + var outB strings.Builder + var errB strings.Builder + log := common.CreateLogger(&errB) + _, err = upgrade012File(fs, log, "", testcase.fmtcompat, false, inR, &outB, &errB) + actualStdOut := outB.String() + actualStdErr := errB.String() + + if err != nil { + t.Fatalf("Got an error when none was expected: %v", err) + } + + if actualStdOut != expected { + t.Errorf("Output does not match expected:\n%s", diff.Diff(actualStdOut, expected)) + } + + checkExpectedErrors(t, actualStdErr, testcase.errMsg) + }) + } +} + +func TestCmdUpgrade012StdinVerbose(t *testing.T) { + testcases := []struct { + name string + sourcefile string + noDiff bool + lineCount int + updatedBlockCount int + totalBlockCount int + fmtcompat bool + }{ + { + name: "Go no change", + sourcefile: "testdata/no_diffs.go", + noDiff: true, + lineCount: 29, + totalBlockCount: 3, + }, + { + name: "Go formatting", + sourcefile: "testdata/has_diffs.go", + lineCount: 39, + updatedBlockCount: 2, + totalBlockCount: 4, + }, + { + name: "Go fmt verbs", + sourcefile: "testdata/fmt_compat.go", + noDiff: true, + lineCount: 33, + totalBlockCount: 3, + fmtcompat: false, + }, + { + name: "Go fmt verbs --fmtcompat", + sourcefile: "testdata/fmt_compat.go", + lineCount: 33, + updatedBlockCount: 2, + totalBlockCount: 3, + fmtcompat: true, + }, + { + name: "Markdown no change", + sourcefile: "testdata/no_diffs.md", + noDiff: true, + lineCount: 25, + totalBlockCount: 3, + }, + { + name: "Markdown formatting", + sourcefile: "testdata/has_diffs.md", + lineCount: 27, + updatedBlockCount: 3, + totalBlockCount: 4, + }, + } + + t.Parallel() + + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + + fs := afero.NewReadOnlyFs(afero.NewOsFs()) + + inR, err := fs.Open(testcase.sourcefile) + if err != nil { + t.Fatalf("Error opening test input file %q: %s", testcase.sourcefile, err) + } + + var outB strings.Builder + var errB strings.Builder + log := common.CreateLogger(&errB) + _, err = upgrade012File(fs, log, "", testcase.fmtcompat, true, inR, &outB, &errB) + actualStdErr := errB.String() + + if err != nil { + t.Fatalf("Got an error when none was expected: %v", err) + } + + filenameColor := "lightMagenta" + if testcase.noDiff { + filenameColor = "magenta" + } + expectedSummaryLine := c.String(fmt.Sprintf( + "<%s>%s: %d lines & formatted %d/%d blocks!", + filenameColor, + "stdin", + testcase.lineCount, + testcase.updatedBlockCount, + testcase.totalBlockCount, + )) + + trimmedStdErr := strings.TrimSpace(actualStdErr) + lines := strings.Split(trimmedStdErr, "\n") + summaryLine := lines[len(lines)-1] + if summaryLine != expectedSummaryLine { + t.Errorf("Unexpected summary:\nexpected %s\ngot %s", expectedSummaryLine, summaryLine) + } + }) + } +} + +func TestCmdUpgrade012File(t *testing.T) { + testcases := []struct { + name string + sourcefile string + resultfile string + noDiff bool + errMsg []string + fmtcompat bool + }{ + { + name: "Go no change", + sourcefile: "testdata/no_diffs.go", + noDiff: true, + }, + { + name: "Go formatting", + sourcefile: "testdata/has_diffs.go", + resultfile: "testdata/has_diffs_upgrade012.go", // This has stricter formatting than `fmt` + }, + { + name: "Go fmt verbs", + sourcefile: "testdata/fmt_compat.go", + noDiff: true, + fmtcompat: false, + errMsg: []string{ + "block 1 @ testdata/fmt_compat.go:8 failed to process with: cmd.Run() failed in terraform init with exit status 1:", + "block 3 @ testdata/fmt_compat.go:26 failed to process with: cmd.Run() failed in terraform init with exit status 1:", + }, + }, + { + name: "Go fmt verbs --fmtcompat", + sourcefile: "testdata/fmt_compat.go", + resultfile: "testdata/fmt_compat_upgrade012.go", + fmtcompat: true, + }, + { + name: "Markdown no change", + sourcefile: "testdata/no_diffs.md", + noDiff: true, + }, + { + name: "Markdown formatting", + sourcefile: "testdata/has_diffs.md", + resultfile: "testdata/has_diffs_upgrade012.md", // This has stricter formatting than `fmt` + }, + } + + t.Parallel() + + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + + fs := afero.NewCopyOnWriteFs( + afero.NewReadOnlyFs(afero.NewOsFs()), + afero.NewMemMapFs(), + ) + + resultfile := testcase.resultfile + if testcase.noDiff { + resultfile = testcase.sourcefile + } + data, err := afero.ReadFile(fs, resultfile) + if err != nil { + t.Fatalf("Error reading test result file %q: %s", resultfile, err) + } + expected := c.String(string(data)) + + var outB strings.Builder + var errB strings.Builder + log := common.CreateLogger(&errB) + _, err = upgrade012File(fs, log, testcase.sourcefile, testcase.fmtcompat, false, nil, &outB, &errB) + actualStdOut := outB.String() + actualStdErr := errB.String() + + if err != nil { + t.Fatalf("Got an error when none was expected: %v", err) + } + + if actualStdOut != "" { + t.Errorf("Stdout should not have output, got:\n%s", actualStdOut) + } + + data, err = afero.ReadFile(fs, testcase.sourcefile) + if err != nil { + t.Fatalf("Error reading results from file %q: %s", resultfile, err) + } + actualContent := c.String(string(data)) + if actualContent != expected { + t.Errorf("File does not match expected:\n%s", diff.Diff(actualContent, expected)) + } + + checkExpectedErrors(t, actualStdErr, testcase.errMsg) + }) + } +} + +func TestCmdUpgrade012FileVerbose(t *testing.T) { + testcases := []struct { + name string + sourcefile string + noDiff bool + lineCount int + updatedBlockCount int + totalBlockCount int + fmtcompat bool + }{ + { + name: "Go no change", + sourcefile: "testdata/no_diffs.go", + noDiff: true, + lineCount: 29, + totalBlockCount: 3, + }, + { + name: "Go formatting", + sourcefile: "testdata/has_diffs.go", + lineCount: 39, + updatedBlockCount: 2, + totalBlockCount: 4, + }, + { + name: "Go fmt verbs", + sourcefile: "testdata/fmt_compat.go", + noDiff: true, + lineCount: 33, + totalBlockCount: 3, + fmtcompat: false, + }, + { + name: "Go fmt verbs --fmtcompat", + sourcefile: "testdata/fmt_compat.go", + lineCount: 33, + updatedBlockCount: 2, + totalBlockCount: 3, + fmtcompat: true, + }, + { + name: "Markdown no change", + sourcefile: "testdata/no_diffs.md", + noDiff: true, + lineCount: 25, + totalBlockCount: 3, + }, + { + name: "Markdown formatting", + sourcefile: "testdata/has_diffs.md", + lineCount: 27, + updatedBlockCount: 3, + totalBlockCount: 4, + }, + } + + t.Parallel() + + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + + fs := afero.NewCopyOnWriteFs( + afero.NewReadOnlyFs(afero.NewOsFs()), + afero.NewMemMapFs(), + ) + + var outB strings.Builder + var errB strings.Builder + log := common.CreateLogger(&errB) + _, err := upgrade012File(fs, log, testcase.sourcefile, testcase.fmtcompat, true, nil, &outB, &errB) + actualStdErr := errB.String() + + if err != nil { + t.Fatalf("Got an error when none was expected: %v", err) + } + + filenameColor := "lightMagenta" + if testcase.noDiff { + filenameColor = "magenta" + } + expectedSummaryLine := c.String(fmt.Sprintf( + "<%s>%s: %d lines & formatted %d/%d blocks!", + filenameColor, + testcase.sourcefile, + testcase.lineCount, + testcase.updatedBlockCount, + testcase.totalBlockCount, + )) + + trimmedStdErr := strings.TrimSpace(actualStdErr) + lines := strings.Split(trimmedStdErr, "\n") + summaryLine := lines[len(lines)-1] + if summaryLine != expectedSummaryLine { + t.Errorf("Unexpected summary:\nexpected %s\ngot %s", expectedSummaryLine, summaryLine) + } + }) + } +} diff --git a/go.mod b/go.mod index 7df5bc8a..118127ad 100644 --- a/go.mod +++ b/go.mod @@ -10,12 +10,13 @@ require ( github.com/gookit/color v1.2.6 github.com/hashicorp/go-multierror v1.1.0 github.com/hashicorp/hcl/v2 v2.6.0 + github.com/kylelemons/godebug v1.1.0 github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/pelletier/go-toml v1.8.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect github.com/sirupsen/logrus v1.6.0 - github.com/spf13/afero v1.3.2 // indirect + github.com/spf13/afero v1.3.2 github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cobra v1.0.0 github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -25,4 +26,5 @@ require ( golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666 // indirect golang.org/x/text v0.3.3 // indirect gopkg.in/ini.v1 v1.57.0 // indirect + gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index a388a9b4..a25521b0 100644 --- a/go.sum +++ b/go.sum @@ -15,7 +15,6 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= @@ -52,7 +51,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -79,7 +77,6 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -105,7 +102,6 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= @@ -135,7 +131,6 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -148,7 +143,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= -github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -160,14 +156,12 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -176,7 +170,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= @@ -200,7 +193,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -214,22 +206,18 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.3.2 h1:GDarE4TJQI52kYSbSAmLiId1Elfj+xgSDqrUZxFhxlU= github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -238,9 +226,7 @@ github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -251,7 +237,6 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/zclconf/go-cty v1.2.0 h1:sPHsy7ADcIZQP3vILvTjrh74ZA175TFP5vqiNK1UmlI= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.5.1 h1:oALUZX+aJeEBUe2a1+uD2+UTaYfEjnKFDEMRydkGvWE= github.com/zclconf/go-cty v1.5.1/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ= @@ -318,11 +303,9 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 h1:vsphBvatvfbhlb4PO1BYSr9dzugGxJ/SQHoNufZJq1w= golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -330,10 +313,8 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666 h1:gVCS+QOncANNPlmlO1AhlU3oxs4V9z+gTtPwIk3p2N8= golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -382,21 +363,17 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= diff --git a/lib/blocks/blockreader.go b/lib/blocks/blockreader.go index db6e02fb..54b7cf74 100644 --- a/lib/blocks/blockreader.go +++ b/lib/blocks/blockreader.go @@ -6,12 +6,11 @@ import ( "fmt" "io" "io/ioutil" - "os" "regexp" "strings" - "github.com/katbyte/terrafmt/lib/common" "github.com/sirupsen/logrus" + "github.com/spf13/afero" ) var ( @@ -22,6 +21,8 @@ var ( type Reader struct { FileName string + Log *logrus.Logger + //io Reader io.Reader Writer io.Writer @@ -76,18 +77,18 @@ func IsFinishLine(line string) bool { return false } -func (br *Reader) DoTheThing(filename string) error { +func (br *Reader) DoTheThing(fs afero.Fs, filename string, stdin io.Reader, stdout io.Writer) error { var buf *bytes.Buffer if filename != "" { br.FileName = filename - common.Log.Debugf("opening src file %s", filename) - fs, err := os.Open(filename) // For read access. + br.Log.Debugf("opening src file %s", filename) + file, err := fs.Open(filename) // For read access. if err != nil { return err } - defer fs.Close() - br.Reader = fs + defer file.Close() + br.Reader = file // for now write to buffer if !br.ReadOnly { @@ -98,8 +99,8 @@ func (br *Reader) DoTheThing(filename string) error { } } else { br.FileName = "stdin" - br.Reader = os.Stdin - br.Writer = os.Stdout + br.Reader = stdin + br.Writer = stdout if br.ReadOnly { br.Writer = ioutil.Discard @@ -131,7 +132,7 @@ func (br *Reader) DoTheThing(filename string) error { // make sure we don't run into another block if IsStartLine(l2) { // the end of current block must be malformed, so lets pass it through and log an error - logrus.Errorf("block %d @ %s:%d failed to find end of block", br.BlockCount, br.FileName, br.LineCount-br.BlockCurrentLine) + br.Log.Errorf("block %d @ %s:%d failed to find end of block", br.BlockCount, br.FileName, br.LineCount-br.BlockCurrentLine) if err := ReaderPassthrough(br, br.LineCount, block); err != nil { // is this ok or should we loop with LineRead? return err } @@ -156,7 +157,7 @@ func (br *Reader) DoTheThing(filename string) error { if err := br.BlockRead(br, br.LineCount, block); err != nil { //for now ignore block errors and output unformatted br.ErrorBlocks += 1 - logrus.Errorf("block %d @ %s:%d failed to process with: %v", br.BlockCount, br.FileName, br.LineCount-br.BlockCurrentLine, err) + br.Log.Errorf("block %d @ %s:%d failed to process with: %v", br.BlockCount, br.FileName, br.LineCount-br.BlockCurrentLine, err) if err := ReaderPassthrough(br, br.LineCount, block); err != nil { return err } @@ -176,7 +177,7 @@ func (br *Reader) DoTheThing(filename string) error { // ensure last block in the file was property handled if block != "" { //for each line { Lineread()? - logrus.Errorf("block %d @ %s:%d failed to find end of block", br.BlockCount, br.FileName, br.LineCount-br.BlockCurrentLine) + br.Log.Errorf("block %d @ %s:%d failed to find end of block", br.BlockCount, br.FileName, br.LineCount-br.BlockCurrentLine) if err := ReaderPassthrough(br, br.LineCount, block); err != nil { // is this ok or should we loop with LineRead? return err } @@ -185,14 +186,14 @@ func (br *Reader) DoTheThing(filename string) error { } // If not read-only, need to write back to file. - if !br.ReadOnly { - destination, err := os.Create(filename) + if !br.ReadOnly && filename != "" { + destination, err := fs.Create(filename) if err != nil { return err } defer destination.Close() - common.Log.Debugf("copying..") + br.Log.Debugf("copying..") _, err = io.Copy(destination, buf) return err } diff --git a/lib/blocks/blockreader_test.go b/lib/blocks/blockreader_test.go index 59e31fb6..41f86867 100644 --- a/lib/blocks/blockreader_test.go +++ b/lib/blocks/blockreader_test.go @@ -1,6 +1,15 @@ package blocks -import "testing" +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/katbyte/terrafmt/lib/common" + "github.com/kylelemons/godebug/diff" + "github.com/spf13/afero" + "gopkg.in/yaml.v2" +) func TestBlockReaderIsFinishLine(t *testing.T) { tests := []struct { @@ -38,3 +47,74 @@ func TestBlockReaderIsFinishLine(t *testing.T) { }) } } + +type results struct { + ExpectedResults []string `yaml:"expected_results"` +} + +func TestBlockDetection(t *testing.T) { + testcases := []struct { + sourcefile string + resultfile string + }{ + { + sourcefile: "testdata/test1.go", + resultfile: "testdata/test1_results.yaml", + }, + { + sourcefile: "testdata/test2.markdown", + resultfile: "testdata/test2_results.yaml", + }, + } + + fs := afero.NewReadOnlyFs(afero.NewOsFs()) + + errB := bytes.NewBufferString("") + log := common.CreateLogger(errB) + + for _, testcase := range testcases { + data, err := ioutil.ReadFile(testcase.resultfile) + if err != nil { + t.Fatalf("Error reading test result file %q: %s", testcase.resultfile, err) + } + var expectedResults results + err = yaml.Unmarshal(data, &expectedResults) + if err != nil { + t.Fatalf("Error parsing test result file %q: %s", testcase.resultfile, err) + } + + var actualBlocks []string + br := Reader{ + Log: log, + ReadOnly: true, + LineRead: ReaderIgnore, + BlockRead: func(br *Reader, i int, b string) error { + actualBlocks = append(actualBlocks, b) + return nil + }, + } + err = br.DoTheThing(fs, testcase.sourcefile, nil, nil) + if err != nil { + t.Errorf("Case %q: Got an error when none was expected: %v", testcase.sourcefile, err) + continue + } + + if len(expectedResults.ExpectedResults) != len(actualBlocks) { + t.Errorf("Case %q: expected %d blocks, got %d", testcase.sourcefile, len(expectedResults.ExpectedResults), len(actualBlocks)) + continue + } + + for i, actual := range actualBlocks { + expected := expectedResults.ExpectedResults[i] + if actual != expected { + t.Errorf("Case %q, block %d:\n%s", testcase.sourcefile, i+1, diff.Diff(expected, actual)) + continue + } + } + + actualErr := errB.String() + if actualErr != "" { + t.Errorf("Case %q: Got error output:\n%s", testcase.sourcefile, actualErr) + } + } +} diff --git a/lib/blocks/testdata/test1.go b/lib/blocks/testdata/test1.go new file mode 100644 index 00000000..b0512b04 --- /dev/null +++ b/lib/blocks/testdata/test1.go @@ -0,0 +1,29 @@ +package test1 + +import ( + "fmt" +) + +func testReturnSprintfSimple() string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "simple" { + bucket = "tf-test-bucket-simple" +} +`) +} + +func testReturnSprintfWithParameters(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "with-parameters" { + bucket = "tf-test-bucket-with-parameters-%d" +} +`, randInt) +} + +func testReturnSprintfWithParametersAndStringAppend(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "with-parameters-and-append" { + bucket = "tf-test-bucket-parameters-and-append-%d" +} +`, randInt) + testReturnSprintfSimple() +} diff --git a/lib/blocks/testdata/test1_results.yaml b/lib/blocks/testdata/test1_results.yaml new file mode 100644 index 00000000..4c38f2e6 --- /dev/null +++ b/lib/blocks/testdata/test1_results.yaml @@ -0,0 +1,13 @@ +expected_results: + - | + resource "aws_s3_bucket" "simple" { + bucket = "tf-test-bucket-simple" + } + - | + resource "aws_s3_bucket" "with-parameters" { + bucket = "tf-test-bucket-with-parameters-%d" + } + - | + resource "aws_s3_bucket" "with-parameters-and-append" { + bucket = "tf-test-bucket-parameters-and-append-%d" + } diff --git a/lib/blocks/testdata/test2.markdown b/lib/blocks/testdata/test2.markdown new file mode 100644 index 00000000..447c99a6 --- /dev/null +++ b/lib/blocks/testdata/test2.markdown @@ -0,0 +1,17 @@ +# Test 2 + +Test fenced code block with `hcl` + +```hcl +resource "aws_s3_bucket" "hcl" { + bucket = "tf-test-bucket-hcl" +} +``` + +Test fenced code block with `tf` + +```tf +resource "aws_s3_bucket" "tf" { + bucket = "tf-test-bucket-tf" +} +``` diff --git a/lib/blocks/testdata/test2_results.yaml b/lib/blocks/testdata/test2_results.yaml new file mode 100644 index 00000000..58293999 --- /dev/null +++ b/lib/blocks/testdata/test2_results.yaml @@ -0,0 +1,9 @@ +expected_results: + - | + resource "aws_s3_bucket" "hcl" { + bucket = "tf-test-bucket-hcl" + } + - | + resource "aws_s3_bucket" "tf" { + bucket = "tf-test-bucket-tf" + } diff --git a/lib/common/log.go b/lib/common/log.go index dbc53450..cbb510ba 100644 --- a/lib/common/log.go +++ b/lib/common/log.go @@ -1,17 +1,16 @@ package common import ( + "io" "os" "github.com/sirupsen/logrus" ) -var Log = createLogger() - -func createLogger() *logrus.Logger { +func CreateLogger(w io.Writer) *logrus.Logger { l := logrus.New() - l.SetOutput(os.Stderr) + l.SetOutput(w) customFormatter := new(logrus.TextFormatter) customFormatter.TimestampFormat = "2006-01-02 15:04:05" diff --git a/lib/format/fmtverbs.go b/lib/format/fmtverbs.go index d90eab69..8429fe3f 100644 --- a/lib/format/fmtverbs.go +++ b/lib/format/fmtverbs.go @@ -2,12 +2,13 @@ package format import ( "github.com/katbyte/terrafmt/lib/fmtverbs" + "github.com/sirupsen/logrus" ) -func FmtVerbBlock(content, path string) (string, error) { +func FmtVerbBlock(log *logrus.Logger, content, path string) (string, error) { content = fmtverbs.Escape(content) - fb, err := Block(content, path) + fb, err := Block(log, content, path) if err != nil { return fb, err } diff --git a/lib/format/fmtverbs_test.go b/lib/format/fmtverbs_test.go index 36e424dc..5615cf41 100644 --- a/lib/format/fmtverbs_test.go +++ b/lib/format/fmtverbs_test.go @@ -1,6 +1,11 @@ package format -import "testing" +import ( + "strings" + "testing" + + "github.com/katbyte/terrafmt/lib/common" +) func TestFmtVerbBlock(t *testing.T) { tests := []struct { @@ -184,7 +189,9 @@ resource "resource" "test" { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - result, err := FmtVerbBlock(test.block, "test") + var errB strings.Builder + log := common.CreateLogger(&errB) + result, err := FmtVerbBlock(log, test.block, "test") if err != nil && !test.error { t.Fatalf("Got an error when none was expected: %v", err) } diff --git a/lib/format/format.go b/lib/format/format.go index 6f15b399..6d50e4ec 100644 --- a/lib/format/format.go +++ b/lib/format/format.go @@ -7,12 +7,12 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/hcl/v2/hclwrite" - "github.com/katbyte/terrafmt/lib/common" + "github.com/sirupsen/logrus" ) -func Block(content, path string) (string, error) { +func Block(log *logrus.Logger, content, path string) (string, error) { b := []byte(content) - common.Log.Debugf("format terraform config... ") + log.Debugf("format terraform config... ") _, syntaxDiags := hclsyntax.ParseConfig(b, path, hcl.Pos{Line: 1, Column: 1}) if syntaxDiags.HasErrors() { return "", fmt.Errorf("failed to parse hcl: %w", errors.New(syntaxDiags.Error())) diff --git a/lib/format/format_test.go b/lib/format/format_test.go index 962cacaf..a89a124f 100644 --- a/lib/format/format_test.go +++ b/lib/format/format_test.go @@ -1,6 +1,11 @@ package format -import "testing" +import ( + "strings" + "testing" + + "github.com/katbyte/terrafmt/lib/common" +) func TestBlock(t *testing.T) { tests := []struct { @@ -84,7 +89,9 @@ resource "resource" "test" { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - result, err := Block(test.block, "test") + var errB strings.Builder + log := common.CreateLogger(&errB) + result, err := Block(log, test.block, "test") if err != nil && !test.error { t.Fatalf("Got an error when none was expected: %v", err) } diff --git a/lib/upgrade012/fmtverbs.go b/lib/upgrade012/fmtverbs.go index 27ba05a0..cf3e0f04 100644 --- a/lib/upgrade012/fmtverbs.go +++ b/lib/upgrade012/fmtverbs.go @@ -2,12 +2,13 @@ package upgrade012 import ( "github.com/katbyte/terrafmt/lib/fmtverbs" + "github.com/sirupsen/logrus" ) -func Upgrade12VerbBlock(b string) (string, error) { +func Upgrade12VerbBlock(log *logrus.Logger, b string) (string, error) { b = fmtverbs.Escape(b) - fb, err := Block(b) + fb, err := Block(log, b) if err != nil { return fb, err } diff --git a/lib/upgrade012/fmtverbs_test.go b/lib/upgrade012/fmtverbs_test.go index 732bbf7e..2af8d108 100644 --- a/lib/upgrade012/fmtverbs_test.go +++ b/lib/upgrade012/fmtverbs_test.go @@ -1,6 +1,11 @@ package upgrade012 -import "testing" +import ( + "strings" + "testing" + + "github.com/katbyte/terrafmt/lib/common" +) func TestFmtVerbBlock(t *testing.T) { tests := []struct { @@ -182,7 +187,9 @@ data "google_dns_managed_zone" "qa" { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - result, err := Upgrade12VerbBlock(test.block) + var errB strings.Builder + log := common.CreateLogger(&errB) + result, err := Upgrade12VerbBlock(log, test.block) if err != nil && !test.error { t.Fatalf("Got an error when none was expected: %v", err) } diff --git a/lib/upgrade012/upgrade.go b/lib/upgrade012/upgrade.go index 532e7688..fc14f178 100644 --- a/lib/upgrade012/upgrade.go +++ b/lib/upgrade012/upgrade.go @@ -4,34 +4,31 @@ import ( "bytes" "fmt" "io/ioutil" - "log" "os" "os/exec" "strings" - "github.com/katbyte/terrafmt/lib/common" + "github.com/sirupsen/logrus" ) -func Block(b string) (string, error) { +func Block(log *logrus.Logger, b string) (string, error) { stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) // Make temp directory - dir, err := ioutil.TempDir(".", "tmp-module") + tempDir, err := ioutil.TempDir(".", "tmp-module") if err != nil { log.Fatal(err) } - defer os.RemoveAll(dir) // clean up + defer os.RemoveAll(tempDir) // clean up // Create temp file - tmpFile, err := ioutil.TempFile(dir, "*.tf") + tmpFile, err := ioutil.TempFile(tempDir, "*.tf") if err != nil { return "", err } - defer os.Remove(tmpFile.Name()) // clean up - // Write from Reader to File if _, err := tmpFile.Write(bytes.NewBufferString(b).Bytes()); err != nil { tmpFile.Close() @@ -42,17 +39,23 @@ func Block(b string) (string, error) { log.Fatal(err) } - cmd := exec.Command("terraform", "init", dir) + cmd := exec.Command("terraform", "init") + cmd.Dir = tempDir + cmd.Env = append(os.Environ(), + "TF_IN_AUTOMATION=1", + ) cmd.Stderr = stderr err = cmd.Run() if err != nil { return "", fmt.Errorf("cmd.Run() failed in terraform init with %s: %s", err, stderr) } - defer os.RemoveAll(".terraform") // clean up - - common.Log.Debugf("running terraform... ") - cmd = exec.Command("terraform", "0.12upgrade", "-yes", dir) + log.Debugf("running terraform... ") + cmd = exec.Command("terraform", "0.12upgrade", "-yes") + cmd.Dir = tempDir + cmd.Env = append(os.Environ(), + "TF_IN_AUTOMATION=1", + ) cmd.Stdout = stdout cmd.Stderr = stderr err = cmd.Run() @@ -67,7 +70,7 @@ func Block(b string) (string, error) { } ec := cmd.ProcessState.ExitCode() - common.Log.Debugf("terraform exited with %d", ec) + log.Debugf("terraform exited with %d", ec) if ec != 0 { return "", fmt.Errorf("terraform failed with %d: %s", ec, stderr) } diff --git a/lib/upgrade012/upgrade_test.go b/lib/upgrade012/upgrade_test.go index a43cb693..629eecf8 100644 --- a/lib/upgrade012/upgrade_test.go +++ b/lib/upgrade012/upgrade_test.go @@ -1,6 +1,11 @@ package upgrade012 -import "testing" +import ( + "strings" + "testing" + + "github.com/katbyte/terrafmt/lib/common" +) func TestBlock(t *testing.T) { tests := []struct { @@ -145,7 +150,9 @@ Hi there i am going to fail... =C } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - result, err := Block(test.block) + var errB strings.Builder + log := common.CreateLogger(&errB) + result, err := Block(log, test.block) if err != nil && !test.error { t.Fatalf("Got an error when none was expected: %v", err) } diff --git a/main.go b/main.go index 437c158a..bf4cb979 100644 --- a/main.go +++ b/main.go @@ -1,16 +1,16 @@ package main import ( + "fmt" "os" c "github.com/gookit/color" "github.com/katbyte/terrafmt/cli" - "github.com/katbyte/terrafmt/lib/common" ) func main() { if err := cli.Make().Execute(); err != nil { - common.Log.Errorf(c.Sprintf("terrafmt: %v", err)) + fmt.Fprint(os.Stderr, c.Sprintf("terrafmt: %v\n", err)) os.Exit(1) } diff --git a/makefile b/makefile index 48b971bc..5c6479f5 100644 --- a/makefile +++ b/makefile @@ -1,5 +1,6 @@ GIT_COMMIT=$(shell git describe --always --long --dirty) -GOLANGCI_LINT_VERSION=v1.29.0 +GOLANGCI_LINT_VERSION?=v1.29.0 +TEST_TIMEOUT?=15m default: fmt build @@ -19,7 +20,7 @@ imports: goimports -w . test: build - go test ./... + go test ./... -timeout ${TEST_TIMEOUT} build: @echo "==> building..." diff --git a/vendor/github.com/kylelemons/godebug/LICENSE b/vendor/github.com/kylelemons/godebug/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/kylelemons/godebug/diff/diff.go b/vendor/github.com/kylelemons/godebug/diff/diff.go new file mode 100644 index 00000000..200e596c --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/diff/diff.go @@ -0,0 +1,186 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package diff implements a linewise diff algorithm. +package diff + +import ( + "bytes" + "fmt" + "strings" +) + +// Chunk represents a piece of the diff. A chunk will not have both added and +// deleted lines. Equal lines are always after any added or deleted lines. +// A Chunk may or may not have any lines in it, especially for the first or last +// chunk in a computation. +type Chunk struct { + Added []string + Deleted []string + Equal []string +} + +func (c *Chunk) empty() bool { + return len(c.Added) == 0 && len(c.Deleted) == 0 && len(c.Equal) == 0 +} + +// Diff returns a string containing a line-by-line unified diff of the linewise +// changes required to make A into B. Each line is prefixed with '+', '-', or +// ' ' to indicate if it should be added, removed, or is correct respectively. +func Diff(A, B string) string { + aLines := strings.Split(A, "\n") + bLines := strings.Split(B, "\n") + + chunks := DiffChunks(aLines, bLines) + + buf := new(bytes.Buffer) + for _, c := range chunks { + for _, line := range c.Added { + fmt.Fprintf(buf, "+%s\n", line) + } + for _, line := range c.Deleted { + fmt.Fprintf(buf, "-%s\n", line) + } + for _, line := range c.Equal { + fmt.Fprintf(buf, " %s\n", line) + } + } + return strings.TrimRight(buf.String(), "\n") +} + +// DiffChunks uses an O(D(N+M)) shortest-edit-script algorithm +// to compute the edits required from A to B and returns the +// edit chunks. +func DiffChunks(a, b []string) []Chunk { + // algorithm: http://www.xmailserver.org/diff2.pdf + + // We'll need these quantities a lot. + alen, blen := len(a), len(b) // M, N + + // At most, it will require len(a) deletions and len(b) additions + // to transform a into b. + maxPath := alen + blen // MAX + if maxPath == 0 { + // degenerate case: two empty lists are the same + return nil + } + + // Store the endpoint of the path for diagonals. + // We store only the a index, because the b index on any diagonal + // (which we know during the loop below) is aidx-diag. + // endpoint[maxPath] represents the 0 diagonal. + // + // Stated differently: + // endpoint[d] contains the aidx of a furthest reaching path in diagonal d + endpoint := make([]int, 2*maxPath+1) // V + + saved := make([][]int, 0, 8) // Vs + save := func() { + dup := make([]int, len(endpoint)) + copy(dup, endpoint) + saved = append(saved, dup) + } + + var editDistance int // D +dLoop: + for editDistance = 0; editDistance <= maxPath; editDistance++ { + // The 0 diag(onal) represents equality of a and b. Each diagonal to + // the left is numbered one lower, to the right is one higher, from + // -alen to +blen. Negative diagonals favor differences from a, + // positive diagonals favor differences from b. The edit distance to a + // diagonal d cannot be shorter than d itself. + // + // The iterations of this loop cover either odds or evens, but not both, + // If odd indices are inputs, even indices are outputs and vice versa. + for diag := -editDistance; diag <= editDistance; diag += 2 { // k + var aidx int // x + switch { + case diag == -editDistance: + // This is a new diagonal; copy from previous iter + aidx = endpoint[maxPath-editDistance+1] + 0 + case diag == editDistance: + // This is a new diagonal; copy from previous iter + aidx = endpoint[maxPath+editDistance-1] + 1 + case endpoint[maxPath+diag+1] > endpoint[maxPath+diag-1]: + // diagonal d+1 was farther along, so use that + aidx = endpoint[maxPath+diag+1] + 0 + default: + // diagonal d-1 was farther (or the same), so use that + aidx = endpoint[maxPath+diag-1] + 1 + } + // On diagonal d, we can compute bidx from aidx. + bidx := aidx - diag // y + // See how far we can go on this diagonal before we find a difference. + for aidx < alen && bidx < blen && a[aidx] == b[bidx] { + aidx++ + bidx++ + } + // Store the end of the current edit chain. + endpoint[maxPath+diag] = aidx + // If we've found the end of both inputs, we're done! + if aidx >= alen && bidx >= blen { + save() // save the final path + break dLoop + } + } + save() // save the current path + } + if editDistance == 0 { + return nil + } + chunks := make([]Chunk, editDistance+1) + + x, y := alen, blen + for d := editDistance; d > 0; d-- { + endpoint := saved[d] + diag := x - y + insert := diag == -d || (diag != d && endpoint[maxPath+diag-1] < endpoint[maxPath+diag+1]) + + x1 := endpoint[maxPath+diag] + var x0, xM, kk int + if insert { + kk = diag + 1 + x0 = endpoint[maxPath+kk] + xM = x0 + } else { + kk = diag - 1 + x0 = endpoint[maxPath+kk] + xM = x0 + 1 + } + y0 := x0 - kk + + var c Chunk + if insert { + c.Added = b[y0:][:1] + } else { + c.Deleted = a[x0:][:1] + } + if xM < x1 { + c.Equal = a[xM:][:x1-xM] + } + + x, y = x0, y0 + chunks[d] = c + } + if x > 0 { + chunks[0].Equal = a[:x] + } + if chunks[0].empty() { + chunks = chunks[1:] + } + if len(chunks) == 0 { + return nil + } + return chunks +} diff --git a/vendor/modules.txt b/vendor/modules.txt index faa0f8d1..78fa9057 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -45,6 +45,9 @@ github.com/hashicorp/hcl/v2/hclwrite github.com/inconshreveable/mousetrap # github.com/konsorten/go-windows-terminal-sequences v1.0.3 github.com/konsorten/go-windows-terminal-sequences +# github.com/kylelemons/godebug v1.1.0 +## explicit +github.com/kylelemons/godebug/diff # github.com/magiconair/properties v1.8.1 github.com/magiconair/properties # github.com/mitchellh/go-wordwrap v1.0.0 @@ -104,4 +107,5 @@ golang.org/x/text/unicode/norm ## explicit gopkg.in/ini.v1 # gopkg.in/yaml.v2 v2.3.0 +## explicit gopkg.in/yaml.v2