1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2025-01-21 07:46:52 +00:00

Delete the markup module, depend on the library instead

The old module had been moved to the library, but I changed it a little, so now both projects are broken. I sure do love programming.
This commit is contained in:
Timur Ismagilov 2021-05-12 18:34:24 +05:00
parent b4dd2dcfad
commit da6ea25bb6
21 changed files with 16 additions and 1486 deletions

View File

@ -20,8 +20,9 @@ import (
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycomarkup/legacy"
)
func geminiHomeHypha(w *gemini.ResponseWriter, rq *gemini.Request) {

2
go.mod
View File

@ -5,7 +5,7 @@ go 1.14
require (
git.sr.ht/~adnano/go-gemini v0.1.13
github.com/adrg/xdg v0.2.2
github.com/bouncepaw/mycomarkup v0.0.0-20210511092446-956f169b1499
github.com/bouncepaw/mycomarkup v0.0.0-20210512131946-becfae76473f
github.com/go-ini/ini v1.62.0
github.com/gorilla/feeds v1.1.1
github.com/kr/pretty v0.2.1 // indirect

3
go.sum
View File

@ -7,6 +7,8 @@ github.com/bouncepaw/mycomarkup v0.0.0-20210502065108-4ddae294864d h1:H20wX93QMe
github.com/bouncepaw/mycomarkup v0.0.0-20210502065108-4ddae294864d/go.mod h1:fx0FBKaCaV1fofgCQOu2lNIVB/5EoDg/83i8BgBdgpc=
github.com/bouncepaw/mycomarkup v0.0.0-20210511092446-956f169b1499 h1:RLKVj992QuayqjpIJ7M2csRjfhAnu9EO2r1Bm4D/WlU=
github.com/bouncepaw/mycomarkup v0.0.0-20210511092446-956f169b1499/go.mod h1:fx0FBKaCaV1fofgCQOu2lNIVB/5EoDg/83i8BgBdgpc=
github.com/bouncepaw/mycomarkup v0.0.0-20210512131946-becfae76473f h1:PgXG/AqO96jahMGQA81CWclIg90gsGt5kK4o7y0eZy8=
github.com/bouncepaw/mycomarkup v0.0.0-20210512131946-becfae76473f/go.mod h1:4Fz80hsrXi3lRVZuVfIk5+2xocp50CFAbJfGeJWb5aM=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ini/ini v1.62.0 h1:7VJT/ZXjzqSrvtraFp4ONq80hTcRQth1c9ZnQ3uNQvU=
@ -62,6 +64,7 @@ golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -1,34 +0,0 @@
package markup
import (
"unicode"
)
// MatchesHorizontalLine checks if the string can be interpreted as suitable for rendering as <hr/>.
//
// The rule is: if there are more than 4 characters "-" in the string, then make it a horizontal line.
// Otherwise it is a paragraph (<p>).
func MatchesHorizontalLine(line string) bool {
counter := 0
// Check initially that the symbol is "-". If it is not a "-", it is most likely a space or another character.
// With unicode.IsLetter() we can separate spaces and characters.
for _, ch := range line {
if ch == '-' {
counter++
continue
}
// If we bump into any other character (letter) in the line, it is immediately an incorrect horizontal line.
// There is no point in counting further, we end the loop.
if unicode.IsLetter(ch) {
counter = 0
break
}
}
if counter >= 4 {
return true
}
return false
}

View File

@ -1,203 +0,0 @@
package markup
import (
"fmt"
"regexp"
"strings"
"github.com/bouncepaw/mycomarkup/links"
)
var imgRe = regexp.MustCompile(`^img\s+{`)
func MatchesImg(line string) bool {
return imgRe.MatchString(line)
}
type imgState int
const (
inRoot imgState = iota
inName
inDimensionsW
inDimensionsH
inDescription
)
type Img struct {
entries []imgEntry
currEntry imgEntry
hyphaName string
state imgState
}
func (img *Img) pushEntry() {
if strings.TrimSpace(img.currEntry.path.String()) != "" {
img.currEntry.srclink = links.From(img.currEntry.path.String(), "", img.hyphaName)
// img.currEntry.srclink.DoubtExistence()
img.entries = append(img.entries, img.currEntry)
img.currEntry = imgEntry{}
img.currEntry.path.Reset()
}
}
func (img *Img) Process(line string) (shouldGoBackToNormal bool) {
stateToProcessor := map[imgState]func(rune) bool{
inRoot: img.processInRoot,
inName: img.processInName,
inDimensionsW: img.processInDimensionsW,
inDimensionsH: img.processInDimensionsH,
inDescription: img.processInDescription,
}
for _, r := range line {
if shouldReturnTrue := stateToProcessor[img.state](r); shouldReturnTrue {
return true
}
}
return false
}
func (img *Img) processInDescription(r rune) (shouldReturnTrue bool) {
switch r {
case '}':
img.state = inName
default:
img.currEntry.desc.WriteRune(r)
}
return false
}
func (img *Img) processInRoot(r rune) (shouldReturnTrue bool) {
switch r {
case '}':
img.pushEntry()
return true
case '\n', '\r':
img.pushEntry()
case ' ', '\t':
default:
img.state = inName
img.currEntry = imgEntry{}
img.currEntry.path.Reset()
img.currEntry.path.WriteRune(r)
}
return false
}
func (img *Img) processInName(r rune) (shouldReturnTrue bool) {
switch r {
case '}':
img.pushEntry()
return true
case '|':
img.state = inDimensionsW
case '{':
img.state = inDescription
case '\n', '\r':
img.pushEntry()
img.state = inRoot
default:
img.currEntry.path.WriteRune(r)
}
return false
}
func (img *Img) processInDimensionsW(r rune) (shouldReturnTrue bool) {
switch r {
case '}':
img.pushEntry()
return true
case '*':
img.state = inDimensionsH
case ' ', '\t', '\n':
case '{':
img.state = inDescription
default:
img.currEntry.sizeW.WriteRune(r)
}
return false
}
func (img *Img) processInDimensionsH(r rune) (shouldGoBackToNormal bool) {
switch r {
case '}':
img.pushEntry()
return true
case ' ', '\t', '\n':
case '{':
img.state = inDescription
default:
img.currEntry.sizeH.WriteRune(r)
}
return false
}
func ImgFromFirstLine(line, hyphaName string) (img *Img, shouldGoBackToNormal bool) {
img = &Img{
hyphaName: hyphaName,
entries: make([]imgEntry, 0),
}
line = line[strings.IndexRune(line, '{')+1:]
return img, img.Process(line)
}
func (img *Img) pagePathFor(path string) string {
path = strings.TrimSpace(path)
if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 {
return path
} else {
return "/page/" + xclCanonicalName(img.hyphaName, path)
}
}
func parseDimensions(dimensions string) (sizeW, sizeH string) {
xIndex := strings.IndexRune(dimensions, '*')
if xIndex == -1 { // If no x in dimensions
sizeW = strings.TrimSpace(dimensions)
} else {
sizeW = strings.TrimSpace(dimensions[:xIndex])
sizeH = strings.TrimSpace(strings.TrimPrefix(dimensions, dimensions[:xIndex+1]))
}
return
}
func (img *Img) markExistenceOfSrcLinks() {
HyphaIterate(func(hn string) {
for _, entry := range img.entries {
if hn == entry.srclink.Address() {
entry.srclink.DestinationUnknown = false
}
}
})
}
func (img *Img) ToHtml() (html string) {
img.markExistenceOfSrcLinks()
isOneImageOnly := len(img.entries) == 1 && img.entries[0].desc.Len() == 0
if isOneImageOnly {
html += `<section class="img-gallery img-gallery_one-image">`
} else {
html += `<section class="img-gallery img-gallery_many-images">`
}
for _, entry := range img.entries {
html += `<figure>`
if entry.srclink.DestinationUnknown {
html += fmt.Sprintf(
`<a class="%s" href="%s">Hypha <i>%s</i> does not exist</a>`,
entry.srclink.Classes(),
entry.srclink.Href(),
entry.srclink.Address)
} else {
html += fmt.Sprintf(
`<a href="%s"><img src="%s" %s %s></a>`,
entry.srclink.Href(),
entry.srclink.ImgSrc(),
entry.sizeWAsAttr(),
entry.sizeHAsAttr())
}
html += entry.descriptionAsHtml(img.hyphaName)
html += `</figure>`
}
return html + `</section>`
}

View File

@ -1,45 +0,0 @@
package markup
import (
"strings"
"github.com/bouncepaw/mycomarkup/links"
)
type imgEntry struct {
srclink *links.Link
path strings.Builder
sizeW strings.Builder
sizeH strings.Builder
desc strings.Builder
}
func (entry *imgEntry) descriptionAsHtml(hyphaName string) (html string) {
if entry.desc.Len() == 0 {
return ""
}
lines := strings.Split(entry.desc.String(), "\n")
for _, line := range lines {
if line = strings.TrimSpace(line); line != "" {
if html != "" {
html += `<br>`
}
html += ParagraphToHtml(hyphaName, line)
}
}
return `<figcaption>` + html + `</figcaption>`
}
func (entry *imgEntry) sizeWAsAttr() string {
if entry.sizeW.Len() == 0 {
return ""
}
return ` width="` + entry.sizeW.String() + `"`
}
func (entry *imgEntry) sizeHAsAttr() string {
if entry.sizeH.Len() == 0 {
return ""
}
return ` height="` + entry.sizeH.String() + `"`
}

View File

@ -1,229 +0,0 @@
package markup
import (
"fmt"
"html"
"strings"
"github.com/bouncepaw/mycorrhiza/util"
)
// HyphaExists holds function that checks that a hypha is present.
var HyphaExists func(string) bool
//
var HyphaImageForOG func(string) string
// HyphaAccess holds function that accesses a hypha by its name.
var HyphaAccess func(string) (rawText, binaryHtml string, err error)
// HyphaIterate is a function that iterates all hypha names existing.
var HyphaIterate func(func(string))
// GemLexerState is used by markup parser to remember what is going on.
type GemLexerState struct {
// Name of hypha being parsed
name string
where string // "", "list", "pre"
// Line id
id int
buf string
// Temporaries
img *Img
table *Table
list *List
}
type Line struct {
id int
// interface{} may be bad. TODO: a proper type
contents interface{}
}
func (md *MycoDoc) lex() (ast []Line) {
var state = GemLexerState{name: md.hyphaName}
for _, line := range append(strings.Split(md.contents, "\n"), "") {
lineToAST(line, &state, &ast)
}
return ast
}
// Lex `line` in markup and save it to `ast` using `state`.
func lineToAST(line string, state *GemLexerState, ast *[]Line) {
addLine := func(text interface{}) {
*ast = append(*ast, Line{id: state.id, contents: text})
}
addParagraphIfNeeded := func() {
if state.where == "p" {
state.where = ""
addLine(fmt.Sprintf("<p id='%d'>%s</p>", state.id, strings.ReplaceAll(ParagraphToHtml(state.name, state.buf), "\n", "<br>")))
state.buf = ""
}
}
// Process empty lines depending on the current state
if "" == strings.TrimSpace(line) {
switch state.where {
case "pre":
state.buf += "\n"
case "launchpad":
state.where = ""
addLine(state.buf + "</ul>")
case "p":
addParagraphIfNeeded()
}
return
}
startsWith := func(token string) bool {
return strings.HasPrefix(line, token)
}
addHeading := func(i int) {
id := util.LettersNumbersOnly(line[i+1:])
addLine(fmt.Sprintf(`<h%d id='%d'>%s<a href="#%s" id="%s" class="heading__link"></a></h%d>`, i, state.id, ParagraphToHtml(state.name, line[i+1:]), id, id, i))
}
// Beware! Usage of goto. Some may say it is considered evil but in this case it helped to make a better-structured code.
switch state.where {
case "img":
goto imgState
case "table":
goto tableState
case "list":
goto listState
case "pre":
goto preformattedState
case "launchpad":
goto launchpadState
default: // "p" or ""
goto normalState
}
imgState:
if shouldGoBackToNormal := state.img.Process(line); shouldGoBackToNormal {
state.where = ""
addLine(*state.img)
}
return
tableState:
if shouldGoBackToNormal := state.table.Process(line); shouldGoBackToNormal {
state.where = ""
addLine(*state.table)
}
return
listState:
if done := state.list.Parse(line); done {
state.list.Finalize()
state.where = ""
goto normalState
}
return
preformattedState:
switch {
case startsWith("```"):
state.where = ""
state.buf = strings.TrimSuffix(state.buf, "\n")
addLine(state.buf + "</code></pre>")
state.buf = ""
default:
state.buf += html.EscapeString(line) + "\n"
}
return
launchpadState:
switch {
case startsWith("=>"):
href, text, class := Rocketlink(line, state.name)
state.buf += fmt.Sprintf(` <li class="launchpad__entry"><a href="%s" class="rocketlink %s">%s</a></li>`, href, class, text)
case startsWith("```"):
state.where = "pre"
addLine(state.buf + "</ul>")
state.id++
state.buf = fmt.Sprintf("<pre id='%d' alt='%s' class='codeblock'><code>", state.id, strings.TrimPrefix(line, "```"))
default:
state.where = ""
addLine(state.buf + "</ul>")
goto normalState
}
return
normalState:
state.id++
switch {
case startsWith("```"):
addParagraphIfNeeded()
state.where = "pre"
state.buf = fmt.Sprintf("<pre id='%d' alt='%s' class='codeblock'><code>", state.id, strings.TrimPrefix(line, "```"))
case startsWith("###### "):
addParagraphIfNeeded()
addHeading(6)
case startsWith("##### "):
addParagraphIfNeeded()
addHeading(5)
case startsWith("#### "):
addParagraphIfNeeded()
addHeading(4)
case startsWith("### "):
addParagraphIfNeeded()
addHeading(3)
case startsWith("## "):
addParagraphIfNeeded()
addHeading(2)
case startsWith("# "):
addParagraphIfNeeded()
addHeading(1)
case startsWith(">"):
addParagraphIfNeeded()
addLine(
fmt.Sprintf(
"<blockquote id='%d'>%s</blockquote>",
state.id,
ParagraphToHtml(state.name, remover(">")(line)),
),
)
case startsWith("=>"):
addParagraphIfNeeded()
state.where = "launchpad"
state.buf = fmt.Sprintf("<ul class='launchpad' id='%d'>\n", state.id)
goto launchpadState
case startsWith("<="):
addParagraphIfNeeded()
addLine(parseTransclusion(line, state.name))
case MatchesHorizontalLine(line):
addParagraphIfNeeded()
*ast = append(*ast, Line{id: -1, contents: "<hr/>"})
case MatchesList(line):
addParagraphIfNeeded()
list, _ := NewList(line, state.name)
state.where = "list"
state.list = list
addLine(state.list)
case MatchesImg(line):
addParagraphIfNeeded()
img, shouldGoBackToNormal := ImgFromFirstLine(line, state.name)
if shouldGoBackToNormal {
addLine(*img)
} else {
state.where = "img"
state.img = img
}
case MatchesTable(line):
addParagraphIfNeeded()
state.where = "table"
state.table = TableFromFirstLine(line, state.name)
case state.where == "p":
state.buf += "\n" + line
default:
state.where = "p"
state.buf = line
}
}

View File

@ -1,33 +0,0 @@
package markup
import (
"strings"
"github.com/bouncepaw/mycomarkup/links"
)
// LinkParts determines what href, text and class should resulting <a> have based on mycomarkup's addr, display and hypha name.
//
// => addr display
// [[addr|display]]
// TODO: deprecate
func LinkParts(addr, display, hyphaName string) (href, text, class string) {
l := links.From(addr, display, hyphaName)
if l.OfKind(links.LinkLocalHypha) && !HyphaExists(l.Address()) {
l.DestinationUnknown = true
}
return l.Href(), l.Display(), l.Classes()
}
// Parse markup line starting with "=>" according to wikilink rules.
// See http://localhost:1737/page/wikilink
func Rocketlink(src, hyphaName string) (href, text, class string) {
src = strings.TrimSpace(src[2:]) // Drop =>
if src == "" {
return
}
// Href is text after => till first whitespace
addr := strings.Fields(src)[0]
display := strings.TrimPrefix(src, addr)
return LinkParts(addr, display, hyphaName)
}

View File

@ -1,171 +0,0 @@
package markup
import (
"errors"
"strings"
)
func parseListItem(line string) (level int, offset int, ordered bool, err error) {
for line[level] == '*' {
level++
}
if line[level] == '.' {
ordered = true
offset = level + 2
} else {
ordered = false
offset = level + 1
}
if line[offset-1] != ' ' || len(line) < offset+2 || level < 1 || level > 6 {
err = errors.New("ill-formatted list item")
}
return
}
func MatchesList(line string) bool {
level, _, _, err := parseListItem(line)
return err == nil && level == 1
}
type listItem struct {
content string
parent *listItem
children []*listItem
depth int
}
func newListItem(parent *listItem) *listItem {
depth := 0
if parent != nil {
depth = parent.depth + 1
}
return &listItem{
parent: parent,
children: make([]*listItem, 0),
depth: depth,
}
}
func (item *listItem) renderAsHtmlTo(b *strings.Builder, hyphaName string, ordered bool) {
if len(item.content) > 0 {
b.WriteString("<li>")
b.WriteString(ParagraphToHtml(hyphaName, item.content))
}
if len(item.children) > 0 {
if ordered {
b.WriteString("<ol>")
} else {
b.WriteString("<ul>")
}
for _, child := range item.children {
child.renderAsHtmlTo(b, hyphaName, ordered)
}
if ordered {
b.WriteString("</ol>")
} else {
b.WriteString("</ul>")
}
}
if len(item.content) > 0 {
b.WriteString("</li>")
}
}
// A structure representing ordered and unordered lists in the AST.
type List struct {
curr *listItem
hyphaName string
ordered bool
finalized bool
}
func NewList(line, hyphaName string) (*List, bool) {
list := &List{
hyphaName: hyphaName,
curr: newListItem(nil),
}
return list, list.Parse(line)
}
func (list *List) pushItem() {
item := newListItem(list.curr)
list.curr.children = append(list.curr.children, item)
list.curr = item
}
func (list *List) popItem() {
if list.curr == nil {
return
}
list.curr = list.curr.parent
}
func (list *List) balance(level int) {
for level > list.curr.depth {
list.pushItem()
}
for level < list.curr.depth {
list.popItem()
}
}
func (list *List) Parse(line string) (done bool) {
level, offset, ordered, err := parseListItem(line)
if err != nil {
list.Finalize()
return true
}
// update ordered flag if the current node is the root one
// (i.e. no parsing has been done yet)
if list.curr.parent == nil {
list.ordered = ordered
}
// if list type has suddenly changed (ill-formatted list), quit
if ordered != list.ordered {
list.Finalize()
return true
}
list.balance(level)
// if the current node already has content, create a new one
// to prevent overwriting existing content (effectively creating
// a new sibling node)
if len(list.curr.content) > 0 {
list.popItem()
list.pushItem()
}
list.curr.content = line[offset:]
return false
}
func (list *List) Finalize() {
if !list.finalized {
// close all opened nodes, effectively going up to the root node
list.balance(0)
list.finalized = true
}
}
func (list *List) RenderAsHtml() (html string) {
// for a good measure
list.Finalize()
b := &strings.Builder{}
// fire up recursive render process
list.curr.renderAsHtmlTo(b, list.hyphaName, list.ordered)
return b.String()
}

View File

@ -1,98 +0,0 @@
// This is not done yet
package markup
import (
"fmt"
"regexp"
"strings"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycomarkup/links"
)
// A Mycomarkup-formatted document
type MycoDoc struct {
// data
hyphaName string
contents string
// indicators
parsedAlready bool
// results
ast []Line
html string
firstImageURL string
description string
}
// Constructor
func Doc(hyphaName, contents string) *MycoDoc {
md := &MycoDoc{
hyphaName: hyphaName,
contents: contents,
}
return md
}
func (md *MycoDoc) Lex(recursionLevel int) *MycoDoc {
if !md.parsedAlready {
md.ast = md.lex()
}
md.parsedAlready = true
return md
}
// AsHTML returns an html representation of the document
func (md *MycoDoc) AsHTML() string {
md.html = Parse(md.Lex(0).ast, 0, 0, 0)
return md.html
}
// AsGemtext returns a gemtext representation of the document. Currently really limited, just returns source text
func (md *MycoDoc) AsGemtext() string {
return md.contents
}
// Used to clear opengraph description from html tags. This method is usually bad because of dangers of malformed HTML, but I'm going to use it only for Mycorrhiza-generated HTML, so it's okay. The question mark is required; without it the whole string is eaten away.
var htmlTagRe = regexp.MustCompile(`<.*?>`)
// OpenGraphHTML returns an html representation of og: meta tags.
func (md *MycoDoc) OpenGraphHTML() string {
md.ogFillVars()
return strings.Join([]string{
ogTag("title", md.hyphaName),
ogTag("type", "article"),
ogTag("image", md.firstImageURL),
ogTag("url", cfg.URL+"/hypha/"+md.hyphaName),
ogTag("determiner", ""),
ogTag("description", htmlTagRe.ReplaceAllString(md.description, "")),
}, "\n")
}
func (md *MycoDoc) ogFillVars() *MycoDoc {
md.firstImageURL = cfg.URL + "/favicon.ico"
foundDesc := false
foundImg := false
for _, line := range md.ast {
switch v := line.contents.(type) {
case string:
if !foundDesc {
md.description = v
foundDesc = true
}
case Img:
if !foundImg && len(v.entries) > 0 {
md.firstImageURL = v.entries[0].srclink.ImgSrc()
if !v.entries[0].srclink.OfKind(links.LinkExternal) {
md.firstImageURL = cfg.URL + md.firstImageURL
}
foundImg = true
}
}
}
return md
}
func ogTag(property, content string) string {
return fmt.Sprintf(`<meta property="og:%s" content="%s"/>`, property, content)
}

View File

@ -1,57 +0,0 @@
package markup
import (
"regexp"
"strings"
"github.com/bouncepaw/mycomarkup/links"
)
// OutLinks returns a channel of names of hyphae this mycodocument links.
// Links include:
// * Regular links
// * Rocketlinks
// * Transclusion
// * Image galleries
// Not needed anymore, I guess.
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, "<p") || strings.HasPrefix(v, "<ul class='launchpad'") {
extractLinks(v, ch)
}
case Transclusion:
ch <- v.name
case Img:
extractImageLinks(v, ch)
}
}
close(ch)
}()
return ch
}
var reLinks = regexp.MustCompile(`<a href="/hypha/([^"]*)".*?</a>`)
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.OfKind(links.LinkLocalHypha) {
ch <- entry.srclink.Address()
}
}
}

