dati

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

data.go (raw) (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 }