annotate qwb.go @ 7:d139d86fb4e1 default tip

html in md
author Atarwn Gard <a@qwa.su>
date Tue, 17 Mar 2026 23:46:56 +0500
parents bd0d3a189f5b
children
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>
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
19 <link rel="stylesheet" href="/x.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>
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
23 <header><h1>{{PAGE_TITLE}}</h1></header>
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
24 <main>{{CONTENT}}</main>
6
bd0d3a189f5b my deadass removed "built with" from defautl template (i need to go to sleep)
Atarwn Gard <a@qwa.su>
parents: 5
diff changeset
25 <footer><p>{{FOOTER_TEXT}}</p><p>Built with <a href="https://hg.reactionary.software/repo/qwb/">qwb</a></p></footer>
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
26 </body>
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
27 </html>`
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
28
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
29 type config struct {
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
30 SiteTitle, FooterText string
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
31 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
32
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
33 type sect struct {
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
34 title string
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
35
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
36 href string
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
37
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
38 pages []page
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
39
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
40 hasIndex bool
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
41
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
42 children []sect
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
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
47 href string
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
48 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
49
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
50 func parseini(r io.Reader) map[string]string {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
51 res := make(map[string]string)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
52 sc := bufio.NewScanner(r)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
53 for sc.Scan() {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
54 line := strings.TrimSpace(sc.Text())
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
55 if line == "" || strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "[") {
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
56 continue
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
57 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
58 if k, v, ok := strings.Cut(line, "="); ok {
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
59 res[strings.TrimSpace(k)] = strings.TrimSpace(v)
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
60 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
61 }
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
62 return res
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
63 }
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
64
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
65 func loadcfg(src string) config {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
66 cfg := config{SiteTitle: "My Site", FooterText: "© 2026"}
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
67 f, err := os.Open(filepath.Join(src, "qwb.ini"))
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
68 if err == nil {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
69 defer f.Close()
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
70 ini := parseini(f)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
71 if v, ok := ini["SiteTitle"]; ok {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
72 cfg.SiteTitle = v
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
73 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
74 if v, ok := ini["FooterText"]; ok {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
75 cfg.FooterText = v
2
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
76 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
77 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
78 return cfg
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
79 }
3222f88c0afe refactored + new css
Atarwn Gard <a@qwa.su>
parents: 0
diff changeset
80
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
81 func gentitle(name string) string {
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
82 name = strings.TrimSuffix(name, ".md")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
83 name = strings.ReplaceAll(name, "-", " ")
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
84 if len(name) > 0 {
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
85 name = strings.ToUpper(name[:1]) + name[1:]
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
86 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
87 return name
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
88 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
89
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
90 func md2html(path string) (string, error) {
7
d139d86fb4e1 html in md
Atarwn Gard <a@qwa.su>
parents: 6
diff changeset
91 cmd := exec.Command("lowdown", "-Thtml", "--html-no-skiphtml", "--html-no-escapehtml")
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
92 f, err := os.Open(path)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
93 if err != nil {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
94 return "", err
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
95 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
96 defer f.Close()
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
97 var buf strings.Builder
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
98 cmd.Stdin, cmd.Stdout = f, &buf
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
99 if err := cmd.Run(); err != nil {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
100 return "", err
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
101 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
102 html := buf.String()
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
103
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
104 if trimmed := strings.TrimSpace(html); strings.HasPrefix(trimmed, "<h1") {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
105 if end := strings.Index(trimmed, "</h1>"); end >= 0 {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
106 html = strings.TrimSpace(trimmed[end+5:])
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
107 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
108 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
109 return html, nil
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
110 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
111
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
112 func mdtitle(path string) string {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
113 f, err := os.Open(path)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
114 if err != nil {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
115 return ""
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
116 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
117 defer f.Close()
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
118 sc := bufio.NewScanner(f)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
119 for sc.Scan() {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
120 line := strings.TrimSpace(sc.Text())
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
121 if strings.HasPrefix(line, "# ") {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
122 return strings.TrimSpace(line[2:])
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
123 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
124 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
125 return ""
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
126 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
127
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
128 //,
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
129
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
130 func mdnavhref(path string) (string, bool) {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
131 f, err := os.Open(path)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
132 if err != nil {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
133 return "", false
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
134 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
135 defer f.Close()
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
136 sc := bufio.NewScanner(f)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
137 if !sc.Scan() {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
138 return "", false
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
139 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
140 line := strings.TrimSpace(sc.Text())
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
141 if strings.Contains(line, "://") {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
142 return line, true
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
143 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
144 return "", false
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
145 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
146
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
147 func scansrc(src string) ([]sect, error) {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
148 root := sect{title: "Home", href: "/index.html"}
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
149
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
150 var walk func(dir, rel string, s *sect) error
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
151 walk = func(dir, rel string, s *sect) error {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
152 entries, err := os.ReadDir(dir)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
153 if err != nil {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
154 return err
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
155 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
156 for _, e := range entries {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
157 name := e.Name()
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
158 childRel := filepath.Join(rel, name)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
159 childAbs := filepath.Join(dir, name)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
160 if e.IsDir() {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
161 child := sect{
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
162 title: gentitle(name),
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
163 href: "/" + filepath.ToSlash(childRel) + "/index.html",
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
164 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
165 if err := walk(childAbs, childRel, &child); err != nil {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
166 return err
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
167 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
168 s.children = append(s.children, child)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
169 continue
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
170 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
171 if !strings.HasSuffix(name, ".md") {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
172 continue
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
173 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
174 if name == "index.md" {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
175 s.hasIndex = true
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
176 if ext, ok := mdnavhref(childAbs); ok {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
177 s.href = ext
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
178 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
179 } else {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
180 href := "/" + filepath.ToSlash(strings.TrimSuffix(childRel, ".md")+".html")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
181 if ext, ok := mdnavhref(childAbs); ok {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
182 href = ext
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
183 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
184 s.pages = append(s.pages, page{
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
185 title: gentitle(name),
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
186 href: href,
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
187 })
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
188 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
189 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
190 return nil
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
191 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
192
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
193 if err := walk(src, "", &root); err != nil {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
194 return nil, err
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
195 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
196 return []sect{root}, nil
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
197 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
198
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
199 func findCurSect(sects []sect, cur string) *sect {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
200 for i := range sects {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
201 s := &sects[i]
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
202 if s.href == cur {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
203 return s
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
204 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
205 for _, p := range s.pages {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
206 if p.href == cur {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
207 return s
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
208 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
209 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
210 if found := findCurSect(s.children, cur); found != nil {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
211 return found
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
212 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
213 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
214 return nil
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
215 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
216
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
217 func navX(roots []sect, cur string) string {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
218 if len(roots) == 0 {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
219 return ""
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
220 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
221 root := &roots[0]
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
222
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
223 var path []*sect
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
224 var findPath func(s *sect) bool
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
225 findPath = func(s *sect) bool {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
226 path = append(path, s)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
227 if s.href == cur {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
228 return true
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
229 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
230 for _, p := range s.pages {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
231 if p.href == cur {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
232 return true
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
233 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
234 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
235 for i := range s.children {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
236 if findPath(&s.children[i]) {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
237 return true
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
238 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
239 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
240 path = path[:len(path)-1]
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
241 return false
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
242 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
243 if !findPath(root) {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
244 return ""
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
245 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
246
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
247 sectLI := func(b *strings.Builder, s *sect, active bool) {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
248 b.WriteString(" <li>")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
249 if s.hasIndex {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
250 cls := ""
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
251 if active {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
252 cls = " current"
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
253 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
254 b.WriteString(`<a` + cls + ` href="` + s.href + `">` + s.title + `</a>`)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
255 } else {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
256 b.WriteString(s.title)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
257 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
258 b.WriteString("</li>\n")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
259 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
260
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
261 var b strings.Builder
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
262
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
263 {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
264 var active1 *sect
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
265
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
266 if len(path) > 1 {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
267 active1 = path[1]
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
268 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
269 b.WriteString("<ul>\n")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
270 sectLI(&b, root, len(path) == 1)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
271
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
272 for i := range root.children {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
273 c := &root.children[i]
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
274 sectLI(&b, c, c == active1)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
275 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
276 b.WriteString("</ul>\n")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
277 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
278
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
279 for depth := 1; depth < len(path); depth++ {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
280 s := path[depth]
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
281 if len(s.children) == 0 && len(s.pages) == 0 {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
282 continue
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
283 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
284 var activeChild *sect
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
285 if depth+1 < len(path) {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
286 activeChild = path[depth+1]
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
287 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
288 b.WriteString("<ul>\n")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
289 for i := range s.children {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
290 c := &s.children[i]
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
291 sectLI(&b, c, c == activeChild)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
292 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
293 for _, p := range s.pages {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
294 cls := ""
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
295 if p.href == cur {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
296 cls = " current"
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
297 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
298 b.WriteString(` <li><a` + cls + ` href="` + p.href + `">` + p.title + `</a></li>` + "\n")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
299 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
300 b.WriteString("</ul>\n")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
301 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
302
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
303 return b.String()
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
304 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
305
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
306 func navY(roots []sect, cur string) string {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
307 curSect := findCurSect(roots, cur)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
308
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
309 var isAncestor func(s *sect) bool
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
310 isAncestor = func(s *sect) bool {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
311 if s == curSect {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
312 return true
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
313 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
314 for i := range s.children {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
315 if isAncestor(&s.children[i]) {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
316 return true
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
317 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
318 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
319 return false
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
320 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
321
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
322 var b strings.Builder
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
323 var renderSects func(sects []sect, depth int)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
324 renderSects = func(sects []sect, depth int) {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
325 indent := strings.Repeat(" ", depth)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
326 b.WriteString(indent + "<ul>\n")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
327 for i := range sects {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
328 s := &sects[i]
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
329 b.WriteString(indent + " <li>")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
330 if s.hasIndex {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
331 cls := ""
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
332 if s == curSect {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
333 cls = " current"
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
334 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
335 b.WriteString(`<a` + cls + ` href="` + s.href + `">` + s.title + `</a>`)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
336 } else {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
337 b.WriteString(s.title)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
338 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
339
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
340 if isAncestor(s) {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
341 if len(s.pages) > 0 || len(s.children) > 0 {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
342 b.WriteString("\n")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
343 if len(s.children) > 0 {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
344 renderSects(s.children, depth+2)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
345 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
346 if len(s.pages) > 0 {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
347 b.WriteString(indent + " <ul>\n")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
348 for _, p := range s.pages {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
349 cls := ""
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
350 if p.href == cur {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
351 cls = " current"
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
352 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
353 b.WriteString(indent + ` <li><a` + cls + ` href="` + p.href + `">` + p.title + `</a></li>` + "\n")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
354 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
355 b.WriteString(indent + " </ul>\n")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
356 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
357 b.WriteString(indent + " ")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
358 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
359 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
360 b.WriteString("</li>\n")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
361 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
362 b.WriteString(indent + "</ul>\n")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
363 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
364 renderSects(roots, 0)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
365 return b.String()
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
366 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
367
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
368 func render(tpl, pageTitle, nav, content string, cfg config) string {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
369 compositeTitle := cfg.SiteTitle + " | " + pageTitle
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
370 r := strings.NewReplacer(
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
371 "{{TITLE}}", compositeTitle,
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
372 "{{NAV}}", nav,
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
373 "{{PAGE_TITLE}}", pageTitle,
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
374 "{{CONTENT}}", content,
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
375 "{{FOOTER_TEXT}}", cfg.FooterText,
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
376 )
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
377 return r.Replace(tpl)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
378 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
379
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
380 func writepage(outpath, html string) error {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
381 if err := os.MkdirAll(filepath.Dir(outpath), 0755); err != nil {
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
382 return err
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
383 }
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
384 return os.WriteFile(outpath, []byte(html), 0644)
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
385 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
386
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
387 func main() {
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
388 if len(os.Args) < 3 {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
389 fmt.Println("usage: qwb <src> <out> [-x|-y]")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
390 return
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
391 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
392 src, out := os.Args[1], os.Args[2]
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
393 mode := "x"
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
394 if len(os.Args) == 4 {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
395 switch os.Args[3] {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
396 case "-x":
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
397 mode = "x"
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
398 case "-y":
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
399 mode = "y"
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
400 default:
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
401 fmt.Println("usage: qwb <src> <out> [-x|-y]")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
402 return
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
403 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
404 }
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
405
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
406 cfg := loadcfg(src)
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
407
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
408 sects, err := scansrc(src)
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
409 if err != nil {
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
410 fmt.Fprintln(os.Stderr, "scan:", err)
0
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
411 os.Exit(1)
ac64aa92dea1 initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
412 }
5
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
413
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
414 var process func(dir, rel string)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
415 process = func(dir, rel string) {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
416 entries, err := os.ReadDir(dir)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
417 if err != nil {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
418 return
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
419 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
420 for _, e := range entries {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
421 name := e.Name()
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
422 childRel := filepath.Join(rel, name)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
423 childAbs := filepath.Join(dir, name)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
424 if e.IsDir() {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
425 process(childAbs, childRel)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
426 continue
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
427 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
428 if !strings.HasSuffix(name, ".md") {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
429
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
430 dst := filepath.Join(out, childRel)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
431 _ = os.MkdirAll(filepath.Dir(dst), 0755)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
432 if data, err := os.ReadFile(childAbs); err == nil {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
433 _ = os.WriteFile(dst, data, 0644)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
434 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
435 continue
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
436 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
437 htmlRel := strings.TrimSuffix(childRel, ".md") + ".html"
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
438 href := "/" + filepath.ToSlash(htmlRel)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
439
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
440 var nav string
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
441 if mode == "y" {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
442 nav = navY(sects, href)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
443 } else {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
444 nav = navX(sects, href)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
445 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
446
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
447 pageTitle := mdtitle(childAbs)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
448 if pageTitle == "" {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
449 pageTitle = gentitle(name)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
450 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
451
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
452 content, err := md2html(childAbs)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
453 if err != nil {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
454 fmt.Fprintf(os.Stderr, "md2html %s: %v\n", childAbs, err)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
455 content = "<p>render error</p>"
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
456 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
457
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
458 html := render(tpldefault, pageTitle, nav, content, cfg)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
459 outpath := filepath.Join(out, htmlRel)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
460 if err := writepage(outpath, html); err != nil {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
461 fmt.Fprintf(os.Stderr, "write %s: %v\n", outpath, err)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
462 } else {
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
463 fmt.Println("→", outpath)
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
464 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
465 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
466 }
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
467
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
468 process(src, "")
Atarwn Gard <a@qwa.su>
parents: 4
diff changeset
469 }