package hyphae import ( "os" "sync" "github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycomarkup" "github.com/bouncepaw/mycomarkup/blocks" "github.com/bouncepaw/mycomarkup/links" "github.com/bouncepaw/mycomarkup/mycocontext" ) // Using set here seems like the most appropriate solution type linkSet map[string]struct{} var backlinkIndex = make(map[string]linkSet) var backlinkIndexMutex = sync.Mutex{} // IndexBacklinks traverses all text hyphae, extracts links from them and forms an initial index func IndexBacklinks() { // It is safe to ignore the mutex, because there is only one worker. src := FilterTextHyphae(YieldExistingHyphae()) for h := range src { fileContentsT, errT := os.ReadFile(h.TextPath) if errT == nil { links := ExtractHyphaLinksFromContent(h.Name, string(fileContentsT)) for _, link := range links { if _, exists := backlinkIndex[link]; !exists { backlinkIndex[link] = make(linkSet) } backlinkIndex[link][h.Name] = struct{}{} } } } } func YieldHyphaBacklinks(query string) <-chan string { hyphaName := util.CanonicalName(query) out := make(chan string) sorted := PathographicSort(out) go func() { links := backlinkIndex[hyphaName] for link := range links { out <- link } close(out) }() return sorted } // YieldHyphaLinks extracts hypha links from a desired hypha and iterates over them func YieldHyphaLinks(query string) <-chan string { // That is merely a debug function, but it could be useful. // Should we extract them into link-specific subfile? -- chekoopa hyphaName := util.CanonicalName(query) out := make(chan string) go func() { links := ExtractHyphaLinks(hyphaName) for _, link := range links { out <- link } close(out) }() return out } // ExtractHyphaLinks extracts hypha links from a desired hypha func ExtractHyphaLinks(hyphaName string) []string { var h = ByName(hyphaName) if h.Exists { fileContentsT, errT := os.ReadFile(h.TextPath) if errT == nil { return ExtractHyphaLinksFromContent(hyphaName, string(fileContentsT)) } } return make([]string, 0) } // ExtractHyphaLinksFromContent extracts hypha links from a provided text func ExtractHyphaLinksFromContent(hyphaName string, contents string) []string { ctx, _ := mycocontext.ContextFromStringInput(hyphaName, contents) linkVisitor, getLinks := LinkVisitor(ctx) mycomarkup.BlockTree(ctx, linkVisitor) foundLinks := getLinks() var result []string for _, link := range foundLinks { if link.OfKind(links.LinkLocalHypha) { result = append(result, link.TargetHypha()) } } return result } // LinkVisitor creates a visitor which extracts all the links func LinkVisitor(ctx mycocontext.Context) ( visitor func(block blocks.Block), result func() []links.Link, ) { var ( collected []links.Link ) var extractBlock func(block blocks.Block) extractBlock = func(block blocks.Block) { // fmt.Println(reflect.TypeOf(block)) switch b := block.(type) { case blocks.Paragraph: extractBlock(b.Formatted) case blocks.Heading: extractBlock(b.GetContents()) case blocks.List: for _, item := range b.Items { for _, sub := range item.Contents { extractBlock(sub) } } case blocks.Img: for _, entry := range b.Entries { extractBlock(entry) } case blocks.ImgEntry: collected = append(collected, *b.Srclink) case blocks.Transclusion: link := *links.From(b.Target, "", ctx.HyphaName()) collected = append(collected, link) case blocks.LaunchPad: for _, rocket := range b.Rockets { extractBlock(rocket) } case blocks.Formatted: for _, line := range b.Lines { for _, span := range line { switch s := span.(type) { case blocks.InlineLink: collected = append(collected, *s.Link) } } } case blocks.RocketLink: if !b.IsEmpty { collected = append(collected, b.Link) } } } visitor = func(block blocks.Block) { extractBlock(block) } result = func() []links.Link { return collected } return }