2020-08-05 20:19:14 +00:00
|
|
|
package tree
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"path"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
2021-04-06 18:47:35 +00:00
|
|
|
"sync"
|
2021-01-10 13:01:38 +00:00
|
|
|
|
2021-02-17 18:41:35 +00:00
|
|
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
2021-01-10 13:01:38 +00:00
|
|
|
"github.com/bouncepaw/mycorrhiza/util"
|
2020-08-05 20:19:14 +00:00
|
|
|
)
|
|
|
|
|
2021-04-06 18:47:35 +00:00
|
|
|
func findSiblingsAndDescendants(hyphaName string) ([]*sibling, map[string]bool) {
|
2021-10-01 08:57:00 +00:00
|
|
|
hyphaDir := ""
|
|
|
|
if hyphaRawDir := path.Dir(hyphaName); hyphaRawDir != "." {
|
|
|
|
hyphaDir = hyphaRawDir
|
|
|
|
}
|
2021-04-06 18:47:35 +00:00
|
|
|
var (
|
2021-10-01 08:57:00 +00:00
|
|
|
siblingsMap = make(map[string]bool)
|
2021-04-06 18:47:35 +00:00
|
|
|
siblingCheck = func(h *hyphae.Hypha) hyphae.CheckResult {
|
2021-10-01 08:57:00 +00:00
|
|
|
// I don't like this double comparison, but it is only the way to circumvent some flickups
|
|
|
|
if strings.HasPrefix(h.Name, hyphaDir) && h.Name != hyphaDir && h.Name != hyphaName {
|
|
|
|
var (
|
|
|
|
rawSubPath = strings.TrimPrefix(h.Name, hyphaDir)[1:]
|
|
|
|
slashIdx = strings.IndexRune(rawSubPath, '/')
|
|
|
|
)
|
|
|
|
if slashIdx > -1 {
|
|
|
|
var sibPath = h.Name[:slashIdx+len(hyphaDir)+1]
|
|
|
|
if _, exists := siblingsMap[sibPath]; !exists {
|
|
|
|
siblingsMap[sibPath] = false
|
|
|
|
}
|
|
|
|
} else { // it is a straight sibling
|
|
|
|
siblingsMap[h.Name] = true
|
|
|
|
}
|
2021-04-06 18:47:35 +00:00
|
|
|
}
|
|
|
|
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()
|
|
|
|
)
|
2021-10-01 08:57:00 +00:00
|
|
|
siblingsMap[hyphaName] = true
|
|
|
|
|
2021-04-06 18:47:35 +00:00
|
|
|
i7n.AddCheck(siblingCheck)
|
|
|
|
i7n.AddCheck(descendantCheck)
|
|
|
|
i7n.Ignite()
|
2021-10-01 08:57:00 +00:00
|
|
|
|
|
|
|
siblings := make([]*sibling, len(siblingsMap))
|
|
|
|
sibIdx := 0
|
|
|
|
for sibName, exists := range siblingsMap {
|
|
|
|
siblings[sibIdx] = &sibling{sibName, 0, 0, exists}
|
|
|
|
sibIdx++
|
|
|
|
}
|
2021-04-06 18:47:35 +00:00
|
|
|
sort.Slice(siblings, func(i, j int) bool {
|
|
|
|
return siblings[i].name < siblings[j].name
|
|
|
|
})
|
|
|
|
return siblings, descendantsPool
|
2020-08-05 20:19:14 +00:00
|
|
|
}
|
|
|
|
|
2021-04-06 18:47:35 +00:00
|
|
|
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
|
2021-07-12 15:23:25 +00:00
|
|
|
siblingsHTML += fmt.Sprintf(`<li class="sibling-hyphae__entry sibling-hyphae__entry_this"><span>%s</span></li>`, util.BeautifulName(path.Base(hyphaName)))
|
2021-04-06 18:47:35 +00:00
|
|
|
} else {
|
2021-04-07 17:49:56 +00:00
|
|
|
siblingsHTML += siblingHTML(s)
|
2021-04-06 18:47:35 +00:00
|
|
|
}
|
2020-08-05 20:19:14 +00:00
|
|
|
}
|
2021-04-07 16:49:00 +00:00
|
|
|
if I != 0 && len(siblings) > 1 {
|
2021-04-06 18:47:35 +00:00
|
|
|
prev = siblings[I-1].name
|
|
|
|
}
|
2021-04-07 16:49:00 +00:00
|
|
|
if I != len(siblings)-1 && len(siblings) > 1 {
|
2021-04-06 18:47:35 +00:00
|
|
|
next = siblings[I+1].name
|
|
|
|
}
|
2021-07-12 15:23:25 +00:00
|
|
|
return fmt.Sprintf(`<ul class="sibling-hyphae__list">%s</ul>`, siblingsHTML), subhyphaeMatrix(children), prev, next
|
2021-04-06 18:47:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2021-10-01 08:57:00 +00:00
|
|
|
exists bool
|
2020-08-05 20:19:14 +00:00
|
|
|
}
|
|
|
|
|
2021-04-06 18:47:35 +00:00
|
|
|
func subhyphaeMatrix(children []child) (html string) {
|
2021-02-19 18:12:36 +00:00
|
|
|
sort.Slice(children, func(i, j int) bool {
|
|
|
|
return children[i].name < children[j].name
|
|
|
|
})
|
|
|
|
for _, child := range children {
|
2021-04-07 17:49:56 +00:00
|
|
|
html += childHTML(&child)
|
2021-02-19 18:12:36 +00:00
|
|
|
}
|
|
|
|
return html
|
|
|
|
}
|