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

Merge pull request #192 from beatcracker/from-file

Add file read/write support to Go package
This commit is contained in:
Tom Wright
2022-03-18 15:29:17 +00:00
committed by GitHub
7 changed files with 431 additions and 8 deletions

View File

@@ -7,7 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
- Nothing yet.
### Added
- `Node.NewFromFile` func to load a root node from a file.
- `Node.NewFromReader` func to load a root node from an `io.Reader`.
- `Node.WriteToFile` func to write results to a file.
- `Node.Write` func to write results to an `io.Writer`.
## [v1.23.0] - 2022-03-10

View File

@@ -12,7 +12,7 @@ func init() {
registerWriteParser([]string{"csv"}, []string{".csv"}, &CSVParser{})
}
// CSVParser is a Parser implementation to handle yaml files.
// CSVParser is a Parser implementation to handle csv files.
type CSVParser struct {
}

View File

@@ -12,7 +12,7 @@ func init() {
registerWriteParser([]string{"json"}, []string{".json"}, &JSONParser{})
}
// JSONParser is a Parser implementation to handle yaml files.
// JSONParser is a Parser implementation to handle json files.
type JSONParser struct {
}

View File

@@ -9,7 +9,7 @@ func init() {
registerWriteParser([]string{"-", "plain"}, []string{}, &PlainParser{})
}
// PlainParser is a Parser implementation to handle yaml files.
// PlainParser is a Parser implementation to handle plain files.
type PlainParser struct {
}

View File

@@ -20,7 +20,7 @@ func init() {
registerWriteParser([]string{"xml"}, []string{".xml"}, &XMLParser{})
}
// XMLParser is a Parser implementation to handle yaml files.
// XMLParser is a Parser implementation to handle xml files.
type XMLParser struct {
}

69
node.go
View File

