move markdown to another module
This commit is contained in:
parent
51c3404e3c
commit
1ec15a4738
2
go.mod
2
go.mod
|
@ -11,3 +11,5 @@ require (
|
||||||
humungus.tedunangst.com/r/go-sqlite3 v1.1.3
|
humungus.tedunangst.com/r/go-sqlite3 v1.1.3
|
||||||
humungus.tedunangst.com/r/webs v0.6.41
|
humungus.tedunangst.com/r/webs v0.6.41
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace humungus.tedunangst.com/r/webs => ../webs
|
||||||
|
|
196
markitzero.go
196
markitzero.go
|
@ -16,200 +16,10 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
mz "humungus.tedunangst.com/r/webs/markitzero"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
|
||||||
"humungus.tedunangst.com/r/webs/synlight"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var re_bolder = regexp.MustCompile(`(^|\W)\*\*((?s:.*?))\*\*($|\W)`)
|
|
||||||
var re_italicer = regexp.MustCompile(`(^|\W)\*((?s:.*?))\*($|\W)`)
|
|
||||||
var re_bigcoder = regexp.MustCompile("```(.*)\n?((?s:.*?))\n?```\n?")
|
|
||||||
var re_coder = regexp.MustCompile("`([^`]*)`")
|
|
||||||
var re_quoter = regexp.MustCompile(`(?m:^> (.*)(\n- ?(.*))?\n?)`)
|
|
||||||
var re_reciter = regexp.MustCompile(`(<cite><a href=".*?">)https://twitter.com/([^/]+)/.*?(</a></cite>)`)
|
|
||||||
var re_link = regexp.MustCompile(`.?.?https?://[^\s"]+[\w/)!]`)
|
|
||||||
var re_zerolink = regexp.MustCompile(`\[([^]]*)\]\(([^)]*\)?)\)`)
|
|
||||||
var re_imgfix = regexp.MustCompile(`<img ([^>]*)>`)
|
|
||||||
var re_lister = regexp.MustCompile(`((^|\n)(\+|-).*)+\n?`)
|
|
||||||
var re_tabler = regexp.MustCompile(`((^|\n)\|.*)+\n?`)
|
|
||||||
var re_header = regexp.MustCompile(`(^|\n)(#+) (.*)\n?`)
|
|
||||||
|
|
||||||
var lighter = synlight.New(synlight.Options{Format: synlight.HTML})
|
|
||||||
|
|
||||||
var allowInlineHtml = false
|
|
||||||
|
|
||||||
func markitzero(s string) string {
|
func markitzero(s string) string {
|
||||||
// prepare the string
|
var marker mz.Marker
|
||||||
s = strings.TrimSpace(s)
|
return marker.Mark(s)
|
||||||
s = strings.Replace(s, "\r", "", -1)
|
|
||||||
|
|
||||||
codeword := "`elided big code`"
|
|
||||||
|
|
||||||
// save away the code blocks so we don't mess them up further
|
|
||||||
var bigcodes, lilcodes, images []string
|
|
||||||
s = re_bigcoder.ReplaceAllStringFunc(s, func(code string) string {
|
|
||||||
bigcodes = append(bigcodes, code)
|
|
||||||
return codeword
|
|
||||||
})
|
|
||||||
s = re_coder.ReplaceAllStringFunc(s, func(code string) string {
|
|
||||||
lilcodes = append(lilcodes, code)
|
|
||||||
return "`x`"
|
|
||||||
})
|
|
||||||
s = re_imgfix.ReplaceAllStringFunc(s, func(img string) string {
|
|
||||||
images = append(images, img)
|
|
||||||
return "<img x>"
|
|
||||||
})
|
|
||||||
|
|
||||||
// fewer side effects than html.EscapeString
|
|
||||||
buf := make([]byte, 0, len(s))
|
|
||||||
for _, c := range []byte(s) {
|
|
||||||
switch c {
|
|
||||||
case '&':
|
|
||||||
buf = append(buf, []byte("&")...)
|
|
||||||
case '<':
|
|
||||||
buf = append(buf, []byte("<")...)
|
|
||||||
case '>':
|
|
||||||
buf = append(buf, []byte(">")...)
|
|
||||||
default:
|
|
||||||
buf = append(buf, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s = string(buf)
|
|
||||||
|
|
||||||
// mark it zero
|
|
||||||
if strings.Contains(s, "http") {
|
|
||||||
s = re_link.ReplaceAllStringFunc(s, linkreplacer)
|
|
||||||
}
|
|
||||||
s = re_zerolink.ReplaceAllString(s, `<a href="$2">$1</a>`)
|
|
||||||
if strings.Contains(s, "*") {
|
|
||||||
s = re_bolder.ReplaceAllString(s, "$1<b>$2</b>$3")
|
|
||||||
s = re_italicer.ReplaceAllString(s, "$1<i>$2</i>$3")
|
|
||||||
}
|
|
||||||
s = re_quoter.ReplaceAllString(s, "<blockquote>$1<br><cite>$3</cite></blockquote><p>")
|
|
||||||
s = re_reciter.ReplaceAllString(s, "$1$2$3")
|
|
||||||
s = strings.Replace(s, "\n---\n", "<hr><p>", -1)
|
|
||||||
|
|
||||||
s = re_lister.ReplaceAllStringFunc(s, func(m string) string {
|
|
||||||
m = strings.Trim(m, "\n")
|
|
||||||
items := strings.Split(m, "\n")
|
|
||||||
r := "<ul>"
|
|
||||||
for _, item := range items {
|
|
||||||
r += "<li>" + strings.Trim(item[1:], " ")
|
|
||||||
}
|
|
||||||
r += "</ul><p>"
|
|
||||||
return r
|
|
||||||
})
|
|
||||||
s = re_tabler.ReplaceAllStringFunc(s, func(m string) string {
|
|
||||||
m = strings.Trim(m, "\n")
|
|
||||||
rows := strings.Split(m, "\n")
|
|
||||||
var r strings.Builder
|
|
||||||
r.WriteString("<table>")
|
|
||||||
alignments := make(map[int]string)
|
|
||||||
for _, row := range rows {
|
|
||||||
hastr := false
|
|
||||||
cells := strings.Split(row, "|")
|
|
||||||
for i, cell := range cells {
|
|
||||||
cell = strings.TrimSpace(cell)
|
|
||||||
if cell == "" && (i == 0 || i == len(cells)-1) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch cell {
|
|
||||||
case ":---":
|
|
||||||
alignments[i] = ` style="text-align: left"`
|
|
||||||
continue
|
|
||||||
case ":---:":
|
|
||||||
alignments[i] = ` style="text-align: center"`
|
|
||||||
continue
|
|
||||||
case "---:":
|
|
||||||
alignments[i] = ` style="text-align: right"`
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !hastr {
|
|
||||||
r.WriteString("<tr>")
|
|
||||||
hastr = true
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&r, "<td%s>", alignments[i])
|
|
||||||
r.WriteString(cell)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.WriteString("</table><p>")
|
|
||||||
return r.String()
|
|
||||||
})
|
|
||||||
s = re_header.ReplaceAllStringFunc(s, func(s string) string {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
m := re_header.FindStringSubmatch(s)
|
|
||||||
num := len(m[2])
|
|
||||||
return fmt.Sprintf("<h%d>%s</h%d><p>", num, m[3], num)
|
|
||||||
})
|
|
||||||
|
|
||||||
// restore images
|
|
||||||
s = strings.Replace(s, "<img x>", "<img x>", -1)
|
|
||||||
s = re_imgfix.ReplaceAllStringFunc(s, func(string) string {
|
|
||||||
img := images[0]
|
|
||||||
images = images[1:]
|
|
||||||
return img
|
|
||||||
})
|
|
||||||
|
|
||||||
s = strings.Replace(s, "\n\n", "<p>", -1)
|
|
||||||
s = strings.Replace(s, "\n", "<br>", -1)
|
|
||||||
|
|
||||||
// now restore the code blocks
|
|
||||||
s = re_coder.ReplaceAllStringFunc(s, func(string) string {
|
|
||||||
code := lilcodes[0]
|
|
||||||
lilcodes = lilcodes[1:]
|
|
||||||
if code == codeword && len(bigcodes) > 0 {
|
|
||||||
code := bigcodes[0]
|
|
||||||
bigcodes = bigcodes[1:]
|
|
||||||
m := re_bigcoder.FindStringSubmatch(code)
|
|
||||||
if allowInlineHtml && m[1] == "inlinehtml" {
|
|
||||||
return m[2]
|
|
||||||
}
|
|
||||||
return "<pre><code>" + lighter.HighlightString(m[2], m[1]) + "</code></pre><p>"
|
|
||||||
}
|
|
||||||
code = html.EscapeString(code)
|
|
||||||
return code
|
|
||||||
})
|
|
||||||
|
|
||||||
s = re_coder.ReplaceAllString(s, "<code>$1</code>")
|
|
||||||
|
|
||||||
// some final fixups
|
|
||||||
s = strings.Replace(s, "<br><blockquote>", "<blockquote>", -1)
|
|
||||||
s = strings.Replace(s, "<br><cite></cite>", "", -1)
|
|
||||||
s = strings.Replace(s, "<br><pre>", "<pre>", -1)
|
|
||||||
s = strings.Replace(s, "<br><ul>", "<ul>", -1)
|
|
||||||
s = strings.Replace(s, "<br><table>", "<table>", -1)
|
|
||||||
s = strings.Replace(s, "<p><br>", "<p>", -1)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func linkreplacer(url string) string {
|
|
||||||
if url[0:2] == "](" {
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
prefix := ""
|
|
||||||
for !strings.HasPrefix(url, "http") {
|
|
||||||
prefix += url[0:1]
|
|
||||||
url = url[1:]
|
|
||||||
}
|
|
||||||
addparen := false
|
|
||||||
adddot := false
|
|
||||||
if strings.HasSuffix(url, ")") && strings.IndexByte(url, '(') == -1 {
|
|
||||||
url = url[:len(url)-1]
|
|
||||||
addparen = true
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(url, ".") {
|
|
||||||
url = url[:len(url)-1]
|
|
||||||
adddot = true
|
|
||||||
}
|
|
||||||
url = fmt.Sprintf(`<a href="%s">%s</a>`, url, url)
|
|
||||||
if adddot {
|
|
||||||
url += "."
|
|
||||||
}
|
|
||||||
if addparen {
|
|
||||||
url += ")"
|
|
||||||
}
|
|
||||||
return prefix + url
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,149 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func doonezerotest(t *testing.T, input, output string) {
|
|
||||||
result := markitzero(input)
|
|
||||||
if result != output {
|
|
||||||
t.Errorf("\nexpected:\n%s\noutput:\n%s", output, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBasictest(t *testing.T) {
|
|
||||||
input := `link to https://example.com/ with **bold** text`
|
|
||||||
output := `link to <a href="https://example.com/">https://example.com/</a> with <b>bold</b> text`
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultibold(t *testing.T) {
|
|
||||||
input := `**in** out **in**`
|
|
||||||
output := `<b>in</b> out <b>in</b>`
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLinebreak1(t *testing.T) {
|
|
||||||
input := "hello\n> a quote\na comment"
|
|
||||||
output := "hello<blockquote>a quote</blockquote><p>a comment"
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLinebreak2(t *testing.T) {
|
|
||||||
input := "hello\n\n> a quote\n\na comment"
|
|
||||||
output := "hello<p><blockquote>a quote</blockquote><p>a comment"
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLinebreak3(t *testing.T) {
|
|
||||||
input := "hello\n\n```\nfunc(s string)\n```\n\ndoes it go?"
|
|
||||||
output := "hello<p><pre><code>func(s string)</code></pre><p>does it go?"
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCodeStyles(t *testing.T) {
|
|
||||||
input := "hello\n\n```go\nfunc(s string)\n```\n\ndoes it go?"
|
|
||||||
output := "hello<p><pre><code><span class=kw>func</span><span class=op>(</span>s <span class=tp>string</span><span class=op>)</span></code></pre><p>does it go?"
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSimplelink(t *testing.T) {
|
|
||||||
input := "This is a [link](https://example.com)."
|
|
||||||
output := `This is a <a href="https://example.com">link</a>.`
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSimplelink2(t *testing.T) {
|
|
||||||
input := "See (http://example.com) for examples."
|
|
||||||
output := `See (<a href="http://example.com">http://example.com</a>) for examples.`
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWikilink(t *testing.T) {
|
|
||||||
input := "I watched [Hackers](https://en.wikipedia.org/wiki/Hackers_(film))"
|
|
||||||
output := `I watched <a href="https://en.wikipedia.org/wiki/Hackers_(film)">Hackers</a>`
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQuotedlink(t *testing.T) {
|
|
||||||
input := `quoted "https://example.com/link" here`
|
|
||||||
output := `quoted "<a href="https://example.com/link">https://example.com/link</a>" here`
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLinkinQuote(t *testing.T) {
|
|
||||||
input := `> a quote and https://example.com/link`
|
|
||||||
output := `<blockquote>a quote and <a href="https://example.com/link">https://example.com/link</a></blockquote><p>`
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoldLink(t *testing.T) {
|
|
||||||
input := `**b https://example.com/link b**`
|
|
||||||
output := `<b>b <a href="https://example.com/link">https://example.com/link</a> b</b>`
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHonklink(t *testing.T) {
|
|
||||||
input := `https://en.wikipedia.org/wiki/Honk!`
|
|
||||||
output := `<a href="https://en.wikipedia.org/wiki/Honk!">https://en.wikipedia.org/wiki/Honk!</a>`
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImagelink(t *testing.T) {
|
|
||||||
input := `an image <img alt="caption" src="https://example.com/wherever"> and linked [<img src="there">](example.com)`
|
|
||||||
output := `an image <img alt="caption" src="https://example.com/wherever"> and linked <a href="example.com"><img src="there"></a>`
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLists(t *testing.T) {
|
|
||||||
input := `hello
|
|
||||||
+ a list
|
|
||||||
+ the list
|
|
||||||
|
|
||||||
para
|
|
||||||
|
|
||||||
- singleton`
|
|
||||||
output := `hello<ul><li>a list<li>the list</ul><p>para<ul><li>singleton</ul><p>`
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTables(t *testing.T) {
|
|
||||||
input := `hello
|
|
||||||
|
|
||||||
| col1 | col 2 |
|
|
||||||
| row2 | cell4 |
|
|
||||||
|
|
||||||
para
|
|
||||||
`
|
|
||||||
output := `hello<table><tr><td>col1<td>col 2<tr><td>row2<td>cell4</table><p>para`
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHeaders(t *testing.T) {
|
|
||||||
input := `hello
|
|
||||||
## fruits
|
|
||||||
Love 'em. Eat 'em.
|
|
||||||
`
|
|
||||||
output := `hello<h2>fruits</h2><p>Love 'em. Eat 'em.`
|
|
||||||
doonezerotest(t, input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
var benchData, simpleData string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
benchData = strings.Repeat("hello there sir. It is a **fine** day for some testing!\n", 100)
|
|
||||||
simpleData = strings.Repeat("just a few words\n", 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkModerateSize(b *testing.B) {
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
markitzero(benchData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSimpleData(b *testing.B) {
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
markitzero(simpleData)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue