diff --git a/cli/cmds.go b/cli/cmds.go index b075c1f3..ffc0299b 100644 --- a/cli/cmds.go +++ b/cli/cmds.go @@ -11,6 +11,7 @@ import ( "os" "path/filepath" "strings" + "unicode" "github.com/andreyvit/diff" c "github.com/gookit/color" @@ -397,7 +398,7 @@ func findBlocksInFile(fs afero.Fs, log *logrus.Logger, filename string, verbose, ReadOnly: true, LineRead: blocks.ReaderIgnore, BlockWriter: blockWriter, - BlockRead: func(br *blocks.Reader, i int, b string) error { + BlockRead: func(br *blocks.Reader, i int, b string, r bool) error { if fmtverbs { b = verbs.Escape(b) } @@ -428,7 +429,7 @@ func diffFile(fs afero.Fs, log *logrus.Logger, filename string, fmtverbs, verbos Log: log, ReadOnly: true, LineRead: blocks.ReaderPassthrough, - BlockRead: func(br *blocks.Reader, i int, b string) error { + BlockRead: func(br *blocks.Reader, i int, b string, r bool) error { var fb string var err error if fmtverbs { @@ -440,6 +441,10 @@ func diffFile(fs afero.Fs, log *logrus.Logger, filename string, fmtverbs, verbos return err } + if r { + fb = reindent(fb, b) + } + if fb == b { return nil } @@ -495,7 +500,7 @@ func formatFile(fs afero.Fs, log *logrus.Logger, filename string, fmtverbs, fixF br := blocks.Reader{ Log: log, LineRead: blocks.ReaderPassthrough, - BlockRead: func(br *blocks.Reader, i int, b string) error { + BlockRead: func(br *blocks.Reader, i int, b string, r bool) error { var fb string var err error if fmtverbs { @@ -507,6 +512,10 @@ func formatFile(fs afero.Fs, log *logrus.Logger, filename string, fmtverbs, fixF return err } + if r { + fb = reindent(fb, b) + } + hasChange := fb != b if br.CurrentNodeCursor != nil { @@ -569,7 +578,7 @@ func upgrade012File(fs afero.Fs, log *logrus.Logger, filename string, fmtverbs, br := blocks.Reader{ Log: log, LineRead: blocks.ReaderPassthrough, - BlockRead: func(br *blocks.Reader, i int, b string) error { + BlockRead: func(br *blocks.Reader, i int, b string, r bool) error { var fb string var err error if fmtverbs { @@ -581,6 +590,10 @@ func upgrade012File(fs afero.Fs, log *logrus.Logger, filename string, fmtverbs, return err } + if r { + fb = reindent(fb, b) + } + hasChange := fb != b if br.CurrentNodeCursor != nil { @@ -624,3 +637,19 @@ func upgrade012File(fs afero.Fs, log *logrus.Logger, filename string, fmtverbs, return &br, err } + +func reindent(formatted string, original string) (reindented string) { + prefix := "" + for _, r := range original { + if unicode.IsSpace(r) { + if r == '\n' { + prefix = "" + continue + } + prefix += string(r) + } else { + break + } + } + return strings.TrimRight(strings.ReplaceAll(formatted, "\n", "\n"+prefix), prefix) +} diff --git a/lib/blocks/blockreader.go b/lib/blocks/blockreader.go index b4f97cea..7129bd71 100644 --- a/lib/blocks/blockreader.go +++ b/lib/blocks/blockreader.go @@ -10,9 +10,11 @@ import ( "go/token" "io" "io/ioutil" + "path/filepath" "regexp" "strconv" "strings" + "unicode" "github.com/sirupsen/logrus" "github.com/spf13/afero" @@ -21,13 +23,55 @@ import ( var lineWithLeadingSpacesMatcher = regexp.MustCompile("^[[:space:]]*(.*\n)$") -type blockReadFunc func(*Reader, int, string) error +type blockReadFunc func(*Reader, int, string, bool) error type BlockWriter interface { Write(index, startLine, endLine int, text string) Close() error } +type blockFormat interface { + isStartingLine(line string) bool + isFinishLine(line string) bool + keepIndentation() bool +} + +type markdownBlockFormat struct{} + +func (markdownBlockFormat) isStartingLine(line string) bool { + // nolint:gocritic + if strings.HasPrefix(line, "```hcl") { // documentation + return true + } else if strings.HasPrefix(line, "```terraform") { // documentation + return true + } else if strings.HasPrefix(line, "```tf") { // documentation + return true + } + return false +} + +func (mbf markdownBlockFormat) isFinishLine(line string) bool { + return strings.HasPrefix(line, "```") +} + +func (mbf markdownBlockFormat) keepIndentation() bool { + return false +} + +type restructuredTextBlockFormat struct{} + +func (restructuredTextBlockFormat) isStartingLine(line string) bool { + return strings.HasPrefix(line, ".. code:: terraform") +} + +func (mbf restructuredTextBlockFormat) isFinishLine(line string) bool { + return strings.Compare(line, strings.TrimLeftFunc(line, unicode.IsSpace)) == 0 +} + +func (mbf restructuredTextBlockFormat) keepIndentation() bool { + return true +} + type Reader struct { FileName string @@ -71,23 +115,6 @@ func ReaderIgnore(br *Reader, number int, line string) error { return nil } -func IsStartLine(line string) bool { - // nolint:gocritic - if strings.HasPrefix(line, "```hcl") { // documentation - return true - } else if strings.HasPrefix(line, "```terraform") { // documentation - return true - } else if strings.HasPrefix(line, "```tf") { // documentation - return true - } - - return false -} - -func IsFinishLine(line string) bool { - return strings.HasPrefix(line, "```") // documentation -} - type blockVisitor struct { br *Reader fset *token.FileSet @@ -113,7 +140,7 @@ func (bv blockVisitor) Visit(cursor *astutil.Cursor) bool { bv.br.LineCount = bv.fset.Position(node.End()).Line // This is to deal with some outputs using just LineCount and some using LineCount-BlockCurrentLine bv.br.BlockCurrentLine = bv.fset.Position(node.End()).Line - bv.fset.Position(node.Pos()).Line - err := bv.f(bv.br, 0, value) + err := bv.f(bv.br, 0, value, false) if err != nil { bv.br.ErrorBlocks++ bv.br.Log.Errorf("block %d @ %s:%d failed to process with: %v", bv.br.BlockCount, bv.br.FileName, bv.fset.Position(node.Pos()).Line, err) @@ -248,6 +275,14 @@ func (br *Reader) doTheThingPatternMatch(fs afero.Fs, filename string, stdin io. } } + var format blockFormat + switch filepath.Ext(filename) { + case ".rst": + format = restructuredTextBlockFormat{} + default: + format = markdownBlockFormat{} + } + br.LineCount = 0 br.BlockCount = 0 s := bufio.NewScanner(br.Reader) @@ -260,7 +295,7 @@ func (br *Reader) doTheThingPatternMatch(fs afero.Fs, filename string, stdin io. return fmt.Errorf("NB LineRead failed @ %s:%d for %s: %v", br.FileName, br.LineCount, l, err) } - if IsStartLine(l) { + if format.isStartingLine(l) { block := "" br.BlockCurrentLine = 0 br.BlockCount += 1 @@ -271,7 +306,7 @@ func (br *Reader) doTheThingPatternMatch(fs afero.Fs, filename string, stdin io. l2 := s.Text() + "\n" // make sure we don't run into another block - if IsStartLine(l2) { + if format.isStartingLine(l2) { // the end of current block must be malformed, so lets pass it through and log an error 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? @@ -287,7 +322,7 @@ func (br *Reader) doTheThingPatternMatch(fs afero.Fs, filename string, stdin io. continue } - if IsFinishLine(l2) { + if format.isFinishLine(l2) { if br.FixFinishLines { l2 = lineWithLeadingSpacesMatcher.ReplaceAllString(l2, `$1`) } @@ -295,7 +330,7 @@ func (br *Reader) doTheThingPatternMatch(fs afero.Fs, filename string, stdin io. br.LinesBlock += br.BlockCurrentLine // todo configure this behaviour with switch's - if err := br.BlockRead(br, br.LineCount, block); err != nil { + if err := br.BlockRead(br, br.LineCount, block, format.keepIndentation()); err != nil { // for now ignore block errors and output unformatted br.ErrorBlocks += 1 br.Log.Errorf("block %d @ %s:%d failed to process with: %v", br.BlockCount, br.FileName, br.LineCount-br.BlockCurrentLine, err) diff --git a/lib/blocks/blockreader_test.go b/lib/blocks/blockreader_test.go index be69f0ae..92c97915 100644 --- a/lib/blocks/blockreader_test.go +++ b/lib/blocks/blockreader_test.go @@ -119,6 +119,17 @@ func TestBlockDetection(t *testing.T) { resource "aws_s3_bucket" "leading-space-and-line" { bucket = "tf-test-bucket-leading-space-and-line" } +`, + }, + }, + }, + { + sourcefile: "testdata/test3.rst", + expectedBlocks: []block{ + {text: ` resource "aws_s3_bucket" "terraform" { + bucket = "tf-test-bucket-terraform" + } + `, }, }, @@ -136,7 +147,7 @@ func TestBlockDetection(t *testing.T) { Log: log, ReadOnly: true, LineRead: ReaderIgnore, - BlockRead: func(br *Reader, i int, b string) error { + BlockRead: func(br *Reader, i int, b string, r bool) error { actualBlocks = append(actualBlocks, block{ leadingPadding: br.CurrentNodeLeadingPadding, text: b, diff --git a/lib/blocks/testdata/test3.rst b/lib/blocks/testdata/test3.rst new file mode 100644 index 00000000..22431b80 --- /dev/null +++ b/lib/blocks/testdata/test3.rst @@ -0,0 +1,10 @@ +# Test 3 + +Test fenced code block with `terraform` + +.. code:: terraform + resource "aws_s3_bucket" "terraform" { + bucket = "tf-test-bucket-terraform" + } + +Stuff that is not to be formatted.