diff --git a/http_readers.go b/http_readers.go index 0146c52..3153f4d 100644 --- a/http_readers.go +++ b/http_readers.go @@ -110,6 +110,7 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) { naviTitle(hyphaName), contents, treeHTML, + h.BackLinkEntriesHTML(), prevHypha, nextHypha, hasAmnt), u, diff --git a/hyphae/backlink.go b/hyphae/backlink.go new file mode 100644 index 0000000..21c79ad --- /dev/null +++ b/hyphae/backlink.go @@ -0,0 +1,72 @@ +package hyphae + +import ( + "fmt" + "io/ioutil" + + "github.com/bouncepaw/mycorrhiza/link" + "github.com/bouncepaw/mycorrhiza/markup" + "github.com/bouncepaw/mycorrhiza/util" +) + +func (h *Hypha) BackLinkEntriesHTML() (html string) { + for _, backlinkHypha := range h.BackLinks { + _ = link.Link{} + html += fmt.Sprintf(`
  • + %s`, backlinkHypha.Name, util.BeautifulName(backlinkHypha.Name)) + } + return +} + +func (h *Hypha) outlinksThis(oh *Hypha) bool { + for _, outlink := range h.OutLinks { + if outlink == oh { + return true + } + } + return false +} + +func (h *Hypha) backlinkedBy(oh *Hypha) bool { + for _, backlink := range h.BackLinks { + if backlink == oh { + return true + } + } + return false +} + +// FindAllBacklinks iterates over all hyphae that have text parts, sets their outlinks and then sets backlinks. +func FindAllBacklinks() { + for h := range FilterTextHyphae(YieldExistingHyphae()) { + findBacklinkWorker(h) + } +} + +func findBacklinkWorker(h *Hypha) { + h.Lock() + defer h.Unlock() + + textContents, err := ioutil.ReadFile(h.TextPath) + if err == nil { + for outlink := range markup.Doc(h.Name, string(textContents)).OutLinks() { + outlink := outlink + outlinkHypha := ByName(outlink) + if outlinkHypha == h { + continue + } + + outlinkHypha.Lock() + if !outlinkHypha.backlinkedBy(h) { + outlinkHypha.BackLinks = append(outlinkHypha.BackLinks, h) + outlinkHypha.InsertIfNewKeepExistence() + } + outlinkHypha.Unlock() + + // Insert outlinkHypha if unique + if !h.outlinksThis(outlinkHypha) { + h.OutLinks = append(h.OutLinks, outlinkHypha) + } + } + } +} diff --git a/hyphae/hyphae.go b/hyphae/hyphae.go index 8db893d..1c4496c 100644 --- a/hyphae/hyphae.go +++ b/hyphae/hyphae.go @@ -49,8 +49,8 @@ type Hypha struct { Exists bool TextPath string BinaryPath string - OutLinks []*Hypha // not used yet - BackLinks []*Hypha // not used yet + OutLinks []*Hypha + BackLinks []*Hypha } var byNames = make(map[string]*Hypha) @@ -59,17 +59,31 @@ var byNamesMutex = sync.Mutex{} // YieldExistingHyphae iterates over all hyphae and yields all existing ones. func YieldExistingHyphae() chan *Hypha { ch := make(chan *Hypha) - go func(ch chan *Hypha) { + go func() { for _, h := range byNames { if h.Exists { ch <- h } } close(ch) - }(ch) + }() return ch } +// FilterTextHyphae filters the source channel and yields only those hyphae than have text parts. +func FilterTextHyphae(src chan *Hypha) chan *Hypha { + sink := make(chan *Hypha) + go func() { + for h := range src { + if h.TextPath != "" { + sink <- h + } + } + close(sink) + }() + return sink +} + // Subhyphae returns slice of subhyphae. func (h *Hypha) Subhyphae() []*Hypha { hyphae := []*Hypha{} @@ -138,6 +152,18 @@ func (h *Hypha) InsertIfNew() (justCreated bool) { return false } +func (h *Hypha) InsertIfNewKeepExistence() { + hp := ByName(h.Name) + + byNamesMutex.Lock() + defer byNamesMutex.Unlock() + if hp.Exists { + hp = h + } else { + byNames[h.Name] = h + } +} + func (h *Hypha) delete() { byNamesMutex.Lock() h.Lock() diff --git a/main.go b/main.go index e667d8f..9e7ce22 100644 --- a/main.go +++ b/main.go @@ -175,6 +175,8 @@ func main() { log.Println("Wiki storage directory is", WikiDir) hyphae.Index(WikiDir) log.Println("Indexed", hyphae.Count(), "hyphae") + hyphae.FindAllBacklinks() + log.Println("Found all backlinks") history.Start(WikiDir) hyphae.SetHeaderLinks() diff --git a/markup/lexer.go b/markup/lexer.go index 4516eb0..607393f 100644 --- a/markup/lexer.go +++ b/markup/lexer.go @@ -166,7 +166,7 @@ launchpadState: switch { case startsWith("=>"): href, text, class := Rocketlink(line, state.name) - state.buf += fmt.Sprintf(`
  • %s
  • `, class, href, text) + state.buf += fmt.Sprintf(`
  • %s
  • `, href, class, text) case startsWith("```"): state.where = "pre" addLine(state.buf + "") diff --git a/markup/outlink.go b/markup/outlink.go new file mode 100644 index 0000000..0580371 --- /dev/null +++ b/markup/outlink.go @@ -0,0 +1,56 @@ +package markup + +import ( + "regexp" + "strings" + + "github.com/bouncepaw/mycorrhiza/link" +) + +// OutLinks returns a channel of names of hyphae this mycodocument links. +// Links include: +// * Regular links +// * Rocketlinks +// * Transclusion +// * Image galleries +func (md *MycoDoc) OutLinks() chan string { + ch := make(chan string) + if !md.parsedAlready { + md.Lex(0) + } + go func() { + for _, line := range md.ast { + switch v := line.contents.(type) { + case string: + if strings.HasPrefix(v, "`) + +func extractLinks(html string, ch chan string) { + if results := reLinks.FindAllStringSubmatch(html, -1); results != nil { + for _, result := range results { + // result[0] is always present at this point and is not needed, because it is the whole matched substring (which we don't need) + ch <- result[1] + } + } +} + +func extractImageLinks(img Img, ch chan string) { + for _, entry := range img.entries { + if entry.srclink.Kind == link.LinkLocalHypha { + ch <- entry.srclink.Address + } + } +} diff --git a/templates/common.qtpl b/templates/common.qtpl index 1e02c6d..1109b93 100644 --- a/templates/common.qtpl +++ b/templates/common.qtpl @@ -58,7 +58,18 @@ var navEntries = []navEntry{ {% func relativeHyphae(relatives string) %} {% endfunc %} + +{% func backlinks(backlinkEntries string) %} + +{% endfunc %} diff --git a/templates/common.qtpl.go b/templates/common.qtpl.go index 587d599..209e68e 100644 --- a/templates/common.qtpl.go +++ b/templates/common.qtpl.go @@ -217,7 +217,7 @@ func streamrelativeHyphae(qw422016 *qt422016.Writer, relatives string) { //line templates/common.qtpl:59 qw422016.N().S(`