Select Git revision
principal.go
assets.go 3.75 KiB
package perxis
import (
"io"
"io/fs"
"path/filepath"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"git.perx.ru/perxis/perxis-go/yaml"
jsoniter "github.com/json-iterator/go"
)
// Assets предоставляет методы для загрузки данных из файловой системы
type Assets[T any] struct {
FS fs.FS
Constructor func() T
}
// NewAssets возвращает новый экземпляр загрузчика
func NewAssets[T any](fsys fs.FS) *Assets[T] {
return &Assets[T]{
FS: fsys,
Constructor: func() (t T) { return t }, // По умолчанию zero-value
}
}
// WithConstructor устанавливает конструктор для создания новых экземпляров
func (a *Assets[T]) WithConstructor(t func() T) *Assets[T] {
a.Constructor = t
return a
}
// MustFrom возвращает все записи из переданного файла или директории
func (a *Assets[T]) MustFrom(path string) []T {
res, err := a.From(path)
if err != nil {
panic(err)
}
return res
}
// MustOneFrom возвращает одну запись из переданного файла или директории
func (a *Assets[T]) MustOneFrom(path string) T {
res, err := a.From(path)
if err != nil {
panic(err)
}
if len(res) == 0 {
panic(errors.Errorf("no entries found"))
}
if len(res) > 1 {
panic(errors.Errorf("multiple entries found"))
}
return res[0]
}
// From возвращает записи из переданного файла или директории
func (a *Assets[T]) From(path string) ([]T, error) {
f, err := a.FS.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
stat, err := f.Stat()
if err != nil {
return nil, err
}
if stat.IsDir() {
return a.FromDir(path)
}
return a.FromFile(f)
}
// FromFile возвращает записи в переданном файле
func (a *Assets[T]) FromFile(file fs.File) ([]T, error) {
stat, err := file.Stat()
if err != nil {
return nil, err
}
switch filepath.Ext(stat.Name()) {
case ".json":
entry, err := a.FromJSON(file)
if err != nil {
return nil, errors.Wrapf(err, "file '%s'", stat.Name())
}
return []T{entry}, nil
case ".yaml", ".yml":
entries, err := a.FromYAML(file)
return entries, errors.Wrapf(err, "file '%s'", stat.Name())
}
return nil, errors.Errorf("file '%s' must be in JSON or YAML format", stat.Name())
}
// FromDir возвращает все записи из переданной папки рекурсивно
func (a *Assets[T]) FromDir(dir string) (result []T, err error) {
if err := fs.WalkDir(a.FS, dir, func(path string, d fs.DirEntry, _ error) error {
file, err := a.FS.Open(path)
if err != nil {
return err
}
defer file.Close()
if entries, err := a.FromFile(file); err == nil {
result = append(result, entries...)
}
return nil
}); err != nil {
return nil, err
}
return result, nil
}
// FromJSON возвращает запись из JSON
func (a *Assets[T]) FromJSON(r io.Reader) (T, error) {
entry := a.Constructor()
data, err := io.ReadAll(r)
if err != nil {
return entry, err
}
err = jsoniter.Unmarshal(data, &entry)
return entry, err
}
// FromYAML возвращает записи из YAML
func (a *Assets[T]) FromYAML(r io.Reader) (result []T, err error) {
decoder := yaml.NewDecoder(r)
for {
var data interface{}
err = decoder.Decode(yaml.WithTagProcessor(a.FS)(&data))
if errors.Is(err, io.EOF) {
break
} if err != nil {
return nil, err
}
json, err := jsoniter.Marshal(data)
if err != nil {
return nil, err
}
entry := a.Constructor()
if err = jsoniter.Unmarshal(json, &entry); err != nil {
return nil, err
}
result = append(result, entry)
}
return result, nil
}