Skip to content

Commit

Permalink
feat: Align comments.
Browse files Browse the repository at this point in the history
Align the comments with a bit of visual space after the longest
configuration line to help make the annotated output more clear.
  • Loading branch information
schmidtw committed Dec 21, 2022
1 parent d1c362b commit db72ec3
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 21 deletions.
114 changes: 108 additions & 6 deletions encoder.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,38 @@
// SPDX-FileCopyrightText: 2022 Weston Schmidt <weston_schmidt@alumni.purdue.edu>
// SPDX-License-Identifier: Apache-2.0

// yaml provides a way to encode both the simple form and the detailed form
// yamlencoder provides a way to encode both the simple form and the detailed form
// of configuration data for the goschtalt library.
package yaml
//
// # Detailed Output
//
// The details about where the configuration value originated are included as
// a list of `file:line[col]` values in a comment if goschtalt knows the origin.
// Not all decoders support tracking all this information. Comment will always
// be present so it's easier to handle the file using simple cli text processors.
//
// Example
//
// candy: bar # file.yml:1[8]
// cats: # file.yml:2[1]
// - madd # file.yml:3[7]
// - tabby # file.yml:4[7]
// other: # file.yml:5[1]
// things: # file.yml:6[5]
// green: # file.yml:8[9]
// - grass # unknown
// - ground # file.yml:10[15]
// red: balloons # file.yml:7[14]
// trending: now # file.yml:12[15]
package yamlencoder

import (
"bufio"
"bytes"
"encoding/base32"
"errors"
"sort"
"strings"

"github.com/goschtalt/goschtalt"
"github.com/goschtalt/goschtalt/pkg/encoder"
Expand Down Expand Up @@ -60,7 +85,12 @@ func (e Encoder) EncodeExtended(obj meta.Object) ([]byte, error) {
}
doc.Content = append(doc.Content, &n)

return yml.Marshal(&doc)
b, err := yml.Marshal(&doc)
if err != nil {
return nil, err
}

return alignComments(b)
}

// encoderWrapper handles the fact that the yaml decoder may panic instead of
Expand All @@ -77,8 +107,10 @@ func encoderWrapper(n *yml.Node, v any) (err error) {

// encode is an internal helper function that builds the yml.Node based tree
// to give to the yaml encoder. This is likely specific to this yaml encoder.
// Also always be sure to include a comment on each line so the alignment process
// in alignComments() is simpler logic.
func encode(obj meta.Object) (n yml.Node, err error) {
n.LineComment = obj.OriginString()
n.LineComment = encodeComment(obj.OriginString())
kind := obj.Kind()

if kind == meta.Value {
Expand All @@ -87,7 +119,7 @@ func encode(obj meta.Object) (n yml.Node, err error) {
if err != nil {
return yml.Node{}, err
}
n.LineComment = obj.OriginString() // The encode wipes this out.
n.LineComment = encodeComment(obj.OriginString()) // The encode wipes this out.
return n, nil
}

Expand Down Expand Up @@ -118,7 +150,7 @@ func encode(obj meta.Object) (n yml.Node, err error) {
v := obj.Map[k]
key := yml.Node{
Kind: yml.ScalarNode,
LineComment: v.OriginString(),
LineComment: encodeComment(v.OriginString()),
Value: k,
}
val, err := encode(v)
Expand All @@ -132,3 +164,73 @@ func encode(obj meta.Object) (n yml.Node, err error) {

return n, nil
}

// encodeComment base32 encodes the comment so the processing needed to align
// the comments is easier. We can simply look for the right-most # because of
// the encoding excluding # from the character set.
func encodeComment(s string) string {
if len(s) == 0 {
s = "unknown"
}
return base32.StdEncoding.EncodeToString([]byte(s))
}

// decodeComment is the reverse of encodeComment(), but handles the case of if
// decoding fails. It should never fail, but it checks for it anyway.
func decodeComment(s string) (string, error) {
buf, err := base32.StdEncoding.DecodeString(s)
if err != nil {
return "", err
}

return string(buf), nil
}

// alignComments finds the longest line, adds 8 spaces, then aligns the comments
// to the next tabstop (assuming tabwidth is 4). This is also where the comments
// are decoded from base32.
func alignComments(buf []byte) ([]byte, error) {
// Assume each line is about 24 bytes long as a starting buffer size.
// A smaller line size guess reduces the re-allocations needed later.
lines := make([]string, 0, len(buf)/24)
scanner := bufio.NewScanner(bytes.NewReader(buf))

var widest int

for scanner.Scan() {
line := scanner.Text()
if found := strings.LastIndex(line, "# "); found > widest {
widest = found
}

lines = append(lines, line)
}

widest += 8 + (widest % 4)

var b strings.Builder
for _, line := range lines {
if found := strings.LastIndex(line, "# "); found > 0 {
left := line[:found]
right := line[found:]
comment, err := decodeComment(right[2:])
if err != nil {
// This isn't really possible unless our the encoder below
// changes. This seems better than either a silent failure
// or a panic.
return nil, err
}

b.WriteString(left)
for found < widest {
b.WriteString(" ")
found++
}
b.WriteString("# ")
b.WriteString(comment)
b.WriteString("\n")
}
}

return []byte(b.String()), nil
}
39 changes: 24 additions & 15 deletions encoder_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2022 Weston Schmidt <weston_schmidt@alumni.purdue.edu>
// SPDX-License-Identifier: Apache-2.0

package yaml
package yamlencoder

import (
"testing"
Expand Down Expand Up @@ -82,8 +82,9 @@ func TestEncodeExtended(t *testing.T) {
Origins: []meta.Origin{{File: "file.yml", Line: 8, Col: 9}},
Array: []meta.Object{
{
Origins: []meta.Origin{{File: "file.yml", Line: 9, Col: 15}},
Value: "grass",
// Leave the origin off here to show what happens if none
// is present.
Value: "grass",
},
{
Origins: []meta.Origin{{File: "file.yml", Line: 10, Col: 15}},
Expand Down Expand Up @@ -118,18 +119,18 @@ other:
red: balloons
trending: now
`,
expectedExtended: `candy: bar # file.yml:1[8]
cats: # file.yml:2[1]
- madd # file.yml:3[7]
- tabby # file.yml:4[7]
other: # file.yml:5[1]
things: # file.yml:6[5]
green: # file.yml:8[9]
- grass # file.yml:9[15]
- ground # file.yml:10[15]
- water # file.yml:11[15]
red: balloons # file.yml:7[14]
trending: now # file.yml:12[15]
expectedExtended: `candy: bar # file.yml:1[8]
cats: # file.yml:2[1]
- madd # file.yml:3[7]
- tabby # file.yml:4[7]
other: # file.yml:5[1]
things: # file.yml:6[5]
green: # file.yml:8[9]
- grass # unknown
- ground # file.yml:10[15]
- water # file.yml:11[15]
red: balloons # file.yml:7[14]
trending: now # file.yml:12[15]
`,
},
{
Expand Down Expand Up @@ -231,3 +232,11 @@ other: # file.yml:5[1]
})
}
}

func TestDecodeComment(t *testing.T) {
assert := assert.New(t)

s, err := decodeComment("#")
assert.Equal("", s)
assert.Error(err)
}

0 comments on commit db72ec3

Please sign in to comment.