github.com/andresbott/yamlfmt@v0.1.0/internal/yamlfmt/yamlfmt.go (about)

     1  package yamlfmt
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"github.com/davecgh/go-spew/spew"
     9  	"gopkg.in/yaml.v3"
    10  	"io"
    11  	"strings"
    12  )
    13  
    14  var _ = spew.Dump // prevent spew from being removed if unused
    15  
    16  // Format takes a yaml bytearray and applies the relevant changes to format it
    17  func Format(in []byte) ([]byte, error) {
    18  
    19  	yamlContent := in
    20  
    21  	var err error
    22  	yamlContent, err = addLineBreakFlag(yamlContent)
    23  	if err != nil {
    24  		return nil, err
    25  	}
    26  
    27  	// Format the yaml content
    28  	reader := bytes.NewReader(yamlContent)
    29  	decoder := yaml.NewDecoder(reader)
    30  	documents := []yaml.Node{}
    31  
    32  	for {
    33  		var docNode yaml.Node
    34  		err := decoder.Decode(&docNode)
    35  		if err != nil {
    36  			if errors.Is(err, io.EOF) {
    37  				break
    38  			}
    39  			return nil, fmt.Errorf("error decoding yaml document: %v", err)
    40  		}
    41  		walkNode(&docNode)
    42  		documents = append(documents, docNode)
    43  	}
    44  
    45  	var b bytes.Buffer
    46  	e := yaml.NewEncoder(&b)
    47  	e.SetIndent(2)
    48  	for _, doc := range documents {
    49  		err = e.Encode(&doc)
    50  		if err != nil {
    51  			return nil, err
    52  		}
    53  	}
    54  
    55  	result := b.Bytes()
    56  	result, err = removeLineBreakFlag(result)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	return result, nil
    62  }
    63  
    64  const magicLine = "#==================xieK7ej3eiwoh9mie4che=========================="
    65  
    66  // addLineBreakFlag reads all the lines in the file and replaces any line breaks with a single flag to be removed later
    67  func addLineBreakFlag(in []byte) ([]byte, error) {
    68  
    69  	reader := bytes.NewReader(in)
    70  	scanner := bufio.NewScanner(reader)
    71  
    72  	out := strings.Builder{}
    73  	lineCount := 0
    74  	for scanner.Scan() {
    75  		line := scanner.Text()
    76  		line = strings.TrimSuffix(line, " ")
    77  
    78  		if strings.TrimSpace(line) == "" {
    79  			if lineCount == 0 {
    80  				lineCount = 1
    81  				out.WriteString(magicLine + "\n")
    82  			}
    83  		} else {
    84  			lineCount = 0
    85  			out.WriteString(line + "\n")
    86  		}
    87  	}
    88  
    89  	if err := scanner.Err(); err != nil {
    90  		return nil, fmt.Errorf("error when readeing a line with scanner: %v", err)
    91  	}
    92  
    93  	return []byte(out.String()), nil
    94  }
    95  
    96  // removeLineBreakFlag reads all the lines in the file and replaces linebreak flags with real line breaks
    97  // sometimes line breaks are still present in the output, this function will remove them as well and only
    98  // introduce line breaks where it finds the magic line
    99  func removeLineBreakFlag(in []byte) ([]byte, error) {
   100  	reader := bytes.NewReader(in)
   101  	scanner := bufio.NewScanner(reader)
   102  
   103  	out := strings.Builder{}
   104  
   105  	for scanner.Scan() {
   106  		line := scanner.Text()
   107  		line = strings.TrimSuffix(line, " ")
   108  		if strings.TrimSpace(line) == "" {
   109  			continue
   110  		}
   111  		if strings.TrimSpace(line) == magicLine {
   112  			out.WriteString("\n")
   113  		} else {
   114  			out.WriteString(line + "\n")
   115  		}
   116  	}
   117  
   118  	if err := scanner.Err(); err != nil {
   119  		return nil, fmt.Errorf("error when readeing a line with scanner: %v", err)
   120  	}
   121  	return []byte(out.String()), nil
   122  }
   123  
   124  // walkNode will recursively walk the yaml structure and apply changes:
   125  // - remove quote style from keys and values
   126  func walkNode(node *yaml.Node) {
   127  
   128  	if len(node.Content) > 0 {
   129  		for _, n := range node.Content {
   130  			if n.Style == yaml.DoubleQuotedStyle {
   131  				n.Style = 0
   132  			}
   133  			walkNode(n)
   134  		}
   135  	}
   136  }