all repos — honk @ 9ed376881433161026dbb0c49b20408850ab83db

my fork of honk

markitzero.go (view raw)

  1//
  2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
  3//
  4// Permission to use, copy, modify, and distribute this software for any
  5// purpose with or without fee is hereby granted, provided that the above
  6// copyright notice and this permission notice appear in all copies.
  7//
  8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 15
 16package main
 17
 18import (
 19	"fmt"
 20	"regexp"
 21	"strings"
 22
 23	"golang.org/x/net/html"
 24	"humungus.tedunangst.com/r/webs/synlight"
 25)
 26
 27var re_bolder = regexp.MustCompile(`(^|\W)\*\*((?s:.*?))\*\*($|\W)`)
 28var re_italicer = regexp.MustCompile(`(^|\W)\*((?s:.*?))\*($|\W)`)
 29var re_bigcoder = regexp.MustCompile("```(.*)\n?((?s:.*?))\n?```\n?")
 30var re_coder = regexp.MustCompile("`([^`]*)`")
 31var re_quoter = regexp.MustCompile(`(?m:^&gt; (.*)\n?)`)
 32var re_link = regexp.MustCompile(`.?.?https?://[^\s"]+[\w/)!]`)
 33var re_zerolink = regexp.MustCompile(`\[([^]]*)\]\(([^)]*\)?)\)`)
 34var re_imgfix = regexp.MustCompile(`<img ([^>]*)>`)
 35var re_lister = regexp.MustCompile(`((^|\n)(\+|-).*)+\n?`)
 36
 37var lighter = synlight.New(synlight.Options{Format: synlight.HTML})
 38
 39func markitzero(s string) string {
 40	// prepare the string
 41	s = strings.TrimSpace(s)
 42	s = strings.Replace(s, "\r", "", -1)
 43
 44	// save away the code blocks so we don't mess them up further
 45	var bigcodes, lilcodes, images []string
 46	s = re_bigcoder.ReplaceAllStringFunc(s, func(code string) string {
 47		bigcodes = append(bigcodes, code)
 48		return "``````"
 49	})
 50	s = re_coder.ReplaceAllStringFunc(s, func(code string) string {
 51		lilcodes = append(lilcodes, code)
 52		return "`x`"
 53	})
 54	s = re_imgfix.ReplaceAllStringFunc(s, func(img string) string {
 55		images = append(images, img)
 56		return "<img x>"
 57	})
 58
 59	// fewer side effects than html.EscapeString
 60	buf := make([]byte, 0, len(s))
 61	for _, c := range []byte(s) {
 62		switch c {
 63		case '&':
 64			buf = append(buf, []byte("&amp;")...)
 65		case '<':
 66			buf = append(buf, []byte("&lt;")...)
 67		case '>':
 68			buf = append(buf, []byte("&gt;")...)
 69		default:
 70			buf = append(buf, c)
 71		}
 72	}
 73	s = string(buf)
 74
 75	// mark it zero
 76	s = re_link.ReplaceAllStringFunc(s, linkreplacer)
 77	s = re_zerolink.ReplaceAllString(s, `<a href="$2">$1</a>`)
 78	s = re_bolder.ReplaceAllString(s, "$1<b>$2</b>$3")
 79	s = re_italicer.ReplaceAllString(s, "$1<i>$2</i>$3")
 80	s = re_quoter.ReplaceAllString(s, "<blockquote>$1</blockquote><p>")
 81
 82	s = re_lister.ReplaceAllStringFunc(s, func(m string) string {
 83		m = strings.Trim(m, "\n")
 84		items := strings.Split(m, "\n")
 85		r := "<ul>"
 86		for _, item := range items {
 87			r += "<li>" + strings.Trim(item[1:], " ")
 88		}
 89		r += "</ul><p>"
 90		return r
 91	})
 92
 93	// restore images
 94	s = strings.Replace(s, "&lt;img x&gt;", "<img x>", -1)
 95	s = re_imgfix.ReplaceAllStringFunc(s, func(string) string {
 96		img := images[0]
 97		images = images[1:]
 98		return img
 99	})
100
101	// now restore the code blocks
102	s = re_coder.ReplaceAllStringFunc(s, func(string) string {
103		code := lilcodes[0]
104		lilcodes = lilcodes[1:]
105		code = html.EscapeString(code)
106		return code
107	})
108	s = re_bigcoder.ReplaceAllStringFunc(s, func(string) string {
109		code := bigcodes[0]
110		bigcodes = bigcodes[1:]
111		m := re_bigcoder.FindStringSubmatch(code)
112		return "<pre><code>" + lighter.HighlightString(m[2], m[1]) + "</code></pre><p>"
113	})
114	s = re_coder.ReplaceAllString(s, "<code>$1</code>")
115
116	// some final fixups
117	s = strings.Replace(s, "\n", "<br>", -1)
118	s = strings.Replace(s, "<br><blockquote>", "<blockquote>", -1)
119	s = strings.Replace(s, "<br><pre>", "<pre>", -1)
120	s = strings.Replace(s, "<br><ul>", "<ul>", -1)
121	s = strings.Replace(s, "<p><br>", "<p>", -1)
122	return s
123}
124
125func linkreplacer(url string) string {
126	if url[0:2] == "](" {
127		return url
128	}
129	prefix := ""
130	for !strings.HasPrefix(url, "http") {
131		prefix += url[0:1]
132		url = url[1:]
133	}
134	addparen := false
135	adddot := false
136	if strings.HasSuffix(url, ")") && strings.IndexByte(url, '(') == -1 {
137		url = url[:len(url)-1]
138		addparen = true
139	}
140	if strings.HasSuffix(url, ".") {
141		url = url[:len(url)-1]
142		adddot = true
143	}
144	url = fmt.Sprintf(`<a href="%s">%s</a>`, url, url)
145	if adddot {
146		url += "."
147	}
148	if addparen {
149		url += ")"
150	}
151	return prefix + url
152}