You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
252 lines
6.1 KiB
252 lines
6.1 KiB
/* |
|
* Wiki - A wiki with editor |
|
* Copyright (c) 2021 Peter Stuifzand |
|
* |
|
* This program is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation, either version 3 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
package main |
|
|
|
import ( |
|
"bufio" |
|
"encoding/json" |
|
"errors" |
|
"fmt" |
|
"log" |
|
"math/rand" |
|
"regexp" |
|
"strconv" |
|
"strings" |
|
"time" |
|
|
|
"p83.nl/go/wiki/link" |
|
) |
|
|
|
var ( |
|
MetaKV = regexp.MustCompile(`(\w+)::\s+(.*)`) |
|
niceDateParseRE = regexp.MustCompile(`^(\d{1,2})_([a-z]+)_(\d{4})$`) |
|
ParseFailed = errors.New("parse failed") |
|
Months = []string{ |
|
"", |
|
"januari", |
|
"februari", |
|
"maart", |
|
"april", |
|
"mei", |
|
"juni", |
|
"juli", |
|
"augustus", |
|
"september", |
|
"oktober", |
|
"november", |
|
"december", |
|
} |
|
) |
|
|
|
type ParsedLink struct { |
|
ID string `json:"ID,omitempty"` |
|
Name string `json:"title,omitempty"` |
|
PageName string `json:"name,omitempty"` |
|
Line string `json:"line,omitempty"` |
|
Href string `json:"href,omitempty"` |
|
} |
|
|
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" |
|
|
|
func RandStringBytes(n int) string { |
|
b := make([]byte, n) |
|
for i := range b { |
|
b[i] = letterBytes[rand.Intn(len(letterBytes))] |
|
} |
|
return string(b) |
|
} |
|
|
|
type DateLink struct { |
|
Link string |
|
Date time.Time |
|
} |
|
|
|
func ParseDates(content string) ([]time.Time, error) { |
|
links := link.FindAllLinks(content) |
|
var result []time.Time |
|
for _, linkName := range links { |
|
date, err := ParseDatePageName(linkName) |
|
if err != nil { |
|
continue |
|
} |
|
result = append(result, date) |
|
} |
|
|
|
return result, nil |
|
} |
|
|
|
func ParseLinks(blockId string, content string) ([]ParsedLink, error) { |
|
hrefRE := regexp.MustCompile(`(#?\[\[\s*([^\]]+)\s*\]\])`) |
|
// keywordsRE := regexp.MustCompile(`(\w+)::`) |
|
|
|
scanner := bufio.NewScanner(strings.NewReader(content)) |
|
scanner.Split(bufio.ScanLines) |
|
|
|
var result []ParsedLink |
|
|
|
for scanner.Scan() { |
|
line := scanner.Text() |
|
|
|
// keywords := keywordsRE.FindAllStringSubmatch(line, -1) |
|
// for _, matches := range keywords { |
|
// link := matches[1] |
|
// l := cleanNameURL(link) |
|
// result = append(result, ParsedLink{blockId, link, l, line, ""}) |
|
// } |
|
|
|
links := hrefRE.FindAllStringSubmatch(line, -1) |
|
|
|
for _, matches := range links { |
|
link := matches[0] |
|
|
|
if link[0] == '#' { |
|
link = strings.TrimPrefix(link, "#[[") |
|
} else { |
|
link = strings.TrimPrefix(link, "[[") |
|
} |
|
|
|
link = strings.TrimSuffix(link, "]]") |
|
link = strings.TrimSpace(link) |
|
l := cleanNameURL(link) |
|
result = append(result, ParsedLink{blockId, link, l, line, ""}) |
|
} |
|
} |
|
|
|
return result, nil |
|
} |
|
|
|
func ParseTags(content string) ([]string, error) { |
|
linkRE := regexp.MustCompile(`(#\[\[\s*([^\]]+)\s*\]\])`) |
|
tagRE := regexp.MustCompile(`#([^ ]+)`) |
|
|
|
scanner := bufio.NewScanner(strings.NewReader(content)) |
|
scanner.Split(bufio.ScanLines) |
|
|
|
var result []string |
|
|
|
for scanner.Scan() { |
|
line := scanner.Text() |
|
links := linkRE.FindAllStringSubmatch(line, -1) |
|
for _, matches := range links { |
|
linkText := matches[0] |
|
linkText = strings.TrimPrefix(linkText, "#[[") |
|
linkText = strings.TrimSuffix(linkText, "]]") |
|
linkText = strings.TrimSpace(linkText) |
|
result = append(result, linkText) |
|
} |
|
for _, matches := range tagRE.FindAllStringSubmatch(line, -1) { |
|
result = append(result, matches[1]) |
|
} |
|
} |
|
|
|
return result, nil |
|
} |
|
|
|
func cleanNameURL(name string) string { |
|
return strings.Replace(name, " ", "_", -1) |
|
} |
|
|
|
func cleanTitle(name string) string { |
|
return strings.Replace(name, "_", " ", -1) |
|
} |
|
|
|
type stopwatch struct { |
|
start time.Time |
|
lastLap time.Time |
|
label string |
|
} |
|
|
|
func (sw *stopwatch) Start(label string) { |
|
sw.start = time.Now() |
|
sw.lastLap = time.Now() |
|
sw.label = label |
|
} |
|
|
|
func (sw *stopwatch) Lap(label string) { |
|
now := time.Now() |
|
d := now.Sub(sw.lastLap) |
|
log.Printf("%-20s: %s\n", label, d.String()) |
|
sw.lastLap = now |
|
} |
|
|
|
func (sw *stopwatch) Stop() { |
|
endTime := time.Now() |
|
d := endTime.Sub(sw.start) |
|
log.Printf("%-20s: %s\n", sw.label, d.String()) |
|
} |
|
|
|
func todayPage() string { |
|
now := time.Now() |
|
return formatDatePageName(now) |
|
} |
|
|
|
func formatDateTitle(date time.Time) string { |
|
return fmt.Sprintf("%d %s %d", date.Day(), Months[date.Month()], date.Year()) |
|
} |
|
func formatDatePageName(date time.Time) string { |
|
return fmt.Sprintf("%d_%s_%d", date.Day(), Months[date.Month()], date.Year()) |
|
} |
|
|
|
func PageTitle(pageText string) (string, error) { |
|
var listItems []struct { |
|
Indented int |
|
Text string |
|
} |
|
err := json.NewDecoder(strings.NewReader(pageText)).Decode(&listItems) |
|
if err != nil { |
|
return "", fmt.Errorf("while decoding page text: %w", err) |
|
} |
|
|
|
for _, li := range listItems { |
|
if matches := MetaKV.FindStringSubmatch(li.Text); matches != nil { |
|
if matches[1] == "Title" { |
|
return matches[2], nil |
|
} |
|
} |
|
} |
|
return "", fmt.Errorf("no meta title found in page text") |
|
} |
|
|
|
func parseMonth(month string) (time.Month, error) { |
|
for i, m := range Months { |
|
if m == month { |
|
return time.Month(i), nil |
|
} |
|
} |
|
return time.Month(0), fmt.Errorf("parseMonth: %q is not a recognized month", month) |
|
} |
|
|
|
func ParseDatePageName(name string) (time.Time, error) { |
|
if matches := niceDateParseRE.FindStringSubmatch(strings.Replace(name, " ", "_", -1)); matches != nil { |
|
day, err := strconv.Atoi(matches[1]) |
|
if err != nil { |
|
return time.Time{}, fmt.Errorf("%q: %s: %w", name, err, ParseFailed) |
|
} |
|
month, err := parseMonth(matches[2]) |
|
if err != nil { |
|
return time.Time{}, fmt.Errorf("%q: %s: %w", name, err, ParseFailed) |
|
} |
|
year, err := strconv.Atoi(matches[3]) |
|
if err != nil { |
|
return time.Time{}, fmt.Errorf("%q: %s: %w", name, err, ParseFailed) |
|
} |
|
return time.Date(year, month, day, 0, 0, 0, 0, time.UTC), nil |
|
} |
|
return time.Time{}, fmt.Errorf("%q: invalid syntax: %w", name, ParseFailed) |
|
}
|
|
|