package main import ( "io/ioutil" "os" "path" yaml "gopkg.in/yaml.v2" ) type Branch struct { Leaf Leaf Branches map[string]Branch } func (branch Branch) IsZero() bool { return branch.Leaf == (Leaf{}) && len(branch.Branches) == 0 } func (branch Branch) Find(baseId string) ([]string, bool) { if _, ok := branch.Branches[baseId]; ok { return []string{baseId}, true } for pid, child := range branch.Branches { if subids, ok := child.Find(baseId); ok { return append([]string{pid}, subids...), true } } return nil, false } type Leaf struct { Title string Deleted bool Content string } func (base Leaf) Merge(updated Leaf) Leaf { if updated.Title != "" { base.Title = updated.Title } base.Deleted = updated.Deleted base.Content = updated.Content return base } type Tree struct { root string cachedRoot Branch } func NewTree(root string) Tree { return Tree{root: root} } func (tree Tree) WithRoot(root string) Tree { tree.root = root return tree } func (tree Tree) Find(baseId string) ([]string, bool) { root, err := tree.GetRoot() if err != nil { return nil, false } return root.Find(baseId) } func (tree Tree) GetRootMeta() (Branch, error) { return tree.getRoot(false, false) } func (tree Tree) GetRoot() (Branch, error) { if !tree.cachedRoot.IsZero() { return tree.cachedRoot, nil } got, err := tree.getRoot(true, false) if err == nil { tree.cachedRoot = got } return got, err } func (tree Tree) getRoot(withContent, withDeleted bool) (Branch, error) { m := Branch{Branches: map[string]Branch{}} entries, err := os.ReadDir(tree.root) if os.IsNotExist(err) { return m, nil } if err != nil { return Branch{}, err } 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 } if !withContent { m.Leaf.Content = "" } if m.Leaf.Deleted && !withDeleted { return m, nil } } else if entry.IsDir() { subtree := tree.WithRoot(path.Join(tree.root, entry.Name())) if branch, err := subtree.getRoot(withContent, withDeleted); err != nil { return Branch{}, err } else if !branch.IsZero() && (!branch.Leaf.Deleted || withDeleted) { m.Branches[entry.Name()] = branch } } } return m, nil } 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 } b, err := yaml.Marshal(old.Merge(input)) if err != nil { return err } if err := ensureAndWrite(tree.toData(id), b); err != nil { return err } tree.cachedRoot = Branch{} return nil } func (tree Tree) Del(id []string) error { got, err := tree.Get(id) if os.IsNotExist(err) { return nil } if err != nil { return err } if got.Deleted { return nil } got.Deleted = true return tree.Put(id, got) } func (tree Tree) HardDel(id []string) error { os.RemoveAll(tree.toDir(id)) tree.cachedRoot = Branch{} 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 }