data.go 4.0 KB

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