2021-08-31 17:27:52 +00:00
|
|
|
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{}
|
|
|
|
|
2021-08-31 19:28:12 +00:00
|
|
|
func toLinkSet(xs []string) linkSet {
|
|
|
|
result := make(linkSet)
|
|
|
|
for _, x := range xs {
|
|
|
|
result[x] = struct{}{}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func fetchText(h *Hypha) string {
|
|
|
|
if h.TextPath == "" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
text, err := os.ReadFile(h.TextPath)
|
|
|
|
if err == nil {
|
|
|
|
return string(text)
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2021-08-31 17:27:52 +00:00
|
|
|
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 {
|
2021-08-31 19:28:12 +00:00
|
|
|
links := ExtractHyphaLinksFromContent(h.Name, fetchText(h))
|
|
|
|
for _, link := range links {
|
|
|
|
if _, exists := backlinkIndex[link]; !exists {
|
|
|
|
backlinkIndex[link] = make(linkSet)
|
|
|
|
}
|
|
|
|
backlinkIndex[link][h.Name] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func BacklinksOnEdit(h *Hypha, oldText string) {
|
|
|
|
backlinkIndexMutex.Lock()
|
|
|
|
newLinks := toLinkSet(ExtractHyphaLinks(h))
|
|
|
|
oldLinks := toLinkSet(ExtractHyphaLinksFromContent(h.Name, oldText))
|
|
|
|
for link := range oldLinks {
|
|
|
|
if _, exists := newLinks[link]; !exists {
|
|
|
|
delete(backlinkIndex[link], h.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for link := range newLinks {
|
|
|
|
if _, exists := oldLinks[link]; !exists {
|
|
|
|
if _, exists := backlinkIndex[link]; !exists {
|
|
|
|
backlinkIndex[link] = make(linkSet)
|
2021-08-31 17:27:52 +00:00
|
|
|
}
|
2021-08-31 19:28:12 +00:00
|
|
|
backlinkIndex[link][h.Name] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
backlinkIndexMutex.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func BacklinksOnDelete(h *Hypha, oldText string) {
|
|
|
|
backlinkIndexMutex.Lock()
|
|
|
|
oldLinks := ExtractHyphaLinksFromContent(h.Name, oldText)
|
|
|
|
for _, link := range oldLinks {
|
|
|
|
if lSet, exists := backlinkIndex[link]; exists {
|
|
|
|
delete(lSet, h.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
backlinkIndexMutex.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func BacklinksOnRename(h *Hypha, oldName string) {
|
|
|
|
backlinkIndexMutex.Lock()
|
|
|
|
actualLinks := ExtractHyphaLinks(h)
|
|
|
|
for _, link := range actualLinks {
|
|
|
|
if lSet, exists := backlinkIndex[link]; exists {
|
|
|
|
delete(lSet, oldName)
|
|
|
|
backlinkIndex[link][h.Name] = struct{}{}
|
2021-08-31 17:27:52 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-31 19:28:12 +00:00
|
|
|
backlinkIndexMutex.Unlock()
|
2021-08-31 17:27:52 +00:00
|
|
|
}
|
|
|
|
|
2021-08-31 19:28:12 +00:00
|
|
|
// YieldHyphaBacklinks gets backlinks for a desired hypha, sorts and iterates over them
|
2021-08-31 17:27:52 +00:00
|
|
|
func YieldHyphaBacklinks(query string) <-chan string {
|
|
|
|
hyphaName := util.CanonicalName(query)
|
|
|
|
out := make(chan string)
|
|
|
|
sorted := PathographicSort(out)
|
|
|
|
go func() {
|
2021-08-31 19:28:12 +00:00
|
|
|
links, exists := backlinkIndex[hyphaName]
|
|
|
|
if exists {
|
|
|
|
for link := range links {
|
|
|
|
out <- link
|
|
|
|
}
|
2021-08-31 17:27:52 +00:00
|
|
|
}
|
|
|
|
close(out)
|
|
|
|
}()
|
|
|
|
return sorted
|
|
|
|
}
|
|
|
|
|
2021-08-31 19:28:12 +00:00
|
|
|
// YieldHyphaLinks extracts hypha links from a desired hypha, sorts and iterates over them
|
2021-08-31 17:27:52 +00:00
|
|
|
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() {
|
2021-08-31 19:28:12 +00:00
|
|
|
var h = ByName(hyphaName)
|
|
|
|
links := ExtractHyphaLinks(h)
|
2021-08-31 17:27:52 +00:00
|
|
|
for _, link := range links {
|
|
|
|
out <- link
|
|
|
|
}
|
|
|
|
close(out)
|
|
|
|
}()
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExtractHyphaLinks extracts hypha links from a desired hypha
|
2021-08-31 19:28:12 +00:00
|
|
|
func ExtractHyphaLinks(h *Hypha) []string {
|
|
|
|
return ExtractHyphaLinksFromContent(h.Name, fetchText(h))
|
2021-08-31 17:27:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|