From 449dacbfc755024e0c2f272986488051fe6db3f6 Mon Sep 17 00:00:00 2001 From: Braydon Kains <93549768+braydonk@users.noreply.github.com> Date: Sat, 10 Feb 2024 09:30:12 -0500 Subject: [PATCH] Retain single line breaks (#159) * Retain single line breaks * docs adjustment --- docs/config-file.md | 25 ++++++++------- formatters/basic/config.go | 21 ++++++------ formatters/basic/features.go | 4 +-- formatters/basic/formatter_test.go | 48 ++++++++++++++++++++-------- internal/hotfix/retain_line_break.go | 18 +++++++---- 5 files changed, 73 insertions(+), 43 deletions(-) diff --git a/docs/config-file.md b/docs/config-file.md index 3d8f12b..15a5489 100644 --- a/docs/config-file.md +++ b/docs/config-file.md @@ -57,18 +57,19 @@ The basic formatter is a barebones formatter that simply takes the data provided ### Configuration -| Key | Type | Default | Description | -|:-------------------------|:---------------|:--------|:------------| -| `indent` | int | 2 | The indentation level in spaces to use for the formatted yaml| -| `include_document_start` | bool | false | Include `---` at document start | -| `line_ending` | `lf` or `crlf` | `crlf` on Windows, `lf` otherwise | Parse and write the file with "lf" or "crlf" line endings. This setting will be overwritten by the global `line_ending`. | -| `retain_line_breaks` | bool | false | Retain line breaks in formatted yaml | -| `disallow_anchors` | bool | false | If true, reject any YAML anchors or aliases found in the document. | -| `max_line_length` | int | 0 | Set the maximum line length (see notes below). if not set, defaults to 0 which means no limit. | -| `scan_folded_as_literal` | bool | false | Option that will preserve newlines in folded block scalars (blocks that start with `>`). | -| `indentless_arrays` | bool | false | Render `-` array items (block sequence items) without an increased indent. | -| `drop_merge_tag` | bool | false | Assume that any well formed merge using just a `<<` token will be a merge, and drop the `!!merge` tag from the formatted result. | -| `pad_line_comments` | int | 1 | The number of padding spaces to insert before line comments. | +| Key | Type | Default | Description | +|:----------------------------|:---------------|:--------|:------------| +| `indent` | int | 2 | The indentation level in spaces to use for the formatted yaml. | +| `include_document_start` | bool | false | Include `---` at document start. | +| `line_ending` | `lf` or `crlf` | `crlf` on Windows, `lf` otherwise | Parse and write the file with "lf" or "crlf" line endings. This setting will be overwritten by the global `line_ending`. | +| `retain_line_breaks` | bool | false | Retain line breaks in formatted yaml. | +| `retain_line_breaks_single` | bool | false | (NOTE: Takes precedence over `retain_line_breaks`) Retain line breaks in formatted yaml, but only keep a single line in groups of many blank lines. | +| `disallow_anchors` | bool | false | If true, reject any YAML anchors or aliases found in the document. | +| `max_line_length` | int | 0 | Set the maximum line length (see notes below). if not set, defaults to 0 which means no limit. | +| `scan_folded_as_literal` | bool | false | Option that will preserve newlines in folded block scalars (blocks that start with `>`). | +| `indentless_arrays` | bool | false | Render `-` array items (block sequence items) without an increased indent. | +| `drop_merge_tag` | bool | false | Assume that any well formed merge using just a `<<` token will be a merge, and drop the `!!merge` tag from the formatted result. | +| `pad_line_comments` | int | 1 | The number of padding spaces to insert before line comments. | ### Note on `max_line_length` diff --git a/formatters/basic/config.go b/formatters/basic/config.go index 16fff7d..6aea472 100644 --- a/formatters/basic/config.go +++ b/formatters/basic/config.go @@ -21,16 +21,17 @@ import ( ) type Config struct { - Indent int `mapstructure:"indent"` - IncludeDocumentStart bool `mapstructure:"include_document_start"` - LineEnding yamlfmt.LineBreakStyle `mapstructure:"line_ending"` - LineLength int `mapstructure:"max_line_length"` - RetainLineBreaks bool `mapstructure:"retain_line_breaks"` - DisallowAnchors bool `mapstructure:"disallow_anchors"` - ScanFoldedAsLiteral bool `mapstructure:"scan_folded_as_literal"` - IndentlessArrays bool `mapstructure:"indentless_arrays"` - DropMergeTag bool `mapstructure:"drop_merge_tag"` - PadLineComments int `mapstructure:"pad_line_comments"` + Indent int `mapstructure:"indent"` + IncludeDocumentStart bool `mapstructure:"include_document_start"` + LineEnding yamlfmt.LineBreakStyle `mapstructure:"line_ending"` + LineLength int `mapstructure:"max_line_length"` + RetainLineBreaks bool `mapstructure:"retain_line_breaks"` + RetainLineBreaksSingle bool `mapstructure:"retain_line_breaks_single"` + DisallowAnchors bool `mapstructure:"disallow_anchors"` + ScanFoldedAsLiteral bool `mapstructure:"scan_folded_as_literal"` + IndentlessArrays bool `mapstructure:"indentless_arrays"` + DropMergeTag bool `mapstructure:"drop_merge_tag"` + PadLineComments int `mapstructure:"pad_line_comments"` } func DefaultConfig() *Config { diff --git a/formatters/basic/features.go b/formatters/basic/features.go index eddb6a4..976c2d6 100644 --- a/formatters/basic/features.go +++ b/formatters/basic/features.go @@ -23,12 +23,12 @@ import ( func ConfigureFeaturesFromConfig(config *Config) yamlfmt.FeatureList { features := []yamlfmt.Feature{} - if config.RetainLineBreaks { + if config.RetainLineBreaks || config.RetainLineBreaksSingle { lineSep, err := config.LineEnding.Separator() if err != nil { lineSep = "\n" } - featLineBreak := hotfix.MakeFeatureRetainLineBreak(lineSep) + featLineBreak := hotfix.MakeFeatureRetainLineBreak(lineSep, config.RetainLineBreaksSingle) features = append(features, featLineBreak) } return features diff --git a/formatters/basic/formatter_test.go b/formatters/basic/formatter_test.go index bd5f338..39d666e 100644 --- a/formatters/basic/formatter_test.go +++ b/formatters/basic/formatter_test.go @@ -125,12 +125,13 @@ func TestEmojiSupport(t *testing.T) { func TestRetainLineBreaks(t *testing.T) { testCases := []struct { - desc string + name string input string expect string + single bool }{ { - desc: "basic", + name: "basic", input: `a: 1 b: 2`, @@ -140,7 +141,7 @@ b: 2 `, }, { - desc: "multi-doc", + name: "multi-doc", input: `a: 1 # tail comment @@ -154,7 +155,7 @@ b: 2 `, }, { - desc: "literal string", + name: "literal string", input: `a: 1 shell: | @@ -175,7 +176,7 @@ shell: | `, }, { - desc: "multi level nested literal string", + name: "multi level nested literal string", input: `a: 1 x: y: @@ -192,20 +193,41 @@ x: # bye echo "hello, world" +`, + }, + { + name: "retain single line break", + single: true, + input: `a: 1 + + + + +b: 2 + + +c: 3 +`, + expect: `a: 1 + +b: 2 + +c: 3 `, }, } - config := basic.DefaultConfig() - config.RetainLineBreaks = true - f := newFormatter(config) - for _, c := range testCases { - t.Run(c.desc, func(t *testing.T) { - got, err := f.Format([]byte(c.input)) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + config := basic.DefaultConfig() + config.RetainLineBreaks = true + config.RetainLineBreaksSingle = tc.single + f := newFormatter(config) + got, err := f.Format([]byte(tc.input)) if err != nil { t.Fatalf("expected formatting to pass, returned error: %v", err) } - if string(got) != c.expect { - t.Fatalf("didn't retain line breaks\nresult: %v\nexpect %s", string(got), c.expect) + if string(got) != tc.expect { + t.Fatalf("didn't retain line breaks\nresult: %v\nexpect %s", string(got), tc.expect) } }) } diff --git a/internal/hotfix/retain_line_break.go b/internal/hotfix/retain_line_break.go index 14ed738..05504e8 100644 --- a/internal/hotfix/retain_line_break.go +++ b/internal/hotfix/retain_line_break.go @@ -42,31 +42,37 @@ func (p *paddinger) adjust(txt string) { } } -func MakeFeatureRetainLineBreak(linebreakStr string) yamlfmt.Feature { +func MakeFeatureRetainLineBreak(linebreakStr string, chomp bool) yamlfmt.Feature { return yamlfmt.Feature{ Name: "Retain Line Breaks", - BeforeAction: replaceLineBreakFeature(linebreakStr), + BeforeAction: replaceLineBreakFeature(linebreakStr, chomp), AfterAction: restoreLineBreakFeature(linebreakStr), } } -func replaceLineBreakFeature(newlineStr string) yamlfmt.FeatureFunc { +func replaceLineBreakFeature(newlineStr string, chomp bool) yamlfmt.FeatureFunc { return func(content []byte) ([]byte, error) { var buf bytes.Buffer reader := bytes.NewReader(content) scanner := bufio.NewScanner(reader) + var inLineBreaks bool var padding paddinger for scanner.Scan() { txt := scanner.Text() padding.adjust(txt) if strings.TrimSpace(txt) == "" { // line break or empty space line. + if chomp && inLineBreaks { + continue + } buf.WriteString(padding.String()) // prepend some padding incase literal multiline strings. buf.WriteString(lineBreakPlaceholder) buf.WriteString(newlineStr) - continue + inLineBreaks = true + } else { + buf.WriteString(txt) + buf.WriteString(newlineStr) + inLineBreaks = false } - buf.WriteString(txt) - buf.WriteString(newlineStr) } return buf.Bytes(), scanner.Err() }