/* * 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 . */ 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) }