package tree import ( "fmt" "path" "sort" "strings" "sync" "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/util" ) func findSiblingsAndDescendants(hyphaName string) ([]*sibling, map[string]bool) { var ( siblings = []*sibling{&sibling{hyphaName, 0, 0}} siblingCheck = func(h *hyphae.Hypha) hyphae.CheckResult { if path.Dir(hyphaName) == path.Dir(h.Name) && h.Name != hyphaName { 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 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 && len(siblings) > 1 { prev = siblings[I-1].name } if I != len(siblings)-1 && len(siblings) > 1 { next = siblings[I+1].name } return fmt.Sprintf(``, siblingsHTML), subhyphaeMatrix(children), prev, next } 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.directSubhyphaeCount > 0 { class += " navitree__sibling_fertile navitree__entry_fertile" } else { class += " navitree__sibling_infertile navitree__entry_infertile" } return fmt.Sprintf( `
  • %s
  • `, class, s.name, util.BeautifulName(path.Base(s.name)), ) } func (c *child) asHTML() string { if len(c.children) == 0 { return fmt.Sprintf(`
  • %s
  • `, c.name, util.BeautifulName(path.Base(c.name))) } sort.Slice(c.children, func(i, j int) bool { return c.children[i].name < c.children[j].name }) html := fmt.Sprintf(`
  • %s
  • ` } func subhyphaeMatrix(children []child) (html string) { sort.Slice(children, func(i, j int) bool { return children[i].name < children[j].name }) for _, child := range children { html += child.asHTML() } return html }