new tree crud test
This commit is contained in:
@@ -1,190 +1,130 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var errChainNotFound = errors.New("pid chain not found")
|
||||
|
||||
type Tree struct {
|
||||
root string
|
||||
type Branch struct {
|
||||
Leaf Leaf
|
||||
Branches map[string]Branch
|
||||
}
|
||||
|
||||
type Branch struct {
|
||||
func (branch Branch) IsZero() bool {
|
||||
return branch.Leaf == (Leaf{}) && len(branch.Branches) == 0
|
||||
}
|
||||
|
||||
type Leaf struct {
|
||||
Title string
|
||||
Deleted bool
|
||||
Updated time.Time
|
||||
PID string
|
||||
Content string
|
||||
}
|
||||
|
||||
type Pretty struct {
|
||||
Children map[string]*Pretty
|
||||
Title string
|
||||
}
|
||||
|
||||
func NewTree(root string) *Tree {
|
||||
return &Tree{
|
||||
root: root,
|
||||
func (base Leaf) Merge(updated Leaf) Leaf {
|
||||
if updated.Title != "" {
|
||||
base.Title = updated.Title
|
||||
}
|
||||
base.Deleted = updated.Deleted
|
||||
base.Content = updated.Content
|
||||
return base
|
||||
}
|
||||
|
||||
func (tree *Tree) GetPretty() (map[string]*Pretty, error) {
|
||||
branches, err := tree.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = branches
|
||||
realpretty := map[string]*Pretty{}
|
||||
lookuppretty := map[string]*Pretty{}
|
||||
timesZero := 0
|
||||
for len(branches) > 0 && timesZero < 3 {
|
||||
topop := []string{}
|
||||
for id, branch := range branches {
|
||||
if branch.Deleted {
|
||||
} else if branch.PID == "" {
|
||||
realpretty[id] = &Pretty{
|
||||
Title: branch.Title,
|
||||
Children: map[string]*Pretty{},
|
||||
}
|
||||
lookuppretty[id] = realpretty[id]
|
||||
} else if ptr, ok := lookuppretty[branch.PID]; ok {
|
||||
ptr.Children[id] = &Pretty{
|
||||
Title: branch.Title,
|
||||
Children: map[string]*Pretty{},
|
||||
}
|
||||
lookuppretty[id] = ptr.Children[id]
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
topop = append(topop, id)
|
||||
}
|
||||
for _, id := range topop {
|
||||
delete(branches, id)
|
||||
}
|
||||
if len(topop) == 0 {
|
||||
timesZero++
|
||||
} else {
|
||||
timesZero = 0
|
||||
}
|
||||
}
|
||||
return realpretty, nil
|
||||
type Tree struct {
|
||||
root string
|
||||
cachedRoot Branch
|
||||
}
|
||||
|
||||
func (tree *Tree) Get() (map[string]Branch, error) {
|
||||
m := map[string]Branch{}
|
||||
err := filepath.Walk(tree.root, func(p string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if info.Name() != "meta.yaml" {
|
||||
return nil
|
||||
}
|
||||
b, err := ioutil.ReadFile(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var branch Branch
|
||||
if err := yaml.Unmarshal(b, &branch); err != nil {
|
||||
return err
|
||||
}
|
||||
id := path.Base(path.Dir(p))
|
||||
pidFullPath := path.Dir(path.Dir(p))
|
||||
if pidFullPath != tree.root {
|
||||
branch.PID = path.Base(pidFullPath)
|
||||
} else {
|
||||
branch.PID = ""
|
||||
}
|
||||
m[id] = branch
|
||||
return nil
|
||||
})
|
||||
func NewTree(root string) Tree {
|
||||
return Tree{root: root}
|
||||
}
|
||||
|
||||
func (tree Tree) WithRoot(root string) Tree {
|
||||
tree.root = root
|
||||
return tree
|
||||
}
|
||||
|
||||
func (tree Tree) GetRoot() (Branch, error) {
|
||||
if !tree.cachedRoot.IsZero() {
|
||||
return tree.cachedRoot, nil
|
||||
}
|
||||
got, err := tree.getRoot()
|
||||
if err == nil {
|
||||
tree.cachedRoot = got
|
||||
}
|
||||
return got, err
|
||||
}
|
||||
|
||||
func (tree Tree) getRoot() (Branch, error) {
|
||||
m := Branch{Branches: map[string]Branch{}}
|
||||
entries, err := os.ReadDir(tree.root)
|
||||
if os.IsNotExist(err) {
|
||||
return m, nil
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
func (tree *Tree) FullId(id string) (string, error) {
|
||||
fullId, _, err := tree.fullIdAndMeta(id)
|
||||
return fullId, err
|
||||
}
|
||||
|
||||
func (tree *Tree) fullIdAndMeta(id string) (string, Branch, error) {
|
||||
m, err := tree.Get()
|
||||
if err != nil {
|
||||
return "", Branch{}, err
|
||||
return Branch{}, err
|
||||
}
|
||||
branch, ok := m[id]
|
||||
if !ok {
|
||||
return "", Branch{}, errChainNotFound
|
||||
}
|
||||
p := id
|
||||
for branch.PID != "" {
|
||||
p = path.Join(branch.PID, p)
|
||||
branch, ok = m[branch.PID]
|
||||
if !ok {
|
||||
return "", Branch{}, errChainNotFound
|
||||
for _, entry := range entries {
|
||||
if entry.Name() == "data.yaml" {
|
||||
if b, err := ioutil.ReadFile(path.Join(tree.root, entry.Name())); err != nil {
|
||||
return Branch{}, err
|
||||
} else if err := yaml.Unmarshal(b, &m.Leaf); err != nil {
|
||||
return Branch{}, err
|
||||
}
|
||||
} else if entry.IsDir() {
|
||||
if branch, err := tree.WithRoot(path.Join(tree.root, entry.Name())).getRoot(); err != nil {
|
||||
return Branch{}, err
|
||||
} else {
|
||||
m.Branches[entry.Name()] = branch
|
||||
}
|
||||
}
|
||||
}
|
||||
return path.Join(tree.root, p), branch, nil
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (tree *Tree) putMeta(fullId string, branch Branch) error {
|
||||
b, err := yaml.Marshal(branch)
|
||||
func (tree Tree) toDir(id []string) string {
|
||||
return path.Dir(tree.toData(id))
|
||||
}
|
||||
|
||||
func (tree Tree) toData(id []string) string {
|
||||
return path.Join(tree.root, path.Join(id...), "data.yaml")
|
||||
}
|
||||
|
||||
func (tree Tree) Put(id []string, input Leaf) error {
|
||||
if _, err := os.Stat(tree.toData(id)); os.IsNotExist(err) {
|
||||
b, err := yaml.Marshal(Leaf{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureAndWrite(tree.toData(id), b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
old, err := tree.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ensureAndWrite(path.Join(fullId, "meta.yaml"), b)
|
||||
}
|
||||
|
||||
func (tree *Tree) Del(id string) error {
|
||||
fullId, meta, err := tree.fullIdAndMeta(id)
|
||||
if err != nil {
|
||||
if err == errChainNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
meta.Updated = time.Now().UTC()
|
||||
meta.Deleted = true
|
||||
|
||||
return tree.putMeta(fullId, meta)
|
||||
}
|
||||
|
||||
func (tree *Tree) Put(id string, branch Branch) error {
|
||||
fullId, meta, err := tree.fullIdAndMeta(id)
|
||||
if err == errChainNotFound {
|
||||
if branch.PID != "" {
|
||||
fullId, _, err = tree.fullIdAndMeta(branch.PID)
|
||||
fullId = path.Join(fullId, id)
|
||||
} else {
|
||||
err = nil
|
||||
fullId = path.Join(tree.root, fullId, id)
|
||||
}
|
||||
}
|
||||
b, err := yaml.Marshal(old.Merge(input))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
meta.Updated = time.Now().UTC()
|
||||
if meta.Title == "" {
|
||||
meta.Title = "Untitled (" + time.Now().UTC().String() + ")"
|
||||
}
|
||||
if branch.Title != "" {
|
||||
meta.Title = branch.Title
|
||||
}
|
||||
meta.PID = branch.PID
|
||||
meta.Deleted = branch.Deleted
|
||||
|
||||
return tree.putMeta(fullId, meta)
|
||||
return ensureAndWrite(tree.toData(id), b)
|
||||
}
|
||||
|
||||
func (tree Tree) Del(id []string) error {
|
||||
os.RemoveAll(tree.toDir(id))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tree Tree) Get(id []string) (Leaf, error) {
|
||||
f, err := os.Open(tree.toData(id))
|
||||
if err != nil {
|
||||
return Leaf{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
var got Leaf
|
||||
err = yaml.NewDecoder(f).Decode(&got)
|
||||
return got, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user