From bc8a0d82dff23a1c3c12e5e3d8895ef75dbc6817 Mon Sep 17 00:00:00 2001 From: Bel LaPointe Date: Wed, 15 Dec 2021 06:53:28 -0700 Subject: [PATCH] yaml namespaces --- db_test.go | 6 +++ go.mod | 1 + yaml.go | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++ yaml_test.go | 51 ++++++++++++++++++++ 4 files changed, 187 insertions(+) create mode 100755 yaml.go create mode 100644 yaml_test.go diff --git a/db_test.go b/db_test.go index a3b0436..a30ee57 100755 --- a/db_test.go +++ b/db_test.go @@ -103,6 +103,12 @@ func TestImplementations(t *testing.T) { cases = append(cases, bolt) } + if yamls, err := NewYaml(path.Join(dir, "yaml")); err != nil { + t.Errorf("cannot make yaml: %v", err) + } else { + cases = append(cases, yamls) + } + if files, err := NewFiles(path.Join(dir, "files")); err != nil { t.Errorf("cannot make files: %v", err) } else { diff --git a/go.mod b/go.mod index 7eb0e15..b71097d 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible github.com/syndtr/goleveldb v1.0.0 go.mongodb.org/mongo-driver v1.7.2 + gopkg.in/yaml.v2 v2.2.8 // indirect local/logb v0.0.0-00010101000000-000000000000 ) diff --git a/yaml.go b/yaml.go new file mode 100755 index 0000000..b4c5193 --- /dev/null +++ b/yaml.go @@ -0,0 +1,129 @@ +package storage + +import ( + "bytes" + "errors" + "io" + "io/ioutil" + "os" + "path" + "path/filepath" + + yaml "gopkg.in/yaml.v2" +) + +const ( + yamlExt = ".yaml" +) + +type Yaml struct { + path string +} + +func NewYaml(p string) (*Yaml, error) { + _, err := os.Stat(path.Dir(p)) + if err != nil { + return nil, err + } + p, err = filepath.Abs(p) + if err != nil { + return nil, err + } + return &Yaml{ + path: p, + }, os.MkdirAll(path.Dir(p), os.ModePerm) +} + +func (y *Yaml) Namespaces() ([][]string, error) { + m, err := y.getMap() + if err != nil { + return nil, err + } + return keysDFS(m) +} + +func (y *Yaml) List(ns []string, limits ...string) ([]string, error) { + return nil, errors.New("not impl") +} + +func (y *Yaml) Get(key string, ns ...string) ([]byte, error) { + r, err := y.GetStream(key, ns...) + if err != nil { + return nil, err + } + return ioutil.ReadAll(r) +} + +func (y *Yaml) GetStream(key string, ns ...string) (io.Reader, error) { + return nil, errors.New("not impl") +} + +func (y *Yaml) Set(key string, value []byte, ns ...string) error { + r := bytes.NewReader(value) + if value == nil { + return y.Del(key, ns...) + } + return y.SetStream(key, r, ns...) +} + +func (y *Yaml) Del(key string, ns ...string) error { + return errors.New("not impl") +} + +func (y *Yaml) SetStream(key string, r io.Reader, ns ...string) error { + return errors.New("not impl") +} + +func (y *Yaml) Close() error { + return nil +} + +func (y *Yaml) getMap() (map[string]interface{}, error) { + b, err := y.get() + if err != nil { + return nil, err + } + var m map[string]interface{} + err = yaml.Unmarshal(b, &m) + return m, err +} + +func (y *Yaml) get() ([]byte, error) { + b, err := ioutil.ReadFile(y.path) + if err == os.ErrNotExist { + return []byte{}, nil + } + return b, err +} + +func (y *Yaml) set(b []byte) error { + return ioutil.WriteFile(y.path, b, os.ModePerm) +} + +func keysDFS(m map[string]interface{}) ([][]string, error) { + keys, _, err := _keysDFS(m) + return keys, err +} + +func _keysDFS(m map[string]interface{}) ([][]string, bool, error) { + keys := make([][]string, 0) + hasNonMaps := false + for k, v := range m { + if subm, ok := v.(map[string]interface{}); ok { + subkeys, hasElements, err := _keysDFS(subm) + if err != nil { + return nil, false, err + } + if hasElements { + subkeys = append(subkeys, []string{}) + } + for i := range subkeys { + subkeys[i] = append([]string{k}, subkeys[i]...) + keys = append(keys, subkeys[i]) + } + } else { + hasNonMaps = true + } + } + return keys, hasNonMaps, nil +} diff --git a/yaml_test.go b/yaml_test.go new file mode 100644 index 0000000..3236a61 --- /dev/null +++ b/yaml_test.go @@ -0,0 +1,51 @@ +package storage + +import ( + "fmt" + "testing" +) + +func TestKeysDFS(t *testing.T) { + cases := map[string]struct { + input map[string]interface{} + want [][]string + }{ + "empty": { + input: map[string]interface{}{}, + want: [][]string{}, + }, + "top level non map keys": { + input: map[string]interface{}{"a": "b", "c": "d"}, + want: [][]string{}, + }, + "top level non map keys and map key": { + input: map[string]interface{}{"a": "b", "c": "d", "e": map[string]interface{}{"f": "g"}}, + want: [][]string{[]string{"e"}}, + }, + "top level non map keys and map key and nested, ignore empty nested": { + input: map[string]interface{}{ + "a": "b", + "c": "d", + "e": map[string]interface{}{ + "f": map[string]interface{}{"g": "h"}, + }, + "i": map[string]interface{}{}, + "j": map[string]interface{}{"k": "l"}, + }, + want: [][]string{[]string{"e", "f"}, []string{"j"}}, + }, + } + + for name, d := range cases { + c := d + t.Run(name, func(t *testing.T) { + got, err := keysDFS(c.input) + if err != nil { + t.Fatal(err) + } + if fmt.Sprintf("%+v", got) != fmt.Sprintf("%+v", c.want) { + t.Fatalf("want: %+v\ngot: %+v", c.want, got) + } + }) + } +}