-
Notifications
You must be signed in to change notification settings - Fork 0
/
page.go
151 lines (136 loc) · 3.23 KB
/
page.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"unicode"
)
// storePage stores page p on disk
func storePage(nss []Namespace, p Page, makeSymlinks bool) error {
filename, err := localFilename(nss, p.Title)
if err != nil {
return err
}
ff := filepath.Clean(*targetDir + "/" + filename)
if err := os.MkdirAll(filepath.Dir(ff), 0700); err != nil {
return err
}
if r := p.Redirect.Title; r != "" {
if !makeSymlinks {
return nil
}
to, err := localFilename(nss, r)
if err != nil {
return err
}
rel, err := filepath.Rel(filepath.Dir(filename), to)
if err != nil {
return err
}
if *verbose {
fmt.Printf("symlink redirect %s -> %s\n", filename, rel)
}
return os.Symlink(rel, ff)
}
return ioutil.WriteFile(ff, []byte(p.Text), 0600)
}
// localFilename makes the full path for a page title
// "foobar" -> "/f/o/o/Foobar"
// "Template:foobar" -> "/Template/f/o/o/Template:Foobar"
func localFilename(nss []Namespace, title string) (string, error) {
if title == "" {
return "", errors.New("empty title")
}
path := ""
ns, pageName := splitNamespace(nss, title)
// start with the template, if it's not empty
if ns.Name != "" {
path += "/" + ns.Name
}
// add /f/o/o
path += addComp(pageName, PathComponents)
// add /[namespace:]<casefolded page name>
path += "/"
if ns.Name != "" {
path += ns.Name + ":"
}
filename, err := caseFold(ns, pageName)
if err != nil {
return "", err
}
path += strings.Replace(filename, "/", "_", -1) // META: needed?
return path, nil
}
func caseFold(ns Namespace, t string) (string, error) {
switch c := ns.Case; c {
case "first-letter":
return ucFirst(t), nil
default:
return "", fmt.Errorf("unhandled namespace case: %q", c)
}
}
// addComp adds the '/f' in '/f/foo'
func addComp(filename string, lvl int) string {
if lvl == 0 {
return ""
}
if len(filename) == 0 {
return "/_" + addComp("", lvl-1)
}
ps := []rune(filename)
sign := '_'
// META: maybe allow unicode.IsLetter for less ASCIIish scripts?
switch r := ps[0]; {
case '0' <= r && r <= '9':
sign = r
case 'a' <= r && r <= 'z':
sign = r
case 'A' <= r && r <= 'Z':
sign = unicode.ToLower(r)
}
return "/" + string(sign) + addComp(string(ps[1:]), lvl-1)
}
func ucFirst(t string) string {
rs := []rune(t)
rs[0] = unicode.ToUpper(rs[0])
return string(rs)
}
// splitNamespace looks through nss to find the namespace of page t, and
// returns that (or the empty namespace), and the page without namespace prefix.
func splitNamespace(nss []Namespace, t string) (Namespace, string) {
var (
tLc = strings.ToLower(t)
)
for _, ns := range nss {
if strings.HasPrefix(tLc, strings.ToLower(ns.Name)+":") {
parts := strings.SplitN(t, ":", 2)
return ns, parts[1]
}
}
// no namespace
for _, ns := range nss {
if ns.Name == "" {
return ns, t
}
}
// Oops. No empty namespace. Weird.
return Namespace{}, t
}
// saveNamespaces stores the namespaces as json. The namespaces are needed when
// finding pages later.
func saveNamespaces(nss []Namespace) error {
fn := *targetDir + "/" + NamespaceFile
f, err := os.Create(fn)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
enc.Encode(nss)
return nil
}