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 }