1
0
mirror of https://github.com/TomWright/dasel.git synced 2022-05-22 02:32:45 +03:00

Add FormatNode and FormatNodes functions

This commit is contained in:
Tom Wright
2021-08-11 08:58:06 +01:00
parent d983208572
commit 56f6f1a3df
3 changed files with 322 additions and 0 deletions

View File

@@ -60,6 +60,12 @@ type Node struct {
wasInitialised bool
}
// String returns the value of the node as a string.
// No formatting is done here, you get the raw value.
func (n *Node) String() string {
return fmt.Sprint(n.InterfaceValue())
}
// InterfaceValue returns the value stored within the node as an interface{}.
func (n *Node) InterfaceValue() interface{} {
// We shouldn't be able to get here but this will stop a panic if we do.

127
output_formatter.go Normal file
View File

@@ -0,0 +1,127 @@
package dasel
import (
"bytes"
"text/template"
)
// FormatNode formats a node with the format template and returns the result.
func FormatNode(node *Node, format string) (*bytes.Buffer, error) {
tpl, err := formatNodeTemplate(
&templateNode{
Node: node,
isFirst: true,
isLast: true,
},
).Parse(format)
if err != nil {
return nil, err
}
buf := new(bytes.Buffer)
err = tpl.Execute(buf, node.InterfaceValue())
return buf, err
}
type templateNode struct {
*Node
isFirst bool
isLast bool
}
// FormatNodes formats a slice of nodes with the format template and returns the result.
func FormatNodes(nodes []*Node, format string) (*bytes.Buffer, error) {
buf := new(bytes.Buffer)
nodesLen := len(nodes)
for k, node := range nodes {
tpl, err := formatNodeTemplate(
&templateNode{
Node: node,
isFirst: k == 0,
isLast: k == (nodesLen - 1),
},
).Parse(format)
if err != nil {
return nil, err
}
if err := tpl.Execute(buf, node.InterfaceValue()); err != nil {
return nil, err
}
}
return buf, nil
}
type formatTemplateFuncs struct {
node *templateNode
}
func (funcs *formatTemplateFuncs) funcMap() template.FuncMap {
return template.FuncMap{
"query": funcs.query,
"queryMultiple": funcs.queryMultiple,
"format": funcs.format,
"isFirst": funcs.isFirst,
"isLast": funcs.isLast,
"newline": funcs.newline,
}
}
func (funcs *formatTemplateFuncs) newline() string {
return "\n"
}
func (funcs *formatTemplateFuncs) isFirst() bool {
return funcs.node.isFirst
}
func (funcs *formatTemplateFuncs) isLast() bool {
return funcs.node.isLast
}
func (funcs *formatTemplateFuncs) query(selector string) *Node {
res, err := funcs.node.Query(selector)
if err != nil {
return nil
}
return res
}
func (funcs *formatTemplateFuncs) queryMultiple(selector string) []*Node {
res, err := funcs.node.QueryMultiple(selector)
if err != nil {
return nil
}
return res
}
func (funcs *formatTemplateFuncs) format(format string, target interface{}) string {
switch t := target.(type) {
case []*Node:
buf, err := FormatNodes(t, format)
if err != nil {
return err.Error()
}
res := buf.String()
return res
case *Node:
buf, err := FormatNode(t, format)
if err != nil {
return err.Error()
}
return buf.String()
}
return "<nil>"
}
func formatNodeTemplate(node *templateNode) *template.Template {
funcs := &formatTemplateFuncs{
node: node,
}
tpl := template.New("nodeFormat")
tpl.Funcs(funcs.funcMap())
return tpl
}

189
output_formatter_test.go Normal file
View File

@@ -0,0 +1,189 @@
package dasel_test
import (
"github.com/tomwright/dasel"
"testing"
)
func testFormatNode(value interface{}, format string, exp string) func(t *testing.T) {
return func(t *testing.T) {
node := dasel.New(value)
buf, err := dasel.FormatNode(node, format)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
got := buf.String()
if exp != got {
t.Errorf("expected %s, got %s", exp, got)
}
}
}
func testFormatNodes(values []interface{}, format string, exp string) func(t *testing.T) {
return func(t *testing.T) {
nodes := make([]*dasel.Node, len(values))
for k, v := range values {
nodes[k] = dasel.New(v)
}
buf, err := dasel.FormatNodes(nodes, format)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
got := buf.String()
if exp != got {
t.Errorf("expected %s, got %s", exp, got)
}
}
}
func TestFormatNode(t *testing.T) {
t.Run("InvalidFormatTemplate", func(t *testing.T) {
_, err := dasel.FormatNode(nil, "{{")
if err == nil {
t.Errorf("expected error but got none")
}
})
t.Run("PropertyAccess", testFormatNode(
map[string]interface{}{
"name": "Tom",
"email": "contact@tomwright.me",
},
`{{ .name }}, {{ .email }}`,
`Tom, contact@tomwright.me`,
))
t.Run("QueryAccess", testFormatNode(
map[string]interface{}{
"name": "Tom",
"email": "contact@tomwright.me",
},
`{{ query ".name" }}, {{ query ".email" }}`,
`Tom, contact@tomwright.me`,
))
t.Run("Format", testFormatNode(
map[string]interface{}{
"name": "Tom",
"email": "contact@tomwright.me",
},
`{{ query ".name" | format "{{ . }}" }}, {{ query ".email" | format "{{ . }}" }}`,
`Tom, contact@tomwright.me`,
))
t.Run("QueryAccessInvalidSelector", testFormatNode(
map[string]interface{}{
"name": "Tom",
"email": "contact@tomwright.me",
},
`{{ query ".bad" }}`,
`<nil>`,
))
t.Run("QueryMultipleCommaSeparated", testFormatNode(
map[string]interface{}{
"users": []map[string]interface{}{
{
"name": "Tom",
},
{
"name": "Jim",
},
{
"name": "Frank",
},
},
},
`{{ queryMultiple ".users.[*]" | format "{{ .name }}{{ if not isLast }},{{ end }}" }}`,
`Tom,Jim,Frank`,
))
t.Run("QueryMultipleLineSeparated", testFormatNode(
map[string]interface{}{
"users": []map[string]interface{}{
{
"name": "Tom",
},
{
"name": "Jim",
},
{
"name": "Frank",
},
},
},
`{{ queryMultiple ".users.[*]" | format "{{ .name }}{{ if not isLast }}{{ newline }}{{ end }}" }}`,
`Tom
Jim
Frank`,
))
t.Run("QueryMultipleDashSeparated", testFormatNode(
map[string]interface{}{
"users": []map[string]interface{}{
{
"name": "Tom",
},
{
"name": "Jim",
},
{
"name": "Frank",
},
},
},
`{{ queryMultiple ".users.[*]" | format "{{ if not isFirst }}---{{ newline }}{{ end }}{{ .name }}{{ if not isLast }}{{ newline }}{{ end }}" }}`,
`Tom
---
Jim
---
Frank`,
))
}
func TestFormatNodes(t *testing.T) {
t.Run("InvalidFormatTemplate", func(t *testing.T) {
_, err := dasel.FormatNodes([]*dasel.Node{dasel.New("")}, "{{")
if err == nil {
t.Errorf("expected error but got none")
}
})
t.Run("PropertyAccess", testFormatNodes(
[]interface{}{
map[string]interface{}{
"name": "Tom",
"email": "contact@tomwright.me",
},
map[string]interface{}{
"name": "Jim",
"email": "jim@gmail.com",
},
},
"{{ .name }}, {{ .email }}{{ if not isLast }}{{ newline }}{{ end }}",
`Tom, contact@tomwright.me
Jim, jim@gmail.com`,
))
t.Run("QueryAccess", testFormatNodes(
[]interface{}{
map[string]interface{}{
"name": "Tom",
"email": "contact@tomwright.me",
},
map[string]interface{}{
"name": "Jim",
"email": "jim@gmail.com",
},
},
`{{ query ".name" }}, {{ query ".email" }}{{ if not isLast }}{{ newline }}{{ end }}`,
`Tom, contact@tomwright.me
Jim, jim@gmail.com`))
t.Run("QueryAccessInvalidSelector", testFormatNodes(
[]interface{}{
map[string]interface{}{
"name": "Tom",
"email": "contact@tomwright.me",
},
map[string]interface{}{
"name": "Jim",
"email": "jim@gmail.com",
},
},
`{{ query ".bad" }}{{ newline }}`,
`<nil>
<nil>
`,
))
}