View File

@ -1,185 +0,0 @@
package markup
import (
"bytes"
"fmt"
"html"
"strings"
"unicode"
)
type spanTokenType int
const (
spanTextNode = iota
spanItalic
spanBold
spanMono
spanSuper
spanSub
spanMark
spanStrike
spanLink
)
func tagFromState(stt spanTokenType, tagState map[spanTokenType]bool, tagName, originalForm string) string {
if tagState[spanMono] && (stt != spanMono) {
return originalForm
}
if tagState[stt] {
tagState[stt] = false
return fmt.Sprintf("</%s>", tagName)
} else {
tagState[stt] = true
return fmt.Sprintf("<%s>", tagName)
}
}
func getLinkNode(input *bytes.Buffer, hyphaName string, isBracketedLink bool) string {
if isBracketedLink {
input.Next(2) // drop those [[
}
var (
escaping = false
addrBuf = bytes.Buffer{}
displayBuf = bytes.Buffer{}
currBuf = &addrBuf
)
for input.Len() != 0 {
b, _ := input.ReadByte()
if escaping {
currBuf.WriteByte(b)
escaping = false
} else if isBracketedLink && b == '|' && currBuf == &addrBuf {
currBuf = &displayBuf
} else if isBracketedLink && b == ']' && bytes.HasPrefix(input.Bytes(), []byte{']'}) {
input.Next(1)
break
} else if !isBracketedLink && (unicode.IsSpace(rune(b)) || strings.ContainsRune("<>{}|\\^[]`,()", rune(b))) {
input.UnreadByte()
break
} else {
currBuf.WriteByte(b)
}
}
href, text, class := LinkParts(addrBuf.String(), displayBuf.String(), hyphaName)
return fmt.Sprintf(`<a href="%s" class="%s">%s</a>`, href, class, html.EscapeString(text))
}
// getTextNode splits the `input` into two parts `textNode` and `rest` by the first encountered rune that resembles a span tag. If there is none, `textNode = input`, `rest = ""`. It handles escaping with backslash.
func getTextNode(input *bytes.Buffer) string {
var (
textNodeBuffer = bytes.Buffer{}
escaping = false
startsWith = func(t string) bool {
return bytes.HasPrefix(input.Bytes(), []byte(t))
}
couldBeLinkStart = func() bool {
return startsWith("https://") || startsWith("http://") || startsWith("gemini://") || startsWith("gopher://") || startsWith("ftp://")
}
)
// Always read the first byte in advance to avoid endless loops that kill computers (sad experience)
if input.Len() != 0 {
b, _ := input.ReadByte()
textNodeBuffer.WriteByte(b)
}
for input.Len() != 0 {
// Assume no error is possible because we check for length
b, _ := input.ReadByte()
if escaping {
textNodeBuffer.WriteByte(b)
escaping = false
} else if b == '\\' {
escaping = true
} else if strings.IndexByte("/*`^,![~", b) >= 0 {
input.UnreadByte()
break
} else if couldBeLinkStart() {
textNodeBuffer.WriteByte(b)
break
} else {
textNodeBuffer.WriteByte(b)
}
}
return textNodeBuffer.String()
}
func ParagraphToHtml(hyphaName, input string) string {
var (
p = bytes.NewBufferString(input)
ret strings.Builder
// true = tag is opened, false = tag is not opened
tagState = map[spanTokenType]bool{
spanItalic: false,
spanBold: false,
spanMono: false,
spanSuper: false,
spanSub: false,
spanMark: false,
spanLink: false,
}
startsWith = func(t string) bool {
return bytes.HasPrefix(p.Bytes(), []byte(t))
}
noTagsActive = func() bool {
return !(tagState[spanItalic] || tagState[spanBold] || tagState[spanMono] || tagState[spanSuper] || tagState[spanSub] || tagState[spanMark] || tagState[spanLink])
}
)
for p.Len() != 0 {
switch {
case startsWith("//"):
ret.WriteString(tagFromState(spanItalic, tagState, "em", "//"))
p.Next(2)
case startsWith("**"):
ret.WriteString(tagFromState(spanBold, tagState, "strong", "**"))
p.Next(2)
case startsWith("`"):
ret.WriteString(tagFromState(spanMono, tagState, "code", "`"))
p.Next(1)
case startsWith("^"):
ret.WriteString(tagFromState(spanSuper, tagState, "sup", "^"))
p.Next(1)
case startsWith(",,"):
ret.WriteString(tagFromState(spanSub, tagState, "sub", ",,"))
p.Next(2)
case startsWith("!!"):
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
p.Next(2)
case startsWith("~~"):
ret.WriteString(tagFromState(spanMark, tagState, "s", "~~"))
p.Next(2)
case startsWith("[["):
ret.WriteString(getLinkNode(p, hyphaName, true))
case (startsWith("https://") || startsWith("http://") || startsWith("gemini://") || startsWith("gopher://") || startsWith("ftp://")) && noTagsActive():
ret.WriteString(getLinkNode(p, hyphaName, false))
default:
ret.WriteString(html.EscapeString(getTextNode(p)))
}
}
for stt, open := range tagState {
if open {
switch stt {
case spanItalic:
ret.WriteString(tagFromState(spanItalic, tagState, "em", "//"))
case spanBold:
ret.WriteString(tagFromState(spanBold, tagState, "strong", "**"))
case spanMono:
ret.WriteString(tagFromState(spanMono, tagState, "code", "`"))
case spanSuper:
ret.WriteString(tagFromState(spanSuper, tagState, "sup", "^"))
case spanSub:
ret.WriteString(tagFromState(spanSub, tagState, "sub", ",,"))
case spanMark:
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
case spanStrike:
ret.WriteString(tagFromState(spanMark, tagState, "s", "~~"))
case spanLink:
ret.WriteString(tagFromState(spanLink, tagState, "a", "[["))
}
}
}
return ret.String()
}

