-
Notifications
You must be signed in to change notification settings - Fork 3
/
utils.go
186 lines (171 loc) · 4.86 KB
/
utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package html
import (
"fmt"
"html"
"regexp"
"strconv"
"strings"
"unicode"
"github.com/thecsw/darkness/emilia"
"github.com/thecsw/darkness/internals"
)
// quotesReplace is the map to replace
var quotesReplace = map[string]string{
"'s": "’s",
"'m": "’m",
"n't": "n’t",
"'re": "’re",
"'ll": "’ll",
"``": "“",
"''": "”",
"--": "—",
}
// fancyQuotes replaces boring single and double quotes with fancier Unicode versions
func fancyQuotes(text string) string {
for k, v := range quotesReplace {
text = strings.ReplaceAll(text, k, v)
}
return text
}
// markupHTMLMapping maps the regex markup to html replacements
var markupHTMLMapping = map[*regexp.Regexp]string{
internals.ItalicText: `$1<em>$2</em>$3`,
internals.BoldText: `$1<strong>$2</strong>$3`,
internals.VerbatimText: `$1<code>$2</code>$3`,
internals.StrikethroughText: `$1<s>$2</s>$3`,
internals.UnderlineText: `$1<u>$2</u>$3`,
}
// markupHTML replaces the markup regexes defined in internal with HTML tags
func markupHTML(text string) string {
// To make bold italics, it has to be wrapped in /*...*/
// instead of */.../*
text = internals.BoldItalicTextBegin.ReplaceAllString(text, `$1/*`)
text = internals.BoldItalicTextEnd.ReplaceAllString(text, `*/$1`)
for source, replacement := range markupHTMLMapping {
text = source.ReplaceAllString(text, replacement)
}
// Double pass for giggles
for source, replacement := range markupHTMLMapping {
text = source.ReplaceAllString(text, replacement)
}
text = internals.KeyboardRegexp.ReplaceAllString(text, `<kbd>$1</kbd>`)
text = internals.NewLineRegexp.ReplaceAllString(text, `$1<br>`)
return text
}
// processText returns a properly formatted HTML of a text
func processText(text string) string {
text = html.EscapeString(fancyQuotes(text))
text = markupHTML(text)
text = strings.ReplaceAll(text, "◼", `<b style="color:#ba3925">◼︎</b>`)
text = internals.LinkRegexp.ReplaceAllString(text, `<a href="$1">$2</a>`)
//text = internals.MathRegexp.ReplaceAllString(text, `\($1\)`)
text = internals.FootnotePostProcessingRegexp.ReplaceAllStringFunc(text, func(what string) string {
num, _ := strconv.Atoi(strings.ReplaceAll(what, "!", ""))
// get the footnote HTML body
footnote := fmt.Sprintf(
`<a id="_footnoteref_%d" class="footnote" href="#_footnotedef_%d" title="View footnote.">%s</a>`,
num, num, footnoteLabel(num))
// Decide if we need to wrap the footnote in square brackets
if emilia.Config.Website.FootnoteBrackets {
footnote = "[" + footnote + "]"
}
return `
<sup class="footnote">` + footnote + `</sup>
`
})
return strings.TrimSpace(text)
}
// processTitle returns a properly formatted HTML of a title
func processTitle(title string) string {
title = fancyQuotes(title)
title = markupHTML(title)
title = internals.MathRegexp.ReplaceAllString(title, `\($1\)`)
return title
}
// flattenFormatting returns a plain-text to be fit into the description
func flattenFormatting(what string) string {
what = fancyQuotes(what)
// To make bold italics, it has to be wrapped in /*...*/
// instead of */.../*
what = internals.BoldItalicTextBegin.ReplaceAllString(what, `$1/*`)
what = internals.BoldItalicTextEnd.ReplaceAllString(what, `*/$1`)
for source := range markupHTMLMapping {
what = source.ReplaceAllString(what, `$1$2$3`)
}
what = internals.KeyboardRegexp.ReplaceAllString(what, `$1`)
what = internals.NewLineRegexp.ReplaceAllString(what, `$1`)
return what
}
// extractID returns a properly formatted ID for a heading title
func extractID(heading string) string {
// Check if heading is a link
match := internals.LinkRegexp.FindStringSubmatch(heading)
if len(match) > 0 {
heading = match[2] // 0 is whole match, 1 is link, 2 is title
}
res := "_"
for _, c := range heading {
if unicode.IsSpace(c) || unicode.IsPunct(c) || unicode.IsSymbol(c) {
res += "_"
continue
}
if c <= unicode.MaxASCII {
res += string(unicode.ToLower(c))
}
}
return res
}
var (
numToRomanMap = map[int]string{
1: "I",
4: "IV",
5: "V",
9: "IX",
10: "X",
40: "XL",
50: "L",
90: "XC",
100: "C",
400: "CD",
500: "D",
900: "CM",
1000: "M",
}
romanNumOrder = []int{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}
numToRomanSmall = map[int]string{
1: "I",
2: "II",
3: "III",
4: "IV",
5: "V",
6: "VI",
7: "VII",
8: "VIII",
9: "IX",
10: "X",
11: "XI",
12: "XII",
13: "XIII",
14: "XIV",
}
)
// numberToRoman converts an integer to a roman numeral
// adapted from https://pencilprogrammer.com/python-programs/convert-integer-to-roman-numerals/
func numberToRoman(num int) string {
if num <= 14 {
return numToRomanSmall[num]
}
res := ""
for _, v := range romanNumOrder {
if num != 0 {
quot := num / v
if quot != 0 {
for x := 0; x < quot; x++ {
res += numToRomanMap[v]
}
}
num %= v
}
}
return res
}