1
0
mirror of https://github.com/TomWright/dasel.git synced 2022-05-22 02:32:45 +03:00
Files
dasel-data-selector/storage/parser.go
2022-03-28 13:46:44 +01:00

199 lines
5.5 KiB
Go

package storage
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
var readParsersByExtension = map[string]ReadParser{}
var writeParsersByExtension = map[string]WriteParser{}
var readParsersByName = map[string]ReadParser{}
var writeParsersByName = map[string]WriteParser{}
func registerReadParser(names []string, extensions []string, parser ReadParser) {
for _, n := range names {
readParsersByName[n] = parser
}
for _, e := range extensions {
readParsersByExtension[e] = parser
}
}
func registerWriteParser(names []string, extensions []string, parser WriteParser) {
for _, n := range names {
writeParsersByName[n] = parser
}
for _, e := range extensions {
writeParsersByExtension[e] = parser
}
}
// UnknownParserErr is returned when an invalid parser name is given.
type UnknownParserErr struct {
Parser string
}
// Error returns the error message.
func (e UnknownParserErr) Error() string {
return fmt.Sprintf("unknown parser: %s", e.Parser)
}
// ReadParser can be used to convert bytes to data.
type ReadParser interface {
// FromBytes returns some data that is represented by the given bytes.
FromBytes(byteData []byte) (interface{}, error)
}
// WriteParser can be used to convert data to bytes.
type WriteParser interface {
// ToBytes returns a slice of bytes that represents the given value.
ToBytes(value interface{}, options ...ReadWriteOption) ([]byte, error)
}
// Parser can be used to load and save files from/to disk.
type Parser interface {
ReadParser
WriteParser
}
// NewReadParserFromFilename returns a ReadParser from the given filename.
func NewReadParserFromFilename(filename string) (ReadParser, error) {
ext := strings.ToLower(filepath.Ext(filename))
p, ok := readParsersByExtension[ext]
if !ok {
return nil, &UnknownParserErr{Parser: ext}
}
return p, nil
}
// NewReadParserFromString returns a ReadParser from the given parser name.
func NewReadParserFromString(parser string) (ReadParser, error) {
p, ok := readParsersByName[parser]
if !ok {
return nil, &UnknownParserErr{Parser: parser}
}
return p, nil
}
// NewWriteParserFromFilename returns a WriteParser from the given filename.
func NewWriteParserFromFilename(filename string) (WriteParser, error) {
ext := strings.ToLower(filepath.Ext(filename))
p, ok := writeParsersByExtension[ext]
if !ok {
return nil, &UnknownParserErr{Parser: ext}
}
return p, nil
}
// NewWriteParserFromString returns a WriteParser from the given parser name.
func NewWriteParserFromString(parser string) (WriteParser, error) {
p, ok := writeParsersByName[parser]
if !ok {
return nil, &UnknownParserErr{Parser: parser}
}
return p, nil
}
// LoadFromFile loads data from the given file.
func LoadFromFile(filename string, p ReadParser) (interface{}, error) {
f, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("could not open file: %w", err)
}
return Load(p, f)
}
// Load loads data from the given io.Reader.
func Load(p ReadParser, reader io.Reader) (interface{}, error) {
byteData, err := io.ReadAll(reader)
if err != nil {
return nil, fmt.Errorf("could not read data: %w", err)
}
return p.FromBytes(byteData)
}
// Write writes the value to the given io.Writer.
func Write(p WriteParser, value interface{}, originalValue interface{}, writer io.Writer, options ...ReadWriteOption) error {
switch typed := originalValue.(type) {
case OriginalRequired:
if typed.OriginalRequired() {
value = originalValue
}
}
byteData, err := p.ToBytes(value, options...)
if err != nil {
return fmt.Errorf("could not get byte data for file: %w", err)
}
if _, err := writer.Write(byteData); err != nil {
return fmt.Errorf("could not write data: %w", err)
}
return nil
}
// OriginalRequired can be used in conjunction with RealValue to allow parsers to be more intelligent
// with the data they read/write.
type OriginalRequired interface {
// OriginalRequired tells dasel if the parser requires the original value when converting to bytes.
OriginalRequired() bool
}
// RealValue can be used in conjunction with OriginalRequired to allow parsers to be more intelligent
// with the data they read/write.
type RealValue interface {
// RealValue returns the real value that dasel should use when processing data.
RealValue() interface{}
}
type originalRequired struct {
}
// OriginalRequired tells dasel if the parser requires the original value when converting to bytes.
func (d originalRequired) OriginalRequired() bool {
return true
}
// SingleDocument is a parser result that contains a single document.
type SingleDocument interface {
Document() interface{}
}
// MultiDocument is a parser result that contains multiple documents.
type MultiDocument interface {
Documents() []interface{}
}
// BasicSingleDocument represents a single document file.
type BasicSingleDocument struct {
originalRequired
Value interface{}
}
// RealValue returns the real value that dasel should use when processing data.
func (d *BasicSingleDocument) RealValue() interface{} {
return d.Value
}
// Document returns the document that should be written to output.
func (d *BasicSingleDocument) Document() interface{} {
return d.Value
}
// BasicMultiDocument represents a multi-document file.
type BasicMultiDocument struct {
originalRequired
Values []interface{}
}
// RealValue returns the real value that dasel should use when processing data.
func (d *BasicMultiDocument) RealValue() interface{} {
return d.Values
}
// Documents returns the documents that should be written to output.
func (d *BasicMultiDocument) Documents() []interface{} {
return d.Values
}