aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPacien2013-07-16 12:50:25 +0200
committerPacien2013-07-16 12:50:25 +0200
commit7f4e93785cd49471b4d902620c9ea4d523d874c7 (patch)
tree035a4b7f0c4de624ea00481e89162251272e4904
parentec4932d86f805864cd35fc27a2f91964b55982d4 (diff)
downloadfoldaweb-7f4e93785cd49471b4d902620c9ea4d523d874c7.tar.gz
First version
-rw-r--r--README.md43
-rw-r--r--common.go143
-rw-r--r--compiled.go43
-rw-r--r--context.go97
-rw-r--r--dynamic.go82
-rw-r--r--interactive.go131
-rw-r--r--main.go221
7 files changed, 228 insertions, 532 deletions
diff --git a/README.md b/README.md
index 1df51f5..6d737a0 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,45 @@
1FoldaWeb 1FoldaWeb
2======== 2========
3 3
4A tree structure based website generator. 4### Description
5
6FoldaWeb is a "keep last legacy" website generator: the program generates a page from parts of pages that are presents inside a directory and directly inside its parents. If multiple parts have the same name, it uses the last one (the one located the deepest in the directories before the current included).
7
8This behaviour makes particularly easy to create well-organized websites with many subpages of different types with associated layouts for each.
9
10___
11
12### Features
13
14- Unique "keep last legacy" generation (no pun intended)
15- Mustache templating: FoldaWeb uses [Mustache](http://mustache.github.io/mustache.5.html) as template engine and adds several handy contextual variables
16- Markdown compatible: pages can be written using the [Markdown syntax](http://daringfireball.net/projects/markdown/syntax)
17
18Moreover, because FoldaWeb generates static files, generated websites are:
19
20- **Portable**: any host and web server software can serve flat files.
21- **Fast**: no server-side scripting is required everytime someone loads a page
22- **Secure**: no CMS security flaws
23
24___
25
26### Example
27
28[Multiverse Inc. Global Website](http://multiverse.pacien.net) is an example of website generated using FoldaWeb.
29
30Its sources are available on GitHub at [Pacien/FoldaWeb-example](https://github.com/Pacien/FoldaWeb-example)
31
32___
33
34### Usage
35
36Simply put the binary inside a directory containing a `source` folder with the website's sources inside and run the program (simply open the executable). Another folder named `out` containing the generated website will be created instantly.
37
38You can also pass custom settings via command line arguments:
39
40 -sourceDir="./source": Path to the source directory.
41 -outputDir="./out": Path to the output directory.
42 -parsableExts="html, txt, md": Parsable file extensions separated by commas.
43 -saveAs="index.html": Save compiled files as named.
44 -startWith="index": Name without extension of the first file that will by parsed.
45 -wordSeparator="-": Word separator used to replace spaces in URLs.
diff --git a/common.go b/common.go
deleted file mode 100644
index 3203945..0000000
--- a/common.go
+++ /dev/null
@@ -1,143 +0,0 @@
1/*
2
3 This file is part of FoldaWeb <https://github.com/Pacien/FoldaWeb>
4
5 FoldaWeb is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Affero General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 FoldaWeb is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with FoldaWeb. If not, see <http://www.gnu.org/licenses/>.
17
18*/
19
20package main
21
22import (
23 "bytes"
24 "fmt"
25 "github.com/Pacien/fcmd"
26 "github.com/drbawb/mustache"
27 "github.com/russross/blackfriday"
28 "io/ioutil"
29 "path"
30 "strings"
31 "sync"
32)
33
34var wait sync.WaitGroup
35
36// Common templating
37
38func isParsable(fileName string, exts []string) bool {
39 for _, ext := range exts {
40 if path.Ext(fileName) == ext {
41 return true
42 }
43 }
44 return false
45}
46
47func read(fileName string) ([]byte, error) {
48 fileBody, err := ioutil.ReadFile(fileName)
49 if err != nil {
50 return nil, err
51 }
52 if path.Ext(fileName) == ".md" {
53 fileBody = blackfriday.MarkdownCommon(fileBody)
54 }
55 return fileBody, nil
56}
57
58func merge(files map[string][]byte) (merged []byte) {
59 merged = files["index"]
60 for pass := 0; bytes.Contains(merged, []byte("{{> ")) && pass < 4000; pass++ {
61 for fileName, fileBody := range files {
62 merged = bytes.Replace(merged, []byte("{{> "+fileName+"}}"), fileBody, -1)
63 }
64 }
65 return
66}
67
68// COMPILED and INTERACTIVE modes
69
70func parse(dirPath string, elements map[string][]byte, exts []string, overwrite bool) (map[string][]byte, bool) {
71 parsed := false
72 _, filesList := fcmd.Ls(dirPath)
73 for _, fileName := range filesList {
74 if isParsable(fileName, exts) && (overwrite || elements[fileName[:len(fileName)-len(path.Ext(fileName))]] == nil) {
75 var err error
76 elements[fileName[:len(fileName)-len(path.Ext(fileName))]], err = read(path.Join(dirPath, fileName))
77 if err != nil {
78 fmt.Println(err)
79 }
80 parsed = true
81 }
82 }
83 return elements, parsed
84}
85
86func compile(dirPath string, elements map[string][]byte, sourceDir, outputDir, saveAs string, exts []string, recursive bool) {
87 defer wait.Done()
88
89 if strings.HasPrefix(dirPath, outputDir) {
90 return
91 }
92
93 parsed := false
94 elements, parsed = parse(dirPath, elements, exts, true)
95
96 if recursive {
97 dirs, _ := fcmd.Ls(dirPath)
98 for _, dir := range dirs {
99 wait.Add(1)
100 go compile(path.Join(dirPath, dir), elements, sourceDir, outputDir, saveAs, exts, recursive)
101 }
102 }
103
104 if !parsed {
105 return
106 }
107
108 pagePath := strings.TrimPrefix(dirPath, sourceDir)
109
110 template := merge(elements)
111 page := mustache.Render(string(template), makeContext(pagePath, sourceDir, exts))
112
113 err := fcmd.WriteFile(path.Join(outputDir, pagePath, saveAs), []byte(page))
114 if err != nil {
115 fmt.Println(err)
116 return
117 }
118}
119
120func copyFiles(dirPath, sourceDir, outputDir string, exts []string, recursive bool) {
121 defer wait.Done()
122
123 if strings.HasPrefix(dirPath, outputDir) {
124 return
125 }
126
127 dirs, files := fcmd.Ls(dirPath)
128 for _, file := range files {
129 if !isParsable(file, exts) {
130 err := fcmd.Cp(path.Join(dirPath, file), path.Join(outputDir, strings.TrimPrefix(dirPath, sourceDir), file))
131 if err != nil {
132 fmt.Println(err)
133 }
134 }
135 }
136
137 if recursive {
138 for _, dir := range dirs {
139 wait.Add(1)
140 go copyFiles(path.Join(dirPath, dir), sourceDir, outputDir, exts, recursive)
141 }
142 }
143}
diff --git a/compiled.go b/compiled.go
deleted file mode 100644
index 7a3bd67..0000000
--- a/compiled.go
+++ /dev/null
@@ -1,43 +0,0 @@
1/*
2
3 This file is part of FoldaWeb <https://github.com/Pacien/FoldaWeb>
4
5 FoldaWeb is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Affero General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 FoldaWeb is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with FoldaWeb. If not, see <http://www.gnu.org/licenses/>.
17
18*/
19
20package main
21
22import (
23 "fmt"
24 "os"
25)
26
27func compiled(sourceDir, outputDir string, exts []string, saveAs string) {
28 // remove previously compiled site
29 err := os.RemoveAll(outputDir)
30 if err != nil {
31 fmt.Println(err)
32 return
33 }
34
35 // compile everything
36 wait.Add(2)
37 go compile(sourceDir, make(map[string][]byte), sourceDir, outputDir, saveAs, exts, true)
38 go copyFiles(sourceDir, sourceDir, outputDir, exts, true)
39
40 // wait until all tasks are completed
41 wait.Wait()
42 fmt.Println("Compilation done.")
43}
diff --git a/context.go b/context.go
deleted file mode 100644
index 27a8c4a..0000000
--- a/context.go
+++ /dev/null
@@ -1,97 +0,0 @@
1/*
2
3 This file is part of FoldaWeb <https://github.com/Pacien/FoldaWeb>
4
5 FoldaWeb is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Affero General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 FoldaWeb is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with FoldaWeb. If not, see <http://www.gnu.org/licenses/>.
17
18*/
19
20package main
21
22import (
23 "github.com/Pacien/fcmd"
24 "path"
25 "strings"
26)
27
28type page struct {
29 Title string
30 Path string
31}
32
33type context struct {
34 filePath string
35 Path string
36 IsCurrent func(params []string, data string) string
37 IsParent func(params []string, data string) string
38}
39
40// Methods accessible in templates
41
42func (c context) Title() string {
43 _, t := path.Split(strings.TrimRight(c.Path, "/"))
44 return t
45}
46
47func (c context) SubPages() (subPages []page) {
48 dirs, _ := fcmd.Ls(c.filePath)
49 for _, dir := range dirs {
50 var page page
51 page.Title = dir
52 page.Path = path.Join(c.Path, dir)
53 subPages = append(subPages, page)
54 }
55 return
56}
57
58func (c context) IsRoot() bool {
59 if c.Path == "/" {
60 return true
61 }
62 return false
63}
64
65func (c context) isCurrent(pageTitle string) bool {
66 if c.Title() == pageTitle {
67 return true
68 }
69 return false
70}
71
72func (c context) isParent(pageTitle string) bool {
73 for _, parent := range strings.Split(c.Path, "/") {
74 if parent == pageTitle {
75 return true
76 }
77 }
78 return false
79}
80
81func makeContext(pagePath, sourceDir string, exts []string) (c context) {
82 c.Path = path.Clean("/" + pagePath)
83 c.filePath = path.Join(sourceDir, c.Path)
84 c.IsCurrent = func(params []string, data string) string {
85 if c.isCurrent(strings.Join(params, " ")) {
86 return data
87 }
88 return ""
89 }
90 c.IsParent = func(params []string, data string) string {
91 if c.isParent(strings.Join(params, " ")) {
92 return data
93 }
94 return ""
95 }
96 return
97}
diff --git a/dynamic.go b/dynamic.go
deleted file mode 100644
index 892ba71..0000000
--- a/dynamic.go
+++ /dev/null
@@ -1,82 +0,0 @@
1/*
2
3 This file is part of FoldaWeb <https://github.com/Pacien/FoldaWeb>
4
5 FoldaWeb is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Affero General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 FoldaWeb is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with FoldaWeb. If not, see <http://www.gnu.org/licenses/>.
17
18*/
19
20package main
21
22import (
23 "fmt"
24 "github.com/drbawb/mustache"
25 "net/http"
26 "path"
27 "strings"
28)
29
30func handle(w http.ResponseWriter, r *http.Request) {
31 // serve static files
32 if !(path.Ext(r.URL.Path) == "" || isParsable(path.Ext(r.URL.Path), settings.exts)) {
33 http.ServeFile(w, r, path.Join(*settings.sourceDir, r.URL.Path))
34 return
35 }
36
37 // redirect to add the trailing slash if missing
38 if !strings.HasSuffix(r.URL.Path, "/") {
39 http.Redirect(w, r, r.URL.Path+"/", http.StatusFound)
40 return
41 }
42
43 // get the list of dirs to parse
44 request := strings.TrimSuffix(r.URL.Path, "/")
45 dirs := strings.Split(request, "/")
46 for i, dir := range dirs {
47 if i != 0 {
48 dirs[i] = path.Join(dirs[i-1], dir)
49 }
50 }
51
52 // parse these dirs
53 elements := make(map[string][]byte)
54 for i := len(dirs) - 1; i >= 0; i-- {
55 parsed := false
56 elements, parsed = parse(path.Join(*settings.sourceDir, dirs[i]), elements, settings.exts, false)
57 if (i == len(dirs)-1) && !parsed {
58 http.Error(w, "404 page not found", http.StatusNotFound)
59 return
60 }
61 }
62
63 // render the page
64 template := merge(elements)
65 page := mustache.Render(string(template), makeContext(r.URL.Path, *settings.sourceDir, settings.exts))
66
67 // serve the page
68 _, err := w.Write([]byte(page))
69 if err != nil {
70 fmt.Println(err)
71 return
72 }
73}
74
75func dynamic(port string) {
76 fmt.Println("Listening on: localhost:" + port)
77 http.HandleFunc("/", handle)
78 err := http.ListenAndServe(":"+port, nil)
79 if err != nil {
80 fmt.Println(err)
81 }
82}
diff --git a/interactive.go b/interactive.go
deleted file mode 100644
index 8ce32ca..0000000
--- a/interactive.go
+++ /dev/null
@@ -1,131 +0,0 @@
1/*
2
3 This file is part of FoldaWeb <https://github.com/Pacien/FoldaWeb>
4
5 FoldaWeb is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Affero General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 FoldaWeb is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with FoldaWeb. If not, see <http://www.gnu.org/licenses/>.
17
18*/
19
20package main
21
22import (
23 "fmt"
24 "github.com/Pacien/fcmd"
25 "github.com/howeyc/fsnotify"
26 "os"
27 "path"
28 "strings"
29)
30
31func watch(dirPath string, watcher *fsnotify.Watcher) *fsnotify.Watcher {
32 watcher.Watch(dirPath)
33 dirs, _ := fcmd.Explore(dirPath)
34 for _, dir := range dirs {
35 if !strings.HasPrefix(dir, *settings.outputDir) {
36 err := watcher.Watch(dir)
37 if err != nil {
38 fmt.Println(err)
39 }
40 }
41 }
42 return watcher
43}
44
45func parseParents(dir, sourceDir string, exts []string) map[string][]byte {
46 dirs := strings.Split(strings.TrimPrefix(dir, sourceDir), "/")
47 elements := make(map[string][]byte)
48 for _, dir := range dirs {
49 elements, _ = parse(path.Join(sourceDir, dir), elements, exts, false)
50 }
51 return elements
52}
53
54func interactive(sourceDir, outputDir string, exts []string, saveAs string) {
55
56 // compile the whole site
57 compiled(sourceDir, outputDir, exts, saveAs)
58
59 // watch the source dir
60 watcher, err := fsnotify.NewWatcher()
61 if err != nil {
62 fmt.Println(err)
63 }
64 defer watcher.Close()
65 watcher = watch(sourceDir, watcher)
66
67 for {
68 select {
69 case ev := <-watcher.Event:
70 fmt.Println(ev)
71
72 // ignore hidden files
73 if fcmd.IsHidden(ev.Name) {
74 break
75 }
76
77 // manage watchers
78 if ev.IsDelete() || ev.IsRename() {
79 err = watcher.RemoveWatch(ev.Name)
80 if err != nil {
81 fmt.Println(err)
82 return
83 }
84 } else if ev.IsCreate() && fcmd.IsDir(ev.Name) {
85 watcher = watch(ev.Name, watcher)
86 }
87
88 dir, _ := path.Split(ev.Name)
89
90 // remove previously compiled files
91 if ev.IsDelete() || ev.IsRename() || ev.IsModify() {
92 var err error
93 if fcmd.IsDir(ev.Name) || !isParsable(ev.Name, exts) {
94 err = os.RemoveAll(path.Join(outputDir, strings.TrimPrefix(ev.Name, sourceDir)))
95 } else {
96 err = os.RemoveAll(path.Join(outputDir, strings.TrimPrefix(dir, sourceDir)))
97 }
98 if err != nil {
99 fmt.Println(err)
100 return
101 }
102 }
103
104 // recompile changed files
105 if ev.IsCreate() || ev.IsModify() {
106 if fcmd.IsDir(ev.Name) {
107 elements := parseParents(ev.Name, sourceDir, exts)
108 dirPath := path.Join(sourceDir, strings.TrimPrefix(ev.Name, sourceDir))
109 wait.Add(2)
110 go compile(dirPath, elements, sourceDir, outputDir, saveAs, exts, true)
111 go copyFiles(dirPath, sourceDir, outputDir, exts, true)
112 } else {
113 dirPath := path.Join(sourceDir, strings.TrimPrefix(dir, sourceDir))
114 if isParsable(path.Ext(ev.Name), exts) {
115 elements := parseParents(dir, sourceDir, exts)
116 wait.Add(1)
117 go compile(dirPath, elements, sourceDir, outputDir, saveAs, exts, true)
118 }
119 wait.Add(1)
120 go copyFiles(dirPath, sourceDir, outputDir, exts, false)
121 }
122 }
123
124 // wait until all tasks are completed
125 wait.Wait()
126
127 case err := <-watcher.Error:
128 fmt.Println(err)
129 }
130 }
131}
diff --git a/main.go b/main.go
index d93977c..050ffe6 100644
--- a/main.go
+++ b/main.go
@@ -17,58 +17,209 @@
17 17
18*/ 18*/
19 19
20// FoldaWeb, a "keep last legacy" website generator
20package main 21package main
21 22
22import ( 23import (
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
29var settings struct { 37type 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
48type 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
38func init() { 61type parts map[string][]byte
39 fcmd.DefaultPerm = 0755 // -rwxr-xr-x 62
63// Creates an initiated generator
64func 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
87func (g *generator) sanitizePath(filePath string) string {
88 sanitizedFilePath := strings.Replace(filePath, " ", g.wordSeparator, -1)
89 return sanitize.Path(sanitizedFilePath)
90}
91
92func (g *generator) sourcePath(filePath string) string {
93 return path.Join(g.sourceDir, filePath)
94}
95
96func (g *generator) outputPath(filePath string) string {
97 return path.Join(g.outputDir, g.sanitizePath(filePath))
98}
99
100func (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
109func (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
117func (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
129func (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
139func (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
166func (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
57func main() { 204func 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}