diff --git a/hyphae/iteration.go b/hyphae/iteration.go
new file mode 100644
index 0000000..a33e9ac
--- /dev/null
+++ b/hyphae/iteration.go
@@ -0,0 +1,52 @@
+package hyphae
+
+import (
+ "sync"
+)
+
+// Iteration represents an iteration over all hyphae in the storage. You may use it instead of directly iterating using hyphae.YieldExistingHyphae when you want to do n checks at once instead of iterating n times.
+type Iteration struct {
+ sync.Mutex
+ iterator func() chan *Hypha
+ checks []func(h *Hypha) CheckResult
+}
+
+func NewIteration() *Iteration {
+ return &Iteration{
+ iterator: YieldExistingHyphae,
+ checks: make([]func(h *Hypha) CheckResult, 0),
+ }
+}
+
+// AddCheck adds the check to the iteration. It is concurrent-safe.
+func (i7n *Iteration) AddCheck(check func(h *Hypha) CheckResult) {
+ i7n.Lock()
+ i7n.checks = append(i7n.checks, check)
+ i7n.Unlock()
+}
+
+func (i7n *Iteration) removeCheck(i int) {
+ i7n.checks[i] = i7n.checks[len(i7n.checks)-1]
+ i7n.checks = i7n.checks[:len(i7n.checks)-1]
+}
+
+// Ignite does the iteration by walking over all hyphae yielded by the iterator used and calling all checks on the hypha. Ignited iterations are not concurrent-safe.
+func (i7n *Iteration) Ignite() {
+ for h := range i7n.iterator() {
+ for i, check := range i7n.checks {
+ if res := check(h); res == CheckForgetMe {
+ i7n.removeCheck(i)
+ }
+ }
+ }
+}
+
+// CheckResult is a result of an iteration check.
+type CheckResult int
+
+const (
+ // CheckContinue is returned when the check wants to be used next time too.
+ CheckContinue CheckResult = iota
+ // CheckForgetMe is returned when the check wants to be forgotten and not used anymore.
+ CheckForgetMe
+)
diff --git a/tree/tree.go b/tree/tree.go
index dc3cf38..d1fbf48 100644
--- a/tree/tree.go
+++ b/tree/tree.go
@@ -5,25 +5,131 @@ import (
"path"
"sort"
"strings"
+ "sync"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/util"
)
-type sibling struct {
- name string
- hasChildren bool
+func findSiblingsAndDescendants(hyphaName string) ([]*sibling, map[string]bool) {
+ var (
+ siblings = make([]*sibling, 0)
+ siblingCheck = func(h *hyphae.Hypha) hyphae.CheckResult {
+ if path.Dir(hyphaName) == path.Dir(h.Name) {
+ siblings = append(siblings, &sibling{h.Name, 0, 0})
+ }
+ return hyphae.CheckContinue
+ }
+
+ descendantsPool = make(map[string]bool, 0)
+ descendantCheck = func(h *hyphae.Hypha) hyphae.CheckResult {
+ if strings.HasPrefix(h.Name, hyphaName+"/") {
+ descendantsPool[h.Name] = true
+ }
+ return hyphae.CheckContinue
+ }
+
+ i7n = hyphae.NewIteration()
+ )
+ i7n.AddCheck(siblingCheck)
+ i7n.AddCheck(descendantCheck)
+ i7n.Ignite()
+ sort.Slice(siblings, func(i, j int) bool {
+ return siblings[i].name < siblings[j].name
+ })
+ return siblings, descendantsPool
}
-func (s *sibling) checkThisChild(hyphaName string) {
- if !s.hasChildren && path.Dir(hyphaName) == s.name {
- s.hasChildren = true
+func countSubhyphae(siblings []*sibling) {
+ var (
+ subhyphaCheck = func(h *hyphae.Hypha) hyphae.CheckResult {
+ for _, s := range siblings {
+ if path.Dir(h.Name) == s.name {
+ s.directSubhyphaeCount++
+ return hyphae.CheckContinue
+ } else if strings.HasPrefix(h.Name, s.name+"/") {
+ s.indirectSubhyphaeCount++
+ return hyphae.CheckContinue
+ }
+ }
+ return hyphae.CheckContinue
+ }
+ i7n = hyphae.NewIteration()
+ )
+ i7n.AddCheck(subhyphaCheck)
+ i7n.Ignite()
+}
+
+// Tree generates a tree for `hyphaName` as html and returns next and previous hyphae if any.
+func Tree(hyphaName string) (siblingsHTML, childrenHTML, prev, next string) {
+ children := make([]child, 0)
+ I := 0
+ // The tree is generated in two iterations of hyphae storage:
+ // 1. Find all siblings (sorted) and descendants' names
+ // 2. Count how many subhyphae siblings have
+ //
+ // We also have to figure out what is going on with the descendants: who is a child of whom. We do that in parallel with (2) because we can.
+ // One of the siblings is the hypha with name `hyphaName`
+ siblings, descendantsPool := findSiblingsAndDescendants(hyphaName)
+
+ wg := sync.WaitGroup{}
+ wg.Add(2)
+ go func() {
+ countSubhyphae(siblings)
+ wg.Done()
+ }()
+ go func() {
+ children = figureOutChildren(hyphaName, descendantsPool).children
+ wg.Done()
+ }()
+ wg.Wait()
+
+ for i, s := range siblings {
+ if s.name == hyphaName {
+ I = i
+ siblingsHTML += fmt.Sprintf(`
%s`, util.BeautifulName(path.Base(hyphaName)))
+ } else {
+ siblingsHTML += s.asHTML(hyphaName)
+ }
}
+ if I != 0 {
+ prev = siblings[I-1].name
+ }
+ if I != len(siblings)-1 {
+ next = siblings[I+1].name
+ }
+ return fmt.Sprintf(``, siblingsHTML), subhyphaeMatrix(children), prev, next
}
-func (s *sibling) asHTML() string {
+type child struct {
+ name string
+ children []child
+}
+
+func figureOutChildren(hyphaName string, subhyphaePool map[string]bool) child {
+ var (
+ nestLevel = strings.Count(hyphaName, "/")
+ adopted = make([]child, 0)
+ )
+ for subhyphaName, _ := range subhyphaePool {
+ subnestLevel := strings.Count(subhyphaName, "/")
+ if subnestLevel-1 == nestLevel && path.Dir(subhyphaName) == hyphaName {
+ delete(subhyphaePool, subhyphaName)
+ adopted = append(adopted, figureOutChildren(subhyphaName, subhyphaePool))
+ }
+ }
+ return child{hyphaName, adopted}
+}
+
+type sibling struct {
+ name string
+ directSubhyphaeCount int
+ indirectSubhyphaeCount int
+}
+
+func (s *sibling) asHTML(hyphaName string) string {
class := "navitree__entry navitree__sibling"
- if s.hasChildren {
+ if s.directSubhyphaeCount > 0 {
class += " navitree__sibling_fertile navitree__entry_fertile"
} else {
class += " navitree__sibling_infertile navitree__entry_infertile"
@@ -36,54 +142,21 @@ func (s *sibling) asHTML() string {
)
}
-type mainFamilyMember struct {
- name string
- children []*mainFamilyMember
-}
-
-func (m *mainFamilyMember) checkThisChild(hyphaName string) (adopted bool) {
- if path.Dir(hyphaName) == m.name {
- m.children = append(m.children, &mainFamilyMember{
- name: hyphaName,
- children: make([]*mainFamilyMember, 0),
- })
- return true
+func (c *child) asHTML() string {
+ if len(c.children) == 0 {
+ return fmt.Sprintf(`%s`, c.name, util.BeautifulName(path.Base(c.name)))
}
- return false
-}
-
-func (m *mainFamilyMember) asHTML() string {
- if len(m.children) == 0 {
- return fmt.Sprintf(`%s`, m.name, util.BeautifulName(path.Base(m.name)))
- }
- sort.Slice(m.children, func(i, j int) bool {
- return m.children[i].name < m.children[j].name
+ sort.Slice(c.children, func(i, j int) bool {
+ return c.children[i].name < c.children[j].name
})
- html := fmt.Sprintf(`%s`, m.name, util.BeautifulName(path.Base(m.name)))
- for _, child := range m.children {
+ html := fmt.Sprintf(`- %s
`, c.name, util.BeautifulName(path.Base(c.name)))
+ for _, child := range c.children {
html += child.asHTML()
}
return html + `
`
}
-func mainFamilyFromPool(hyphaName string, subhyphaePool map[string]bool) *mainFamilyMember {
- var (
- nestLevel = strings.Count(hyphaName, "/")
- adopted = make([]*mainFamilyMember, 0)
- )
- for subhyphaName, _ := range subhyphaePool {
- subnestLevel := strings.Count(subhyphaName, "/")
- if subnestLevel-1 == nestLevel && path.Dir(subhyphaName) == hyphaName {
- delete(subhyphaePool, subhyphaName)
- adopted = append(adopted, mainFamilyFromPool(subhyphaName, subhyphaePool))
- }
- }
- return &mainFamilyMember{name: hyphaName, children: adopted}
-}
-
-func subhyphaeMatrix(hyphaName string, subhyphaePool map[string]bool) string {
- var html string
- children := mainFamilyFromPool(hyphaName, subhyphaePool).children
+func subhyphaeMatrix(children []child) (html string) {
sort.Slice(children, func(i, j int) bool {
return children[i].name < children[j].name
})
@@ -92,49 +165,3 @@ func subhyphaeMatrix(hyphaName string, subhyphaePool map[string]bool) string {
}
return html
}
-
-// Tree generates a tree for `hyphaName` as html and returns next and previous hyphae if any.
-func Tree(hyphaName string) (relatives, subhyphae, prev, next string) {
- var (
- // One of the siblings is the hypha with name `hyphaName`
- siblings = findSiblings(hyphaName)
- subhyphaePool = make(map[string]bool)
- I int
- )
- for h := range hyphae.YieldExistingHyphae() {
- for _, s := range siblings {
- s.checkThisChild(h.Name)
- }
- if strings.HasPrefix(h.Name, hyphaName+"/") {
- subhyphaePool[h.Name] = true
- }
- }
- for i, s := range siblings {
- if s.name == hyphaName {
- I = i
- relatives += fmt.Sprintf(`%s`, util.BeautifulName(path.Base(hyphaName)))
- } else {
- relatives += s.asHTML()
- }
- }
- if I != 0 {
- prev = siblings[I-1].name
- }
- if I != len(siblings)-1 {
- next = siblings[I+1].name
- }
- return fmt.Sprintf(``, relatives), subhyphaeMatrix(hyphaName, subhyphaePool), prev, next
-}
-
-func findSiblings(hyphaName string) []*sibling {
- siblings := []*sibling{&sibling{name: hyphaName, hasChildren: true}}
- for h := range hyphae.YieldExistingHyphae() {
- if path.Dir(hyphaName) == path.Dir(h.Name) && hyphaName != h.Name {
- siblings = append(siblings, &sibling{name: h.Name, hasChildren: false})
- }
- }
- sort.Slice(siblings, func(i, j int) bool {
- return siblings[i].name < siblings[j].name
- })
- return siblings
-}