github.com/google/yamlfmt@v0.12.2-0.20240514121411-7f77800e2681/internal/hotfix/retain_line_break.go (about)

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // The features in this file are to retain line breaks.
    16  // The basic idea is to insert/remove placeholder comments in the yaml document before and after the format process.
    17  
    18  package hotfix
    19  
    20  import (
    21  	"bufio"
    22  	"bytes"
    23  	"strings"
    24  
    25  	"github.com/google/yamlfmt"
    26  )
    27  
    28  const lineBreakPlaceholder = "#magic___^_^___line"
    29  
    30  type paddinger struct {
    31  	strings.Builder
    32  }
    33  
    34  func (p *paddinger) adjust(txt string) {
    35  	var indentSize int
    36  	for i := 0; i < len(txt) && txt[i] == ' '; i++ { // yaml only allows space to indent.
    37  		indentSize++
    38  	}
    39  	// Grows if the given size is larger than us and always return the max padding.
    40  	for diff := indentSize - p.Len(); diff > 0; diff-- {
    41  		p.WriteByte(' ')
    42  	}
    43  }
    44  
    45  func MakeFeatureRetainLineBreak(linebreakStr string, chomp bool) yamlfmt.Feature {
    46  	return yamlfmt.Feature{
    47  		Name:         "Retain Line Breaks",
    48  		BeforeAction: replaceLineBreakFeature(linebreakStr, chomp),
    49  		AfterAction:  restoreLineBreakFeature(linebreakStr),
    50  	}
    51  }
    52  
    53  func replaceLineBreakFeature(newlineStr string, chomp bool) yamlfmt.FeatureFunc {
    54  	return func(content []byte) ([]byte, error) {
    55  		var buf bytes.Buffer
    56  		reader := bytes.NewReader(content)
    57  		scanner := bufio.NewScanner(reader)
    58  		var inLineBreaks bool
    59  		var padding paddinger
    60  		for scanner.Scan() {
    61  			txt := scanner.Text()
    62  			padding.adjust(txt)
    63  			if strings.TrimSpace(txt) == "" { // line break or empty space line.
    64  				if chomp && inLineBreaks {
    65  					continue
    66  				}
    67  				buf.WriteString(padding.String()) // prepend some padding incase literal multiline strings.
    68  				buf.WriteString(lineBreakPlaceholder)
    69  				buf.WriteString(newlineStr)
    70  				inLineBreaks = true
    71  			} else {
    72  				buf.WriteString(txt)
    73  				buf.WriteString(newlineStr)
    74  				inLineBreaks = false
    75  			}
    76  		}
    77  		return buf.Bytes(), scanner.Err()
    78  	}
    79  }
    80  
    81  func restoreLineBreakFeature(newlineStr string) yamlfmt.FeatureFunc {
    82  	return func(content []byte) ([]byte, error) {
    83  		var buf bytes.Buffer
    84  		reader := bytes.NewReader(content)
    85  		scanner := bufio.NewScanner(reader)
    86  		for scanner.Scan() {
    87  			txt := scanner.Text()
    88  			if strings.TrimSpace(txt) == "" {
    89  				// The basic yaml lib inserts newline when there is a comment(either placeholder or by user)
    90  				// followed by optional line breaks and a `---` multi-documents.
    91  				// To fix it, the empty line could only be inserted by us.
    92  				continue
    93  			}
    94  			if strings.HasPrefix(strings.TrimLeft(txt, " "), lineBreakPlaceholder) {
    95  				buf.WriteString(newlineStr)
    96  				continue
    97  			}
    98  			buf.WriteString(txt)
    99  			buf.WriteString(newlineStr)
   100  		}
   101  		return buf.Bytes(), scanner.Err()
   102  	}
   103  }