aboutsummaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go221
1 files changed, 186 insertions, 35 deletions
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}