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 }