View File

@ -1,28 +0,0 @@
package markup
const maxRecursionLevel = 3
func Parse(ast []Line, from, to int, recursionLevel int) (html string) {
if recursionLevel > maxRecursionLevel {
return "Transclusion depth limit"
}
for _, line := range ast {
if line.id >= from && (line.id <= to || to == 0) || line.id == -1 {
switch v := line.contents.(type) {
case Transclusion:
html += Transclude(v, recursionLevel)
case Img:
html += v.ToHtml()
case Table:
html += v.asHtml()
case *List:
html += v.RenderAsHtml()
case string:
html += v
default:
html += "<b class='error'>Unknown element.</b>"
}
}
}
return html
}

View File

@ -1,230 +0,0 @@
package markup
import (
"fmt"
"regexp"
"strings"
"unicode"
)
var tableRe = regexp.MustCompile(`^table\s+{`)
func MatchesTable(line string) bool {
return tableRe.MatchString(line)
}
func TableFromFirstLine(line, hyphaName string) *Table {
return &Table{
hyphaName: hyphaName,
caption: line[strings.IndexRune(line, '{')+1:],
rows: make([]*tableRow, 0),
}
}
func (t *Table) Process(line string) (shouldGoBackToNormal bool) {
if strings.TrimSpace(line) == "}" && !t.inMultiline {
return true
}
if !t.inMultiline {
t.pushRow()
}
var (
inLink bool
skipNext bool
escaping bool
lookingForNonSpace = !t.inMultiline
countingColspan bool
)
for i, r := range line {
switch {
case skipNext:
skipNext = false
continue
case lookingForNonSpace && unicode.IsSpace(r):
case lookingForNonSpace && (r == '!' || r == '|'):
t.currCellMarker = r
t.currColspan = 1
lookingForNonSpace = false
countingColspan = true
case lookingForNonSpace:
t.currCellMarker = '^' // ^ represents implicit |, not part of syntax
t.currColspan = 1
lookingForNonSpace = false
t.currCellBuilder.WriteRune(r)
case escaping:
t.currCellBuilder.WriteRune(r)
case inLink && r == ']' && len(line)-1 > i && line[i+1] == ']':
t.currCellBuilder.WriteString("]]")
inLink = false
skipNext = true
case inLink:
t.currCellBuilder.WriteRune(r)
case t.inMultiline && r == '}':
t.inMultiline = false
case t.inMultiline && i == len(line)-1:
t.currCellBuilder.WriteRune('\n')
case t.inMultiline:
t.currCellBuilder.WriteRune(r)
// Not in multiline:
case (r == '|' || r == '!') && !countingColspan:
t.pushCell()
t.currCellMarker = r
t.currColspan = 1
countingColspan = true
case r == t.currCellMarker && (r == '|' || r == '!') && countingColspan:
t.currColspan++
case r == '{':
t.inMultiline = true
countingColspan = false
case r == '[' && len(line)-1 > i && line[i+1] == '[':
t.currCellBuilder.WriteString("[[")
inLink = true
skipNext = true
case i == len(line)-1:
t.pushCell()
default:
t.currCellBuilder.WriteRune(r)
countingColspan = false
}
}
return false
}
type Table struct {
// data
hyphaName string
caption string
rows []*tableRow
// state
inMultiline bool
// tmp
currCellMarker rune
currColspan uint
currCellBuilder strings.Builder
}
func (t *Table) pushRow() {
t.rows = append(t.rows, &tableRow{
cells: make([]*tableCell, 0),
})
}
func (t *Table) pushCell() {
tc := &tableCell{
content: t.currCellBuilder.String(),
colspan: t.currColspan,
}
switch t.currCellMarker {
case '|', '^':
tc.kind = tableCellDatum
case '!':
tc.kind = tableCellHeader
}
// We expect the table to have at least one row ready, so no nil-checking
tr := t.rows[len(t.rows)-1]
tr.cells = append(tr.cells, tc)
t.currCellBuilder = strings.Builder{}
}
func (t *Table) asHtml() (html string) {
if t.caption != "" {
html += fmt.Sprintf("<caption>%s</caption>", t.caption)
}
if len(t.rows) > 0 && t.rows[0].looksLikeThead() {
html += fmt.Sprintf("<thead>%s</thead>", t.rows[0].asHtml(t.hyphaName))
t.rows = t.rows[1:]
}
html += "\n<tbody>\n"
for _, tr := range t.rows {
html += tr.asHtml(t.hyphaName)
}
return fmt.Sprintf(`<table>%s</tbody></table>`, html)
}
type tableRow struct {
cells []*tableCell
}
func (tr *tableRow) asHtml(hyphaName string) (html string) {
for _, tc := range tr.cells {
html += tc.asHtml(hyphaName)
}
return fmt.Sprintf("<tr>%s</tr>\n", html)
}
// Most likely, rows with more than two header cells are theads. I allow one extra datum cell for tables like this:
// | ! a ! b
// ! c | d | e
// ! f | g | h
func (tr *tableRow) looksLikeThead() bool {
var (
headerAmount = 0
datumAmount = 0
)
for _, tc := range tr.cells {
switch tc.kind {
case tableCellHeader:
headerAmount++
case tableCellDatum:
datumAmount++
}
}
return headerAmount >= 2 && datumAmount <= 1
}
type tableCell struct {
kind tableCellKind
colspan uint
content string
}
func (tc *tableCell) asHtml(hyphaName string) string {
return fmt.Sprintf(
"<%[1]s %[2]s>%[3]s</%[1]s>\n",
tc.kind.tagName(),
tc.colspanAttribute(),
tc.contentAsHtml(hyphaName),
)
}
func (tc *tableCell) colspanAttribute() string {
if tc.colspan <= 1 {
return ""
}
return fmt.Sprintf(`colspan="%d"`, tc.colspan)
}
func (tc *tableCell) contentAsHtml(hyphaName string) (html string) {
for _, line := range strings.Split(tc.content, "\n") {
if line = strings.TrimSpace(line); line != "" {
if html != "" {
html += `<br>`
}
html += ParagraphToHtml(hyphaName, line)
}
}
return html
}
type tableCellKind int
const (
tableCellUnknown tableCellKind = iota
tableCellHeader
tableCellDatum
)
func (tck tableCellKind) tagName() string {
switch tck {
case tableCellHeader:
return "th"
case tableCellDatum:
return "td"
default:
return "p"
}
}

