annotate qwb.go @ 2:3222f88c0afe

refactored + new css
author Atarwn Gard <a@qwa.su>
date Sat, 14 Mar 2026 12:03:52 +0500
parents ac64aa92dea1
children ce2b6dde4c10
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
1 package main
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
2
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
3 import (
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
4 "bufio"
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
5 "fmt"
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
6 "io"
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
7 "os"
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
8 "os/exec"
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
9 "path/filepath"
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
10 "strings"
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
11 )
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
12
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
13 const tpldefault = `<!DOCTYPE html>
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
14 <html lang="en">
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
15 <head>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
16 <meta charset="UTF-8">
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
17 <meta name="viewport" content="width=device-width, initial-scale=1">
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
18 <title>{{TITLE}}</title>
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
19 <link rel="stylesheet" href="{{CSS}}">
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
20 </head>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
21 <body>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
22 <nav>{{NAV}}</nav>
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
23 <header><h1>{{SITE_TITLE}}</h1></header>
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
24 <main>{{CONTENT}}</main>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
25 <footer>
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
26 <p>{{FOOTER_TEXT}}</p>
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
27 <p>Built with <a href="https://hg.reactionary.software/qwb">qwb</a></p>
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
28 </footer>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
29 </body>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
30 </html>`
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
31
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
32 type config struct {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
33 headertext string
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
34 footertext string
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
35 cssfile string
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
36 tplfile string
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
37 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
38
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
39 type section struct {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
40 title string
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
41 index string
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
42 pages []page
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
43 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
44
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
45 type page struct {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
46 title string
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
47 path string
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
48 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
49
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
50 func ParseINI(r io.Reader) (map[string]map[string]string, error) {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
51 res := make(map[string]map[string]string)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
52 sec := "default"
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
53 scanner := bufio.NewScanner(r)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
54 for scanner.Scan() {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
55 line := strings.TrimSpace(scanner.Text())
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
56 if line == "" || strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
57 continue
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
58 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
59 if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
60 sec = line[1 : len(line)-1]
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
61 if res[sec] == nil {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
62 res[sec] = make(map[string]string)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
63 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
64 continue
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
65 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
66 if k, v, ok := strings.Cut(line, "="); ok {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
67 if res[sec] == nil {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
68 res[sec] = make(map[string]string)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
69 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
70 res[sec][strings.TrimSpace(k)] = strings.TrimSpace(v)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
71 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
72 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
73 return res, scanner.Err()
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
74 }
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
75
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
76 func loadconfig(src string) config {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
77 cfg := config{
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
78 headertext: "My Site",
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
79 footertext: "&copy; Me",
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
80 cssfile: "/style.css",
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
81 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
82 f, err := os.Open(filepath.Join(src, "qwb.ini"))
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
83 if err != nil {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
84 return cfg
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
85 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
86 defer f.Close()
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
87 ini, err := ParseINI(f)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
88 if err != nil {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
89 return cfg
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
90 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
91 s := ini["site"]
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
92 set := func(key string, target *string) {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
93 if v, ok := s[key]; ok {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
94 *target = v
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
95 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
96 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
97 set("header", &cfg.headertext)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
98 set("footer", &cfg.footertext)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
99 set("style", &cfg.cssfile)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
100 set("template", &cfg.tplfile)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
101 return cfg
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
102 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
103
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
104 func loadtemplate(cfg config) string {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
105 if cfg.tplfile == "" {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
106 return tpldefault
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
107 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
108 b, err := os.ReadFile(cfg.tplfile)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
109 if err != nil {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
110 fmt.Fprintf(os.Stderr, "cannot read template %s: %v", cfg.tplfile, err)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
111 return tpldefault
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
112 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
113 return string(b)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
114 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
115
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
116 func collectsections(root, siteTitle string) (section, []section) {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
117 var subs []section
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
118 root_ := section{title: siteTitle}
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
119 entries, _ := os.ReadDir(root)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
120 for _, e := range entries {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
121 full := filepath.Join(root, e.Name())
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
122 if e.IsDir() {
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
123 s := scansection(full, root)
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
124 if s.index != "" || len(s.pages) > 0 {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
125 subs = append(subs, s)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
126 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
127 continue
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
128 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
129 if !strings.HasSuffix(e.Name(), ".md") {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
130 continue
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
131 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
132 if e.Name() == "index.md" {
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
133 root_.index = "/index.html"
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
134 } else {
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
135 rel, _ := filepath.Rel(root, full)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
136 root_.pages = append(root_.pages, page{
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
137 title: titlefromname(e.Name()),
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
138 path: "/" + strings.TrimSuffix(rel, ".md") + ".html",
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
139 })
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
140 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
141 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
142 return root_, subs
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
143 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
144
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
145 func scansection(dir, root string) section {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
146 s := section{title: titlefromname(filepath.Base(dir))}
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
147 entries, _ := os.ReadDir(dir)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
148 for _, e := range entries {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
149 if e.IsDir() || !strings.HasSuffix(e.Name(), ".md") {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
150 continue
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
151 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
152 rel, _ := filepath.Rel(root, filepath.Join(dir, e.Name()))
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
153 htmlpath := "/" + strings.TrimSuffix(rel, ".md") + ".html"
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
154 if e.Name() == "index.md" {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
155 s.index = htmlpath
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
156 } else {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
157 s.pages = append(s.pages, page{titlefromname(e.Name()), htmlpath})
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
158 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
159 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
160 return s
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
161 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
162
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
163 func navlink(b *strings.Builder, p page, cur string) {
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
164 if p.path == cur {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
165 fmt.Fprintf(b, "<li><b><a href=\"%s\">%s</a></b></li>\n", p.path, p.title)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
166 } else {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
167 fmt.Fprintf(b, "<li><a href=\"%s\">%s</a></li>\n", p.path, p.title)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
168 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
169 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
170
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
171 func buildnav(root section, subs []section, cur string) string {
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
172 var b strings.Builder
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
173 b.WriteString("<ul>\n")
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
174 if root.index != "" {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
175 navlink(&b, page{"Home", root.index}, cur)
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
176 }
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
177 for _, p := range root.pages {
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
178 navlink(&b, p, cur)
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
179 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
180 for _, s := range subs {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
181 link := s.index
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
182 if link == "" && len(s.pages) > 0 {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
183 link = s.pages[0].path
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
184 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
185 if link == "" {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
186 continue
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
187 }
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
188 navlink(&b, page{s.title, link}, cur)
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
189 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
190 b.WriteString("</ul>\n")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
191 for _, s := range subs {
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
192 if !sectioncontains(s, cur) {
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
193 continue
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
194 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
195 total := len(s.pages)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
196 if s.index != "" {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
197 total++
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
198 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
199 if total < 2 {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
200 break
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
201 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
202 b.WriteString("<ul>\n")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
203 if s.index != "" {
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
204 navlink(&b, page{"Index", s.index}, cur)
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
205 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
206 for _, p := range s.pages {
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
207 navlink(&b, p, cur)
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
208 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
209 b.WriteString("</ul>\n")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
210 break
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
211 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
212 return b.String()
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
213 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
214
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
215 func sectioncontains(s section, cur string) bool {
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
216 if s.index == cur {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
217 return true
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
218 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
219 for _, p := range s.pages {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
220 if p.path == cur {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
221 return true
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
222 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
223 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
224 return false
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
225 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
226
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
227 func mdtohtml(path string) (string, error) {
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
228 cmd := exec.Command("lowdown", "-T", "html", "--html-no-skiphtml", "--html-no-escapehtml")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
229 f, err := os.Open(path)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
230 if err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
231 return "", err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
232 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
233 defer f.Close()
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
234 var buf strings.Builder
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
235 cmd.Stdin = f
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
236 cmd.Stdout = &buf
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
237 cmd.Stderr = os.Stderr
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
238 if err := cmd.Run(); err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
239 return "", err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
240 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
241 return buf.String(), nil
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
242 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
243
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
244 func titlefromname(name string) string {
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
245 name = strings.TrimSuffix(name, ".md")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
246 name = strings.ReplaceAll(name, "-", " ")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
247 if len(name) > 0 {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
248 name = strings.ToUpper(name[:1]) + name[1:]
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
249 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
250 return name
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
251 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
252
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
253 func fixlinks(s string) string {
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
254 return strings.NewReplacer(
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
255 ".md)", ".html)",
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
256 ".md\"", ".html\"",
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
257 ".md'", ".html'",
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
258 ".md#", ".html#",
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
259 ".md>", ".html>",
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
260 ".md ", ".html ",
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
261 ".md,", ".html,",
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
262 ).Replace(s)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
263 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
264
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
265 func copyfile(src, dst string) error {
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
266 in, err := os.Open(src)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
267 if err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
268 return err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
269 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
270 defer in.Close()
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
271 out, err := os.Create(dst)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
272 if err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
273 return err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
274 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
275 defer out.Close()
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
276 _, err = io.Copy(out, in)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
277 return err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
278 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
279
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
280 func main() {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
281 if len(os.Args) != 3 {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
282 fmt.Fprintln(os.Stderr, "usage: qwb <in> <out>")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
283 os.Exit(1)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
284 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
285 src, out := os.Args[1], os.Args[2]
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
286
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
287 if entries, err := os.ReadDir(out); err == nil && len(entries) > 0 {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
288 fmt.Fprintf(os.Stderr, "qwb: %s is not empty, overwrite? [y/N] ", out)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
289 s, _ := bufio.NewReader(os.Stdin).ReadString('\n')
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
290 if strings.TrimSpace(strings.ToLower(s)) != "y" {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
291 fmt.Fprintln(os.Stderr, "aborted")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
292 os.Exit(1)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
293 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
294 if err := os.RemoveAll(out); err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
295 fmt.Fprintln(os.Stderr, err)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
296 os.Exit(1)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
297 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
298 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
299
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
300 cfg := loadconfig(src)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
301 tmpl := loadtemplate(cfg)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
302 rootsec, subs := collectsections(src, cfg.headertext)
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
303
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
304 err := filepath.WalkDir(src, func(path string, d os.DirEntry, err error) error {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
305 if err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
306 return err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
307 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
308 rel, _ := filepath.Rel(src, path)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
309 outpath := filepath.Join(out, rel)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
310 if d.IsDir() {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
311 return os.MkdirAll(outpath, 0755)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
312 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
313 if !strings.HasSuffix(path, ".md") {
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
314 return copyfile(path, outpath)
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
315 }
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
316 body, err := mdtohtml(path)
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
317 if err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
318 return err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
319 }
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
320 body = fixlinks(body)
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
321 cur := "/" + strings.TrimSuffix(rel, ".md") + ".html"
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
322 title := cfg.headertext
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
323 if filepath.Base(path) != "index.md" {
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
324 title = cfg.headertext + " | " + titlefromname(filepath.Base(path))
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
325 }
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
326 pg:= strings.ReplaceAll(tmpl, "{{TITLE}}", title)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
327 pg = strings.ReplaceAll(pg, "{{SITE_TITLE}}", cfg.headertext)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
328 pg = strings.ReplaceAll(pg, "{{FOOTER_TEXT}}", cfg.footertext)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
329 pg = strings.ReplaceAll(pg, "{{CSS}}", cfg.cssfile)
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
330 pg = strings.ReplaceAll(pg, "{{NAV}}", buildnav(rootsec, subs, cur))
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
331 pg = strings.ReplaceAll(pg, "{{CONTENT}}", body)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
332 outpath = strings.TrimSuffix(outpath, ".md") + ".html"
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
333 return os.WriteFile(outpath, []byte(pg), 0644)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
334 })
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
335
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
336 if err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
337 fmt.Fprintln(os.Stderr, err)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
338 os.Exit(1)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
339 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
340 }