annotate qwb.go @ 0:ac64aa92dea1

initial
author Atarwn Gard <a@qwa.su>
date Fri, 13 Mar 2026 13:13:07 +0500
parents
children 3222f88c0afe
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
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
13 const siteTitle = "My Site"
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
14
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
15 const tmpl = `<!DOCTYPE html>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
16 <html lang="en">
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
17 <head>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
18 <meta charset="UTF-8">
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
19 <meta name="viewport" content="width=device-width, initial-scale=1">
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
20 <title>{{TITLE}}</title>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
21 <link rel="stylesheet" href="/x.css">
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
22 </head>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
23 <body>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
24 <header><h1>` + siteTitle + `</h1></header>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
25 <nav>{{NAV}}</nav>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
26 <main>{{CONTENT}}</main>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
27 <footer>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
28 <p>` + siteTitle + `</p>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
29 <p>&copy; Built with <a href="https://hg.reactionary.software/qwb">qwb</a></p>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
30 </footer>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
31 </body>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
32 </html>`
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
33
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
34 // section — top-level directory, each with its own index and pages
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
35 type section struct {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
36 title string
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
37 index string // /section/index.html, or "" if none
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
38 pages []page // non-index .md files within this section
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
39 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
40
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
41 type page struct {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
42 title string
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
43 path string
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
44 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
45
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
46 // collectSections scans one level deep: root files go into a synthetic root
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
47 // section, each subdirectory becomes its own section.
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
48 func collectSections(root string) (rootSection section, subs []section) {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
49 rootSection.title = siteTitle
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
50
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
51 entries, _ := os.ReadDir(root)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
52 for _, e := range entries {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
53 full := filepath.Join(root, e.Name())
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
54 if e.IsDir() {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
55 s := scanSection(full, root)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
56 if s.index != "" || len(s.pages) > 0 {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
57 subs = append(subs, s)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
58 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
59 continue
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
60 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
61 if strings.HasSuffix(e.Name(), ".md") {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
62 if e.Name() == "index.md" {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
63 rootSection.index = "/index.html"
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
64 } else {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
65 rel, _ := filepath.Rel(root, full)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
66 rootSection.pages = append(rootSection.pages, page{
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
67 title: titleFromName(e.Name()),
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
68 path: "/" + strings.TrimSuffix(rel, ".md") + ".html",
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
69 })
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
70 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
71 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
72 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
73 return
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
74 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
75
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
76 func scanSection(dir, root string) section {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
77 s := section{title: titleFromName(filepath.Base(dir))}
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
78 entries, _ := os.ReadDir(dir)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
79 for _, e := range entries {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
80 if e.IsDir() {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
81 continue // only one level deep
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
82 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
83 if !strings.HasSuffix(e.Name(), ".md") {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
84 continue
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
85 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
86 rel, _ := filepath.Rel(root, filepath.Join(dir, e.Name()))
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
87 htmlPath := "/" + strings.TrimSuffix(rel, ".md") + ".html"
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
88 if e.Name() == "index.md" {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
89 s.index = htmlPath
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
90 } else {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
91 s.pages = append(s.pages, page{titleFromName(e.Name()), htmlPath})
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
92 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
93 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
94 return s
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
95 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
96
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
97 func navLink(b *strings.Builder, p page, cur string) {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
98 if p.path == cur {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
99 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
100 } else {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
101 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
102 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
103 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
104
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
105 // buildNav produces one or two <ul> blocks.
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
106 // Top <ul>: root index + root loose pages + one entry per sub-section (via its index).
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
107 // Second <ul>: pages of the active sub-section, only when section has 2+ pages.
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
108 func buildNav(rootSec section, subs []section, cur string) string {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
109 var b strings.Builder
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
110
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
111 b.WriteString("<ul>\n")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
112 if rootSec.index != "" {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
113 navLink(&b, page{"Home", rootSec.index}, cur)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
114 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
115 for _, p := range rootSec.pages {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
116 navLink(&b, p, cur)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
117 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
118 for _, s := range subs {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
119 link := s.index
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
120 if link == "" && len(s.pages) > 0 {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
121 link = s.pages[0].path
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
122 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
123 if link == "" {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
124 continue
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
125 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
126 navLink(&b, page{s.title, link}, cur)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
127 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
128 b.WriteString("</ul>\n")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
129
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
130 // Find which sub-section cur belongs to
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
131 for _, s := range subs {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
132 if !sectionContains(s, cur) {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
133 continue
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
134 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
135 // Only render sub-nav if there are 2+ addressable pages in the section
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
136 total := len(s.pages)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
137 if s.index != "" {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
138 total++
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
139 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
140 if total < 2 {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
141 break
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
142 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
143 b.WriteString("<ul>\n")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
144 if s.index != "" {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
145 navLink(&b, page{"Index", s.index}, cur)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
146 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
147 for _, p := range s.pages {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
148 navLink(&b, p, cur)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
149 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
150 b.WriteString("</ul>\n")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
151 break
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
152 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
153
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
154 return b.String()
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
155 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
156
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
157 func sectionContains(s section, cur string) bool {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
158 if s.index == cur {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
159 return true
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
160 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
161 for _, p := range s.pages {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
162 if p.path == cur {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
163 return true
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
164 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
165 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
166 return false
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
167 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
168
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
169 func mdToHTML(path string) (string, error) {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
170 cmd := exec.Command("lowdown", "-T", "html", "--html-no-skiphtml", "--html-no-escapehtml")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
171 f, err := os.Open(path)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
172 if err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
173 return "", err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
174 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
175 defer f.Close()
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
176 var buf strings.Builder
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
177 cmd.Stdin = f
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
178 cmd.Stdout = &buf
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
179 cmd.Stderr = os.Stderr
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
180 if err := cmd.Run(); err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
181 return "", err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
182 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
183 return buf.String(), nil
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
184 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
185
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
186 func titleFromName(name string) string {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
187 name = strings.TrimSuffix(name, ".md")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
188 name = strings.ReplaceAll(name, "-", " ")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
189 if len(name) > 0 {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
190 name = strings.ToUpper(name[:1]) + name[1:]
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
191 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
192 return name
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
193 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
194
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
195 func fixLinks(s string) string {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
196 return strings.NewReplacer(
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
197 ".md)", ".html)", ".md\"", ".html\"",
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
198 ".md'", ".html'", ".md#", ".html#",
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
199 ".md>", ".html>", ".md ", ".html ",
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
200 ".md,", ".html,",
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
201 ).Replace(s)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
202 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
203
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
204 func copyFile(src, dst string) error {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
205 in, err := os.Open(src)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
206 if err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
207 return err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
208 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
209 defer in.Close()
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
210 out, err := os.Create(dst)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
211 if err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
212 return err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
213 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
214 defer out.Close()
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
215 _, err = io.Copy(out, in)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
216 return err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
217 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
218
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
219 func main() {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
220 if len(os.Args) != 3 {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
221 fmt.Fprintln(os.Stderr, "usage: qwb <in> <out>")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
222 os.Exit(1)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
223 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
224 src, out := os.Args[1], os.Args[2]
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
225
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
226 if entries, err := os.ReadDir(out); err == nil && len(entries) > 0 {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
227 fmt.Fprintf(os.Stderr, "qwb: %s is not empty, overwrite? [y/N] ", out)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
228 s, _ := bufio.NewReader(os.Stdin).ReadString('\n')
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
229 if strings.TrimSpace(strings.ToLower(s)) != "y" {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
230 fmt.Fprintln(os.Stderr, "aborted")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
231 os.Exit(1)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
232 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
233 if err := os.RemoveAll(out); err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
234 fmt.Fprintln(os.Stderr, err)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
235 os.Exit(1)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
236 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
237 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
238
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
239 rootSec, subs := collectSections(src)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
240
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
241 err := filepath.WalkDir(src, func(path string, d os.DirEntry, err error) error {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
242 if err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
243 return err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
244 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
245 rel, _ := filepath.Rel(src, path)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
246 outpath := filepath.Join(out, rel)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
247
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
248 if d.IsDir() {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
249 return os.MkdirAll(outpath, 0755)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
250 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
251
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
252 if !strings.HasSuffix(path, ".md") {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
253 return copyFile(path, outpath)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
254 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
255
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
256 body, err := mdToHTML(path)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
257 if err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
258 return err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
259 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
260 body = fixLinks(body)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
261
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
262 cur := "/" + strings.TrimSuffix(rel, ".md") + ".html"
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
263
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
264 title := siteTitle
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
265 if filepath.Base(path) != "index.md" {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
266 title = siteTitle + " | " + titleFromName(filepath.Base(path))
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
267 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
268
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
269 pg := strings.ReplaceAll(tmpl, "{{TITLE}}", title)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
270 pg = strings.ReplaceAll(pg, "{{NAV}}", buildNav(rootSec, subs, cur))
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
271 pg = strings.ReplaceAll(pg, "{{CONTENT}}", body)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
272
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
273 outpath = strings.TrimSuffix(outpath, ".md") + ".html"
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
274 return os.WriteFile(outpath, []byte(pg), 0644)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
275 })
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
276
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
277 if err != nil {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
278 fmt.Fprintln(os.Stderr, err)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
279 os.Exit(1)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
280 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
281 }