@@ -2,9 +2,12 @@ package dasel
import (
"fmt"
"github.com/tomwright/dasel/internal/storage"
"io"
"os"
"reflect"
"regexp"
"github.com/tomwright/dasel/internal/storage"
)
// Selector represents the selector for a node.
@@ -129,6 +132,70 @@ func New(value interface{}) *Node {
return rootNode
}
// NewFromFile returns a new root node by parsing file using specified read parser.
func NewFromFile(filename, parser string) (*Node, error) {
readParser, err := storage.NewReadParserFromString(parser)
if err != nil {
return nil, err
}
data, err := storage.LoadFromFile(filename, readParser)
if err != nil {
return nil, err
}
return New(data), nil
}
// NewFromReader returns a new root node by parsing from Reader using specified read parser.
func NewFromReader(reader io.Reader, parser string) (*Node, error) {
readParser, err := storage.NewReadParserFromString(parser)
if err != nil {
return nil, err
}
data, err := storage.Load(readParser, reader)
if err != nil {
return nil, err
}
return New(data), nil
}
// WriteToFile writes data to the given file with the specified options.
func (n *Node) WriteToFile(filename, parser string, writeOptions []storage.ReadWriteOption) error {
f, err := os.Create(filename)
if err != nil {
return err
}
// https://www.joeshaw.org/dont-defer-close-on-writable-files/
if err = n.Write(f, parser, writeOptions); err != nil {
_ = f.Close()
return err
}
return f.Close()
}
// Write writes data to Writer using specified write parser and options.
func (n *Node) Write(writer io.Writer, parser string, writeOptions []storage.ReadWriteOption) error {
writeParser, err := storage.NewWriteParserFromString(parser)
if err != nil {
return err
}
value := n.InterfaceValue()
originalValue := n.OriginalValue
if err := storage.Write(writeParser, value, originalValue, writer, writeOptions...); err != nil {
return err
}
return nil
}
func (n *Node) setValue(newValue interface{}) {
n.Value = reflect.ValueOf(newValue)
if n.Selector.Type == "ROOT" {

View File

@@ -1,13 +1,16 @@
package dasel_test
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
"testing"
"github.com/tomwright/dasel"
"github.com/tomwright/dasel/internal/storage"
"reflect"
"testing"
)
// ExampleNode_ReadmeExample tests the code from the readme explanation.
@@ -1312,3 +1315,351 @@ func TestNode_DeleteMultiple_Query(t *testing.T) {
}), ".", []interface{}{}))
t.Run("RootNodeUnknown", deleteMultipleTest(dasel.New(false), ".", map[string]interface{}{}))
}
// TestNode_NewFromFile tests parsing from file.
func TestNode_NewFromFile(t *testing.T) {
noSuchFileName := "no_such_file"
noSuchFileErrMsg := "could not open file: open " + noSuchFileName
unmarshalFailMsg := "could not unmarshal data"
tests := []struct {
name string
file string
parser string
err error
}{
{
name: "Existing JSON file and JSON Read parser specified",
file: "./tests/assets/example.json",
parser: "json",
err: nil,
},
{
name: "Existing JSON file and YAML Read parser specified",
file: "./tests/assets/example.json",
parser: "yaml",
// JSON is a subset of YAML v1.2
// https://stackoverflow.com/questions/21584985/what-valid-json-files-are-not-valid-yaml-1-1-files
err: nil,
},
{
name: "Existing YAML file and YAML Read parser specified",
file: "./tests/assets/example.yaml",
parser: "yaml",
err: nil,
},
{
name: "Existing YAML file and XML Read parser specified",
file: "./tests/assets/example.yaml",
parser: "xml",
err: errors.New(unmarshalFailMsg),
},
{
name: "Existing XML file and XML Read parser specified",
file: "./tests/assets/example.xml",
parser: "xml",
err: nil,
},
{
name: "Existing XML file and JSON Read parser specified",
file: "./tests/assets/example.xml",
parser: "json",
err: errors.New(unmarshalFailMsg),
},
{
name: "Existing JSON file and invalid Read parser specified",
file: "./tests/assets/example.json",
parser: "bad",
err: storage.UnknownParserErr{Parser: "bad"},
},
{
name: "File doesn't exist and JSON Read parser specified",
file: noSuchFileName,
parser: "json",
err: errors.New(noSuchFileErrMsg),
},
{
name: "File doesn't exist and YAML Read parser specified",
file: noSuchFileName,
parser: "yaml",
err: errors.New(noSuchFileErrMsg),
},
{
name: "File doesn't exist and YML Read parser specified",
file: noSuchFileName,
parser: "yml",
err: errors.New(noSuchFileErrMsg),
},
{
name: "File doesn't exist and TOML Read parser specified",
file: noSuchFileName,
parser: "toml",
err: errors.New(noSuchFileErrMsg),
},
{
name: "File doesn't exist and XML Read parser specified",
file: noSuchFileName,
parser: "xml",
err: errors.New(noSuchFileErrMsg),
},
{
name: "File doesn't exist and CSV Read parser specified",
file: noSuchFileName,
parser: "csv",
err: errors.New(noSuchFileErrMsg),
},
{
name: "File doesn't exist and invalid ('bad') Read parser specified",
file: noSuchFileName,
parser: "bad",
err: storage.UnknownParserErr{Parser: "bad"},
},
{
name: "File doesn't exist and invalid ('-') Read parser specified",
file: noSuchFileName,
parser: "-",
err: storage.UnknownParserErr{Parser: "-"},
},
}
for _, testCase := range tests {
tc := testCase
t.Run(tc.name, func(t *testing.T) {
_, err := dasel.NewFromFile(tc.file, tc.parser)
if tc.err == nil && err != nil {
t.Errorf("expected err %v, got %v", tc.err, err)
return
}
if tc.err != nil && err == nil {
t.Errorf("expected err %v, got %v", tc.err, err)
return
}
if tc.err != nil && err != nil && !strings.Contains(err.Error(), tc.err.Error()) {
t.Errorf("expected err %v, got %v", tc.err, err)
return
}
})
}
}
// TestNode_Write tests writing to Writer.
func TestNode_Write(t *testing.T) {
type testData = map[string]interface{}
type args struct {
parser string
compact bool
escapeHTML bool
}
commonData := testData{">": "&"}
xmlData := testData{
"key": testData{
"value": "<&>",
},
}
xmlEscapedData := testData{
"key": testData{
"value": "&lt;&amp;&gt;",
},
}
tests := []struct {
name string
data testData
args args
wantWriter string
wantErr bool
}{
{
name: "JSON, compact, no escape",
data: commonData,
args: args{
parser: "json",
compact: true,
escapeHTML: false,
},
wantWriter: "{\">\":\"&\"}\n",
wantErr: false,
},
{
name: "JSON, pretty, escape",
data: commonData,
args: args{
parser: "json",
compact: false,
escapeHTML: true,
},
wantWriter: "{\n \"\\u003e\": \"\\u0026\"\n}\n",
wantErr: false,
},
{
name: "YAML, compact, no escape",
data: commonData,
args: args{
parser: "yaml",
compact: true,
escapeHTML: false,
},
wantWriter: "'>': '&'\n",
wantErr: false,
},
{
name: "YAML, pretty, escape",
data: commonData,
args: args{
parser: "yaml",
compact: false,
escapeHTML: true,
},
wantWriter: "'>': '&'\n",
wantErr: false,
},
{
name: "YML, compact, no escape",
data: commonData,
args: args{
parser: "yml",
compact: true,
escapeHTML: false,
},
wantWriter: "'>': '&'\n",
wantErr: false,
},
{
name: "YML, pretty, escape",
data: commonData,
args: args{
parser: "yml",
compact: false,
escapeHTML: true,
},
wantWriter: "'>': '&'\n",
wantErr: false,
},
{
name: "TOML, compact, no escape",
data: commonData,
args: args{
parser: "toml",
compact: true,
escapeHTML: false,
},
wantWriter: "\">\" = \"&\"\n",
wantErr: false,
},
{
name: "TOML, pretty, escape",
data: commonData,
args: args{
parser: "toml",
compact: false,
escapeHTML: true,
},
wantWriter: "\">\" = \"&\"\n",
wantErr: false,
},
{
name: "PLAIN, pretty, escape",
data: commonData,
args: args{
parser: "plain",
compact: false,
escapeHTML: true,
},
wantWriter: "map[>:&]\n",
wantErr: false,
},
{
name: "PLAIN, pretty, no escape",
data: commonData,
args: args{
parser: "plain",
compact: false,
escapeHTML: false,
},
wantWriter: "map[>:&]\n",
wantErr: false,
},
{
name: "-, pretty, escape",
data: commonData,
args: args{
parser: "-",
compact: false,
escapeHTML: true,
},
wantWriter: "map[>:&]\n",
wantErr: false,
},
{
name: "-, pretty, no escape",
data: commonData,
args: args{
parser: "-",
compact: false,
escapeHTML: false,
},
wantWriter: "map[>:&]\n",
wantErr: false,
},
{
name: "XML, pretty, no escape",
data: xmlData,
args: args{
parser: "xml",
compact: false,
escapeHTML: false,
},
// Invalid XML, but since we were asked not to escape,
// this is probably the best we should do
wantWriter: "<key>\n <value><&></value>\n</key>\n",
wantErr: false,
},
{
name: "Pre-escaped XML, pretty, no escape",
data: xmlEscapedData,
args: args{
parser: "xml",
compact: false,
escapeHTML: false,
},
wantWriter: "<key>\n <value>&lt;&amp;&gt;</value>\n</key>\n",
wantErr: false,
},
{
name: "Invalid Write parser: bad",
data: commonData,
args: args{
parser: "bad",
compact: false,
escapeHTML: false,
},
wantWriter: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
writer := &bytes.Buffer{}
node := dasel.New(tt.data)
if err := node.Write(writer, tt.args.parser, []storage.ReadWriteOption{
storage.EscapeHTMLOption(tt.args.escapeHTML),
storage.PrettyPrintOption(!tt.args.compact),
}); (err != nil) != tt.wantErr {
t.Errorf("Node.Write() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotWriter := writer.String(); gotWriter != tt.wantWriter {
t.Errorf("Node.Write() = %v want %v", gotWriter, tt.wantWriter)
}
})
}
}