package definition
import (
"html"
"net/url"
"strconv"
"strings"
"unicode"
)
func Render(path string) string {
u, err := url.Parse(path)
if err != nil {
return renderError("Invalid URL")
}
query, err := url.ParseQuery(u.RawQuery)
if err != nil {
return renderError("Invalid query")
}
page := 1
if raw := query.Get("page"); raw != "" {
page, err = strconv.Atoi(raw)
if err != nil || page < 1 {
return renderError("Invalid page")
}
}
key := query.Get("key")
output := "# AKKADIA Definition\n\n[All items](./definition:)\n\n" + renderSearchForm(key)
switch u.Path {
case "", "list":
total := GetKeyCount()
output += "## Items (" + strconv.Itoa(total) + ")\n\n"
keys := ListKeys(page, 20)
if len(keys) == 0 {
output += "No entries on this page.\n"
}
for _, key := range keys {
label := key
if itemKey, found := GetField(key, "key")["value"]; found && itemKey != "" {
label += " — " + itemKey
}
output += "* [" + renderLabel(label) + "](./definition:search?key=" + url.QueryEscape(key) + "&page=" + strconv.Itoa(page) + ")\n"
}
pages := 1
if total > 0 {
pages = (total-1)/20 + 1
}
output += "\nPage " + strconv.Itoa(page) + " / " + strconv.Itoa(pages) + "\n"
return output + renderPages("./definition:list?", page, total)
case "search":
if strings.TrimSpace(key) == "" {
return output + "Key is required\n"
}
fields, found := getFields(key)
if !found {
return output + "Entry not found\n"
}
output += "[Back to list](./definition:list?page=" + strconv.Itoa(page) + ")\n\n"
output += "## " + renderLabel(key) + "\n\n"
for _, field := range fields {
output += "### " + renderLabel(field.Key) + "\n\n"
link, err := url.Parse(field.Value)
isLink := err == nil && (link.Scheme == "http" || link.Scheme == "https") && link.Hostname() != "" && link.Opaque == ""
for _, r := range field.Value {
if unicode.IsSpace(r) || unicode.IsControl(r) {
isLink = false
}
}
if isLink {
// Angle-delimited Markdown destinations retain parentheses and ampersands.
target := strings.ReplaceAll(strings.ReplaceAll(link.String(), "<", "%3C"), ">", "%3E")
output += "[" + renderLabel(field.Value) + "](<" + target + ">)\n\n"
} else {
// A fence longer than any user fence keeps HTML and Markdown literal.
longest, run := 2, 0
for _, r := range field.Value {
if r == '`' {
run++
if run > longest {
longest = run
}
} else {
run = 0
}
}
fence := strings.Repeat("`", longest+1)
output += fence + "\n" + field.Value + "\n" + fence + "\n\n"
}
}
return output
default:
return renderError("Page not found")
}
}
func renderLabel(value string) string {
// Gnoweb's TOC unescapes Markdown punctuation but leaves HTML entities literal.
var output strings.Builder
for _, r := range value {
if unicode.IsControl(r) {
output.WriteString(" ")
continue
}
if (r >= '!' && r <= '/') || (r >= ':' && r <= '@') || (r >= '[' && r <= '`') || (r >= '{' && r <= '~') {
output.WriteString("\\")
}
output.WriteRune(r)
}
return output.String()
}
func renderPages(base string, page, total int) string {
output := "\n"
if page > 1 {
output += "[Previous](" + base + "page=" + strconv.Itoa(page-1) + ") "
}
if total > 0 && page <= (total-1)/20 {
output += "[Next](" + base + "page=" + strconv.Itoa(page+1) + ")"
}
return output + "\n"
}
func renderError(message string) string {
return "# Definition\n\n" + message + "\n\n[All items](./definition:)\n\n" + renderSearchForm("")
}
func renderSearchForm(key string) string {
return "\n \n\n\n"
}