From 7dd12001536675b01740a2e559d6c72f274848b2 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 15 Jun 2022 18:36:18 +0200 Subject: [PATCH] feat: support reStructuredText --- cli/cmds.go | 20 ++++-- cli/util.go | 26 ++++++++ lib/blocks/blockreader.go | 38 +++++------ lib/blocks/blockreader_test.go | 112 ++++++++++++++++++++++++++++++++- lib/blocks/testdata/test3.rst | 110 ++++++++++++++++++++++++++++++++ lib/blocks/textformat.go | 51 +++++++++++++++ 6 files changed, 329 insertions(+), 28 deletions(-) create mode 100644 cli/util.go create mode 100644 lib/blocks/testdata/test3.rst create mode 100644 lib/blocks/textformat.go diff --git a/cli/cmds.go b/cli/cmds.go index 6dc8858d..c7697f4a 100644 --- a/cli/cmds.go +++ b/cli/cmds.go @@ -400,7 +400,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, preserveIndent bool) error { if fmtverbs { b = verbs.Escape(b) } @@ -433,7 +433,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, preserveIndent bool) error { var fb string var err error if fmtverbs { @@ -445,6 +445,10 @@ func diffFile(fs afero.Fs, log *logrus.Logger, filename string, fmtverbs, verbos return err } + if preserveIndent { + fb = indentToOriginalLevel(fb, b) + } + if fb == b { return nil } @@ -500,7 +504,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, preserveIndent bool) error { var fb string var err error if fmtverbs { @@ -512,6 +516,10 @@ func formatFile(fs afero.Fs, log *logrus.Logger, filename string, fmtverbs, fixF return err } + if preserveIndent { + fb = indentToOriginalLevel(fb, b) + } + hasChange := fb != b if br.CurrentNodeCursor != nil { @@ -574,7 +582,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, preserveIndent bool) error { var fb string var err error if fmtverbs { @@ -586,6 +594,10 @@ func upgrade012File(fs afero.Fs, log *logrus.Logger, filename string, fmtverbs, return err } + if preserveIndent { + fb = indentToOriginalLevel(fb, b) + } + hasChange := fb != b if br.CurrentNodeCursor != nil { diff --git a/cli/util.go b/cli/util.go new file mode 100644 index 00000000..367eb717 --- /dev/null +++ b/cli/util.go @@ -0,0 +1,26 @@ +package cli + +import ( + "strings" + "unicode" +) + +func indentToOriginalLevel(formatted string, original string) string { + prefix := "" + for _, r := range original { + if unicode.IsSpace(r) { + if r == '\n' { + prefix = "" + continue + } + prefix += string(r) + } else { + break + } + } + res := strings.ReplaceAll(formatted, "\n", "\n"+prefix) + res = strings.TrimRight(res, prefix) + res = strings.TrimLeft(res, prefix) + + return prefix + res +} diff --git a/lib/blocks/blockreader.go b/lib/blocks/blockreader.go index 3c808125..4cc9b034 100644 --- a/lib/blocks/blockreader.go +++ b/lib/blocks/blockreader.go @@ -10,6 +10,7 @@ import ( "go/token" "io" "io/ioutil" + "path/filepath" "regexp" "strconv" "strings" @@ -21,7 +22,7 @@ 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) @@ -71,23 +72,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 @@ -116,7 +100,7 @@ func (bv blockVisitor) Visit(cursor *astutil.Cursor) bool { // 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) @@ -256,6 +240,14 @@ func (br *Reader) doTheThingPatternMatch(fs afero.Fs, filename string, stdin io. } } + var format textFormat + switch filepath.Ext(filename) { + case ".rst": + format = restructuredTextFormat{} + default: + format = markdownTextFormat{} + } + br.LineCount = 0 br.BlockCount = 0 s := bufio.NewScanner(br.Reader) @@ -268,7 +260,7 @@ func (br *Reader) doTheThingPatternMatch(fs afero.Fs, filename string, stdin io. return fmt.Errorf("NB LineRead failed @ %s:%d for %s: %w", br.FileName, br.LineCount, l, err) } - if IsStartLine(l) { + if format.isStartingLine(l) { block := "" br.BlockCurrentLine = 0 br.BlockCount++ @@ -279,7 +271,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? @@ -296,7 +288,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`) } @@ -304,7 +296,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.preserveIndentation()); err != nil { // for now ignore block errors and output unformatted br.ErrorBlocks++ 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 cf940141..c98fb597 100644 --- a/lib/blocks/blockreader_test.go +++ b/lib/blocks/blockreader_test.go @@ -120,6 +120,116 @@ 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" + } + +`, + }, + { + text: ` resource "azurerm_resource_group" "example" { + name = "testaccbatch" + location = "West Europe" + } + + resource "azurerm_storage_account" "example" { + name = "testaccsa" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "LRS" + } + + resource "azurerm_batch_account" "example" { + name = "testaccbatch" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + pool_allocation_mode = "BatchService" + storage_account_id = azurerm_storage_account.example.id + + tags = { + env = "test" + } + } + + resource "azurerm_batch_certificate" "example" { + resource_group_name = azurerm_resource_group.example.name + account_name = azurerm_batch_account.example.name + certificate = filebase64("certificate.cer") + format = "Cer" + thumbprint = "312d31a79fa0cef49c00f769afc2b73e9f4edf34" + thumbprint_algorithm = "SHA1" + } + + resource "azurerm_batch_pool" "example" { + name = "testaccpool" + resource_group_name = azurerm_resource_group.example.name + account_name = azurerm_batch_account.example.name + display_name = "Test Acc Pool Auto" + vm_size = "Standard_A1" + node_agent_sku_id = "batch.node.ubuntu 20.04" + + auto_scale { + evaluation_interval = "PT15M" + + formula = <