View File

@ -1,38 +0,0 @@
# 1
## 2
### 3
> quote
* li 1
* li 2
text
more text
=> Pear some link
* li\n"+
```alt text goes here
=> preformatted text
where markup is not lexed
```it ends here"
=>linking
text
```
()
/\
```
<= Apple : 1..3
img {
hypha1
hypha2|
hypha3| 60
hypha4| { line1
line2
} this is ignored
hypha5| {
state of minnesota
}
}

View File

@ -1,12 +0,0 @@
package markup
import (
"strings"
)
// Function that returns a function that can strip `prefix` and trim whitespace when called.
func remover(prefix string) func(string) string {
return func(l string) string {
return strings.TrimSpace(strings.TrimPrefix(l, prefix))
}
}

View File

@ -1,108 +0,0 @@
package markup
import (
"fmt"
"github.com/bouncepaw/mycorrhiza/util"
"path"
"strconv"
"strings"
)
const xclError = -9
// Transclusion is used by markup parser to remember what hyphae shall be transcluded.
type Transclusion struct {
name string
from int // inclusive
to int // inclusive
}
// Transclude transcludes `xcl` and returns html representation.
func Transclude(xcl Transclusion, recursionLevel int) (html string) {
recursionLevel++
tmptOk := `<section class="transclusion transclusion_ok">
<a class="transclusion__link" href="/page/%s">%s</a>
<div class="transclusion__content">%s</div>
</section>`
tmptFailed := `<section class="transclusion transclusion_failed">
<p class="error">Hypha <a class="wikilink_new" href="/page/%s">%s</a> does not exist</p>
</section>`
if xcl.from == xclError || xcl.to == xclError || xcl.from > xcl.to {
return fmt.Sprintf(tmptFailed, xcl.name, xcl.name)
}
rawText, binaryHtml, err := HyphaAccess(xcl.name)
if err != nil {
return fmt.Sprintf(tmptFailed, xcl.name, xcl.name)
}
md := Doc(xcl.name, rawText)
xclText := Parse(md.lex(), xcl.from, xcl.to, recursionLevel)
return fmt.Sprintf(tmptOk, xcl.name, xcl.name, binaryHtml+xclText)
}
/* Grammar from hypha transclusion:
transclusion_line ::= transclusion_token hypha_name LWS* [":" LWS* range LWS*]
transclusion_token ::= "<=" LWS+
hypha_name ::= canonical_name | noncanonical_name
range ::= id | (from_id two_dots to_id) | (from_id two_dots) | (two_dots to_id)
two_dots ::= ".."
*/
func parseTransclusion(line, hyphaName string) (xclusion Transclusion) {
line = strings.TrimSpace(remover("<=")(line))
if line == "" {
return Transclusion{"", xclError, xclError}
}
if strings.ContainsRune(line, ':') {
parts := strings.SplitN(line, ":", 2)
xclusion.name = xclCanonicalName(hyphaName, strings.TrimSpace(parts[0]))
selector := strings.TrimSpace(parts[1])
xclusion.from, xclusion.to = parseSelector(selector)
} else {
xclusion.name = xclCanonicalName(hyphaName, strings.TrimSpace(line))
}
return xclusion
}
func xclCanonicalName(hyphaName, xclName string) string {
switch {
case strings.HasPrefix(xclName, "./"):
return util.CanonicalName(path.Join(hyphaName, strings.TrimPrefix(xclName, "./")))
case strings.HasPrefix(xclName, "../"):
return util.CanonicalName(path.Join(path.Dir(hyphaName), strings.TrimPrefix(xclName, "../")))
default:
return util.CanonicalName(xclName)
}
}
// At this point:
// selector ::= id
// | from ".."
// | from ".." to
// | ".." to
// If it is not, return (xclError, xclError).
func parseSelector(selector string) (from, to int) {
if selector == "" {
return 0, 0
}
if strings.Contains(selector, "..") {
parts := strings.Split(selector, "..")
var (
fromStr = strings.TrimSpace(parts[0])
from, fromErr = strconv.Atoi(fromStr)
toStr = strings.TrimSpace(parts[1])
to, toErr = strconv.Atoi(toStr)
)
if fromStr == "" && toStr == "" {
return 0, 0
}
if fromErr == nil || toErr == nil {
return from, to
}
} else if id, err := strconv.Atoi(selector); err == nil {
return id, id
}
return xclError, xclError
}

