dati

A Go library/binary to parse & execute data against template langauges.
git clone git://src.gearsix.net/dati
Log | Files | Refs | Atom | README | LICENSE

data.go (4099B)


      1 package dati
      2 
      3 /*
      4 Copyright (C) 2023 gearsix <gearsix@tuta.io>
      5 
      6 This program is free software: you can redistribute it and/or modify
      7 it under the terms of the GNU General Public License as published by
      8 the Free Software Foundation, either version 3 of the License, or
      9 at your option) any later version.
     10 
     11 This program is distributed in the hope that it will be useful,
     12 but WITHOUT ANY WARRANTY; without even the implied warranty of
     13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14 GNU General Public License for more details.
     15 
     16 You should have received a copy of the GNU General Public License
     17 along with this program.  If not, see <https://www.gnu.org/licenses/>.
     18 */
     19 
     20 import (
     21 	"encoding/json"
     22 	"fmt"
     23 	"io"
     24 	"io/ioutil"
     25 	"os"
     26 	"path/filepath"
     27 	"strings"
     28 
     29 	"github.com/pelletier/go-toml"
     30 	"gopkg.in/yaml.v3"
     31 )
     32 
     33 // DataFormat provides a list of supported languages for
     34 // data files (lower-case)
     35 type DataFormat string
     36 
     37 // String returns the typical file extension used to
     38 // represent `df`
     39 func (df DataFormat) String() string {
     40 	return string(df)
     41 }
     42 
     43 const (
     44 	JSON DataFormat = "json"
     45 	YAML DataFormat = "yaml"
     46 	TOML DataFormat = "toml"
     47 )
     48 
     49 var ErrUnsupportedData = func(format string) error {
     50 	return fmt.Errorf("data format '%s' is not supported", format)
     51 }
     52 
     53 // IsDataFile checks if `path` is one of the known *DatFormat*s.
     54 func IsDataFormat(path string) bool {
     55 	return ReadDataFormat(path) != ""
     56 }
     57 
     58 // ReadDataFormat returns the *DataFormat* that the file
     59 // extension of `path` matches. If the file extension of `path` does
     60 // not match any *DataFormat*, then an "" is returned.
     61 func ReadDataFormat(path string) DataFormat {
     62 	if len(path) == 0 {
     63 		return ""
     64 	}
     65 
     66 	ext := filepath.Ext(path)
     67 	if len(ext) == 0 {
     68 		ext = path // assume `path` the name of the format
     69 	}
     70 
     71 	ext = strings.ToLower(ext)
     72 	if len(ext) > 0 && ext[0] == '.' {
     73 		ext = ext[1:]
     74 	}
     75 
     76 	for _, fmt := range []DataFormat{JSON, YAML, TOML} {
     77 		if fmt.String() == ext {
     78 			return fmt
     79 		}
     80 	}
     81 	return ""
     82 }
     83 
     84 // LoadData attempts to load all data from `in` as `format` and writes
     85 // the result in the pointer `out`.
     86 func LoadData(format DataFormat, in io.Reader, out interface{}) error {
     87 	inbuf, err := ioutil.ReadAll(in)
     88 	if err != nil {
     89 		return err
     90 	} else if len(inbuf) == 0 {
     91 		return nil
     92 	}
     93 
     94 	switch format {
     95 	case JSON:
     96 		if err = json.Unmarshal(inbuf, out); err != nil {
     97 			err = fmt.Errorf("%s: %s", JSON, err.Error())
     98 		}
     99 	case YAML:
    100 		err = yaml.Unmarshal(inbuf, out)
    101 	case TOML:
    102 		if err = toml.Unmarshal(inbuf, out); err != nil {
    103 			err = fmt.Errorf("%s %s", TOML, err.Error())
    104 		}
    105 	default:
    106 		err = ErrUnsupportedData(format.String())
    107 	}
    108 
    109 	return err
    110 }
    111 
    112 // LoadDataFile loads all the data from the file found at `path` into
    113 // the the format of that files extension (e.g. "x.json" will be loaded
    114 // as a json). The result is written to the value pointed at by `outp`.
    115 func LoadDataFile(path string, outp interface{}) error {
    116 	file, err := os.Open(path)
    117 	if err != nil {
    118 		return err
    119 	}
    120 	defer file.Close()
    121 
    122 	return LoadData(ReadDataFormat(path), file, outp)
    123 }
    124 
    125 // WriteData attempts to write `data` as `format` to `outp`.
    126 func WriteData(format DataFormat, data interface{}, w io.Writer) error {
    127 	var err error
    128 
    129 	switch format {
    130 	case JSON:
    131 		if err = json.NewEncoder(w).Encode(data); err != nil {
    132 			err = fmt.Errorf("%s: %s", JSON, err.Error())
    133 		}
    134 	case YAML:
    135 		err = yaml.NewEncoder(w).Encode(data)
    136 	case TOML:
    137 		if err = toml.NewEncoder(w).Encode(data); err != nil {
    138 			err = fmt.Errorf("%s %s", TOML, err.Error())
    139 		}
    140 	default:
    141 		err = ErrUnsupportedData(format.String())
    142 	}
    143 
    144 	return err
    145 }
    146 
    147 // WriteDataFile attempts to write `data` as `format` to the file at `path`.
    148 // If `force` is *true*, then any existing files will be overwritten.
    149 func WriteDataFile(format DataFormat, data interface{}, path string, force bool) (f *os.File, err error) {
    150 	if f, err = os.Open(path); force || os.IsNotExist(err) {
    151 		if force {
    152 			f.Close()
    153 		}
    154 		f, err = os.Create(path)
    155 	} else if !force {
    156 		err = os.ErrExist
    157 	}
    158 
    159 	if err != nil  {
    160 		return
    161 	}
    162 
    163 	if err = WriteData(format, data, f); err != nil {
    164 		f = nil
    165 	}
    166 
    167 	return
    168 }