1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2024-12-12 05:20:26 +00:00
mycorrhiza/hyphae/backlinks.go
Timur Ismagilov 5dd74da8ed Migrate to Mycomarkup v3
Some stuff is broken, but at least it compiles. Funnily enough, the API turned out to be not broken. This is surprising.
2021-10-05 23:10:28 +03:00

193 lines
5.3 KiB
Go

package hyphae
import (
"github.com/bouncepaw/mycomarkup/v3/tools"
"os"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycomarkup/v3"
"github.com/bouncepaw/mycomarkup/v3/links"
"github.com/bouncepaw/mycomarkup/v3/mycocontext"
)
// Using set here seems like the most appropriate solution
type linkSet map[string]struct{}
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 ""
}
// BacklinkIndexOperation is an operation for the backlink index. This operation is executed async-safe.
type BacklinkIndexOperation interface {
Apply()
}
// BacklinkIndexEdit contains data for backlink index update after a hypha edit
type BacklinkIndexEdit struct {
Name string
OldLinks []string
NewLinks []string
}
// Apply changes backlink index respective to the operation data
func (op BacklinkIndexEdit) Apply() {
oldLinks := toLinkSet(op.OldLinks)
newLinks := toLinkSet(op.NewLinks)
for link := range oldLinks {
if _, exists := newLinks[link]; !exists {
delete(backlinkIndex[link], op.Name)
}
}
for link := range newLinks {
if _, exists := oldLinks[link]; !exists {
if _, exists := backlinkIndex[link]; !exists {
backlinkIndex[link] = make(linkSet)
}
backlinkIndex[link][op.Name] = struct{}{}
}
}
}
// BacklinkIndexDeletion contains data for backlink index update after a hypha deletion
type BacklinkIndexDeletion struct {
Name string
Links []string
}
// Apply changes backlink index respective to the operation data
func (op BacklinkIndexDeletion) Apply() {
for _, link := range op.Links {
if lSet, exists := backlinkIndex[link]; exists {
delete(lSet, op.Name)
}
}
}
// BacklinkIndexRenaming contains data for backlink index update after a hypha renaming
type BacklinkIndexRenaming struct {
OldName string
NewName string
Links []string
}
// Apply changes backlink index respective to the operation data
func (op BacklinkIndexRenaming) Apply() {
for _, link := range op.Links {
if lSet, exists := backlinkIndex[link]; exists {
delete(lSet, op.OldName)
backlinkIndex[link][op.NewName] = struct{}{}
}
}
}
var backlinkIndex = make(map[string]linkSet)
var backlinkConveyor = make(chan BacklinkIndexOperation, 64)
// I hope, the buffer size is enough -- chekoopa
// Do we really need the buffer though? Dunno -- bouncepaw
// 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 {
foundLinks := extractHyphaLinksFromContent(h.Name, fetchText(h))
for _, link := range foundLinks {
if _, exists := backlinkIndex[link]; !exists {
backlinkIndex[link] = make(linkSet)
}
backlinkIndex[link][h.Name] = struct{}{}
}
}
}
// RunBacklinksConveyor runs an index operation processing loop
func RunBacklinksConveyor() {
// It is supposed to run as a goroutine for all the time. So, don't blame the infinite loop.
defer close(backlinkConveyor)
for {
(<-backlinkConveyor).Apply()
}
}
// BacklinksCount returns the amount of backlinks to the hypha.
func BacklinksCount(h *Hypha) int {
if _, exists := backlinkIndex[h.Name]; exists {
return len(backlinkIndex[h.Name])
}
return 0
}
// BacklinksOnEdit is a creation/editing hook for backlinks index
func BacklinksOnEdit(h *Hypha, oldText string) {
oldLinks := extractHyphaLinksFromContent(h.Name, oldText)
newLinks := extractHyphaLinks(h)
backlinkConveyor <- BacklinkIndexEdit{h.Name, oldLinks, newLinks}
}
// BacklinksOnDelete is a deletion hook for backlinks index
func BacklinksOnDelete(h *Hypha, oldText string) {
oldLinks := extractHyphaLinksFromContent(h.Name, oldText)
backlinkConveyor <- BacklinkIndexDeletion{h.Name, oldLinks}
}
// BacklinksOnRename is a renaming hook for backlinks index
func BacklinksOnRename(h *Hypha, oldName string) {
actualLinks := extractHyphaLinks(h)
backlinkConveyor <- BacklinkIndexRenaming{oldName, h.Name, actualLinks}
}
// YieldHyphaBacklinks gets backlinks for a desired hypha, sorts and iterates over them
func YieldHyphaBacklinks(query string) <-chan string {
hyphaName := util.CanonicalName(query)
out := make(chan string)
sorted := PathographicSort(out)
go func() {
backlinks, exists := backlinkIndex[hyphaName]
if exists {
for link := range backlinks {
out <- link
}
}
close(out)
}()
return sorted
}
// extractHyphaLinks extracts hypha links from a desired hypha
func extractHyphaLinks(h *Hypha) []string {
return extractHyphaLinksFromContent(h.Name, fetchText(h))
}
// extractHyphaLinksFromContent extracts local hypha links from the provided text.
func extractHyphaLinksFromContent(hyphaName string, contents string) []string {
ctx, _ := mycocontext.ContextFromStringInput(hyphaName, contents)
linkVisitor, getLinks := tools.LinkVisitor(ctx)
// Ignore the result of BlockTree because we call it for linkVisitor.
_ = 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
}