View File

@ -2,11 +2,11 @@ package shroom
import (
"errors"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/views"
"github.com/bouncepaw/mycomarkup/legacy"
)
func init() {
@ -29,10 +29,4 @@ func init() {
λ(h.Name)
}
}
markup.HyphaImageForOG = func(hyphaName string) string {
if h := hyphae.ByName(hyphaName); h.Exists && h.BinaryPath != "" {
return cfg.URL + "/binary/" + hyphaName
}
return cfg.URL + "/favicon.ico"
}
}

View File

@ -1,12 +1,13 @@
package shroom
import (
"github.com/bouncepaw/mycorrhiza/cfg"
"io/ioutil"
"os"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycomarkup/blocks"
)
// FetchTextPart tries to read text file of the given hypha. If there is no file, empty string is returned.
@ -32,7 +33,7 @@ func SetHeaderLinks() {
cfg.SetDefaultHeaderLinks()
} else {
text := string(contents)
cfg.ParseHeaderLinks(text, markup.Rocketlink)
cfg.ParseHeaderLinks(text, blocks.Rocketlink)
}
}
}

View File

@ -7,11 +7,12 @@ import (
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/shroom"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views"
"github.com/bouncepaw/mycomarkup/legacy"
)
func initMutators() {

View File

@ -11,11 +11,12 @@ import (
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/mimetype"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views"
"github.com/bouncepaw/mycomarkup/legacy"
)
func initReaders() {