diff options
author | Pacien | 2013-07-16 12:50:25 +0200 |
---|---|---|
committer | Pacien | 2013-07-16 12:50:25 +0200 |
commit | 7f4e93785cd49471b4d902620c9ea4d523d874c7 (patch) | |
tree | 035a4b7f0c4de624ea00481e89162251272e4904 /main.go | |
parent | ec4932d86f805864cd35fc27a2f91964b55982d4 (diff) | |
download | foldaweb-7f4e93785cd49471b4d902620c9ea4d523d874c7.tar.gz |
First version
Diffstat (limited to 'main.go')
-rw-r--r-- | main.go | 221 |
1 files changed, 186 insertions, 35 deletions
@@ -17,58 +17,209 @@ | |||
17 | 17 | ||
18 | */ | 18 | */ |
19 | 19 | ||
20 | // FoldaWeb, a "keep last legacy" website generator | ||
20 | package main | 21 | package main |
21 | 22 | ||
22 | import ( | 23 | import ( |
24 | "bytes" | ||
23 | "flag" | 25 | "flag" |
24 | "fmt" | 26 | "fmt" |
25 | "github.com/Pacien/fcmd" | 27 | "github.com/Pacien/fcmd" |
28 | "github.com/drbawb/mustache" | ||
29 | "github.com/frankbille/sanitize" | ||
30 | "github.com/russross/blackfriday" | ||
31 | "io/ioutil" | ||
32 | "path" | ||
26 | "strings" | 33 | "strings" |
34 | "sync" | ||
27 | ) | 35 | ) |
28 | 36 | ||
29 | var settings struct { | 37 | type generator struct { |
30 | mode *string // compiled, interactive or dynamic | 38 | // parameters |
31 | sourceDir *string | 39 | sourceDir, outputDir string |
32 | outputDir *string // for compiled site | 40 | startWith, saveAs string |
33 | port *string // for the integrated web server (dynamic mode only) | 41 | wordSeparator string |
34 | exts []string | 42 | parsableExts []string |
35 | saveAs *string | 43 | |
44 | // go routine sync | ||
45 | tasks sync.WaitGroup | ||
46 | } | ||
47 | |||
48 | type page struct { | ||
49 | // properties accessible in templates | ||
50 | Title string | ||
51 | AbsPath, Path string | ||
52 | IsRoot bool | ||
53 | IsCurrent, IsParent func(params []string, data string) string | ||
54 | |||
55 | // properties used for page generation | ||
56 | dirPath string | ||
57 | parts parts | ||
58 | body []byte | ||
36 | } | 59 | } |
37 | 60 | ||
38 | func init() { | 61 | type parts map[string][]byte |
39 | fcmd.DefaultPerm = 0755 // -rwxr-xr-x | 62 | |
63 | // Creates an initiated generator | ||
64 | func newGenerator() (g generator) { | ||
65 | |||
66 | // Read the command line arguments | ||
67 | flag.StringVar(&g.sourceDir, "sourceDir", "./source", "Path to the source directory.") | ||
68 | flag.StringVar(&g.outputDir, "outputDir", "./out", "Path to the output directory.") | ||
69 | flag.StringVar(&g.startWith, "startWith", "index", "Name without extension of the first file that will by parsed.") | ||
70 | flag.StringVar(&g.saveAs, "saveAs", "index.html", "Save compiled files as named.") | ||
71 | flag.StringVar(&g.wordSeparator, "wordSeparator", "-", "Word separator used to replace spaces in URLs.") | ||
72 | var parsableExts string | ||
73 | flag.StringVar(&parsableExts, "parsableExts", "html, txt, md", "Parsable file extensions separated by commas.") | ||
40 | 74 | ||
41 | // read settings | ||
42 | settings.mode = flag.String("mode", "compiled", "compiled|interactive|dynamic") | ||
43 | settings.sourceDir = flag.String("source", "./source", "Path to sources directory.") | ||
44 | settings.outputDir = flag.String("output", "./out", "[compiled mode] Path to output directory.") | ||
45 | settings.port = flag.String("port", "8080", "[dynamic mode] Port to listen.") | ||
46 | exts := flag.String("exts", "html, txt, md", "List parsable file extensions. Separated by commas.") | ||
47 | settings.saveAs = flag.String("saveAs", "index.html", "[compiled and interactive modes] Save compiled files as named.") | ||
48 | flag.Parse() | 75 | flag.Parse() |
49 | settings.exts = strings.Split(*exts, ",") | 76 | |
50 | for i, ext := range settings.exts { | 77 | g.sourceDir = path.Clean(g.sourceDir) |
51 | settings.exts[i] = "." + strings.Trim(ext, ". ") | 78 | g.outputDir = path.Clean(g.outputDir) |
79 | for _, ext := range strings.Split(parsableExts, ",") { | ||
80 | g.parsableExts = append(g.parsableExts, "."+strings.Trim(ext, ". ")) | ||
81 | } | ||
82 | |||
83 | return | ||
84 | |||
85 | } | ||
86 | |||
87 | func (g *generator) sanitizePath(filePath string) string { | ||
88 | sanitizedFilePath := strings.Replace(filePath, " ", g.wordSeparator, -1) | ||
89 | return sanitize.Path(sanitizedFilePath) | ||
90 | } | ||
91 | |||
92 | func (g *generator) sourcePath(filePath string) string { | ||
93 | return path.Join(g.sourceDir, filePath) | ||
94 | } | ||
95 | |||
96 | func (g *generator) outputPath(filePath string) string { | ||
97 | return path.Join(g.outputDir, g.sanitizePath(filePath)) | ||
98 | } | ||
99 | |||
100 | func (g *generator) isFileParsable(fileName string) bool { | ||
101 | for _, ext := range g.parsableExts { | ||
102 | if path.Ext(fileName) == ext { | ||
103 | return true | ||
104 | } | ||
105 | } | ||
106 | return false | ||
107 | } | ||
108 | |||
109 | func (g *generator) copyFile(filePath string) { | ||
110 | defer g.tasks.Done() | ||
111 | err := fcmd.Cp(g.sourcePath(filePath), g.outputPath(filePath)) | ||
112 | if err != nil { | ||
113 | fmt.Println(err) | ||
114 | } | ||
115 | } | ||
116 | |||
117 | func (g *generator) parseFile(filePath string) []byte { | ||
118 | fileBody, err := ioutil.ReadFile(g.sourcePath(filePath)) | ||
119 | if err != nil { | ||
120 | fmt.Println(err) | ||
121 | return nil | ||
122 | } | ||
123 | if path.Ext(filePath) == ".md" { | ||
124 | fileBody = blackfriday.MarkdownCommon(fileBody) | ||
125 | } | ||
126 | return fileBody | ||
127 | } | ||
128 | |||
129 | func (g *generator) mergeParts(parts parts) []byte { | ||
130 | merged := parts[g.startWith] | ||
131 | for pass := 0; bytes.Contains(merged, []byte("{{> ")) && pass < 1000; pass++ { | ||
132 | for partName, partBody := range parts { | ||
133 | merged = bytes.Replace(merged, []byte("{{> "+partName+"}}"), partBody, -1) | ||
134 | } | ||
135 | } | ||
136 | return merged | ||
137 | } | ||
138 | |||
139 | func (g *generator) contextualize(page page) page { | ||
140 | _, page.Title = path.Split(page.dirPath) | ||
141 | if page.dirPath == "" { | ||
142 | page.IsRoot = true | ||
143 | page.AbsPath, page.Path = "/", "/" | ||
144 | } else { | ||
145 | page.AbsPath = g.sanitizePath("/" + page.dirPath) | ||
146 | _, page.Path = path.Split(page.AbsPath) | ||
147 | } | ||
148 | |||
149 | page.IsCurrent = func(params []string, data string) string { | ||
150 | if page.Path == path.Clean(params[0]) { | ||
151 | return data | ||
152 | } | ||
153 | return "" | ||
154 | } | ||
155 | |||
156 | page.IsParent = func(params []string, data string) string { | ||
157 | if strings.Contains(page.AbsPath, path.Clean(params[0])) { | ||
158 | return data | ||
159 | } | ||
160 | return "" | ||
161 | } | ||
162 | |||
163 | return page | ||
164 | } | ||
165 | |||
166 | func (g *generator) generate(page page) { | ||
167 | defer g.tasks.Done() | ||
168 | |||
169 | dirs, files := fcmd.Ls(g.sourcePath(page.dirPath)) | ||
170 | |||
171 | // Parse or copy files in the current directory | ||
172 | containsParsableFiles := false | ||
173 | for _, file := range files { | ||
174 | filePath := path.Join(page.dirPath, file) | ||
175 | if g.isFileParsable(file) { | ||
176 | containsParsableFiles = true | ||
177 | page.parts[file[:len(file)-len(path.Ext(file))]] = g.parseFile(filePath) | ||
178 | } else { | ||
179 | g.tasks.Add(1) | ||
180 | go g.copyFile(filePath) | ||
181 | } | ||
182 | } | ||
183 | |||
184 | // Generate subpages in surdirectories | ||
185 | currentDirPath := page.dirPath | ||
186 | for _, dir := range dirs { | ||
187 | page.dirPath = path.Join(currentDirPath, dir) | ||
188 | g.tasks.Add(1) | ||
189 | go g.generate(page) | ||
190 | page.dirPath = currentDirPath | ||
191 | } | ||
192 | |||
193 | // Generate the page at the current directory | ||
194 | if containsParsableFiles { | ||
195 | page.body = []byte(mustache.Render(string(g.mergeParts(page.parts)), g.contextualize(page))) | ||
196 | |||
197 | err := fcmd.WriteFile(g.outputPath(path.Join(page.dirPath, g.saveAs)), page.body) | ||
198 | if err != nil { | ||
199 | fmt.Println(err) | ||
200 | } | ||
52 | } | 201 | } |
53 | *settings.sourceDir = strings.TrimPrefix(*settings.sourceDir, "./") | ||
54 | *settings.outputDir = strings.TrimPrefix(*settings.outputDir, "./") | ||
55 | } | 202 | } |
56 | 203 | ||
57 | func main() { | 204 | func main() { |
58 | fmt.Println("FoldaWeb <https://github.com/Pacien/FoldaWeb>") | 205 | fmt.Println("FoldaWeb <https://github.com/Pacien/FoldaWeb>") |
59 | fmt.Println("Mode: " + *settings.mode) | 206 | |
60 | fmt.Println("Source: " + *settings.sourceDir) | 207 | g := newGenerator() |
61 | fmt.Println("Output: " + *settings.outputDir) | 208 | |
62 | fmt.Println("====================") | 209 | // Remove previously generated site |
63 | 210 | err := fcmd.Rm(g.outputDir) | |
64 | switch *settings.mode { | 211 | if err != nil { |
65 | case "compiled": | 212 | fmt.Println(err) |
66 | compiled(*settings.sourceDir, *settings.outputDir, settings.exts, *settings.saveAs) | 213 | return |
67 | case "interactive": | ||
68 | interactive(*settings.sourceDir, *settings.outputDir, settings.exts, *settings.saveAs) | ||
69 | case "dynamic": | ||
70 | dynamic(*settings.port) | ||
71 | default: | ||
72 | fmt.Println("Invalid mode.") | ||
73 | } | 214 | } |
215 | |||
216 | // Generate everything | ||
217 | page := page{} | ||
218 | page.parts = make(parts) | ||
219 | g.tasks.Add(1) | ||
220 | go g.generate(page) | ||
221 | |||
222 | // Wait until all tasks are completed | ||
223 | g.tasks.Wait() | ||
224 | fmt.Println("Done.") | ||
74 | } | 225 | } |