From 7f4e93785cd49471b4d902620c9ea4d523d874c7 Mon Sep 17 00:00:00 2001 From: Pacien Date: Tue, 16 Jul 2013 12:50:25 +0200 Subject: First version --- main.go | 221 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 186 insertions(+), 35 deletions(-) (limited to 'main.go') diff --git a/main.go b/main.go index d93977c..050ffe6 100644 --- a/main.go +++ b/main.go @@ -17,58 +17,209 @@ */ +// FoldaWeb, a "keep last legacy" website generator package main import ( + "bytes" "flag" "fmt" "github.com/Pacien/fcmd" + "github.com/drbawb/mustache" + "github.com/frankbille/sanitize" + "github.com/russross/blackfriday" + "io/ioutil" + "path" "strings" + "sync" ) -var settings struct { - mode *string // compiled, interactive or dynamic - sourceDir *string - outputDir *string // for compiled site - port *string // for the integrated web server (dynamic mode only) - exts []string - saveAs *string +type generator struct { + // parameters + sourceDir, outputDir string + startWith, saveAs string + wordSeparator string + parsableExts []string + + // go routine sync + tasks sync.WaitGroup +} + +type page struct { + // properties accessible in templates + Title string + AbsPath, Path string + IsRoot bool + IsCurrent, IsParent func(params []string, data string) string + + // properties used for page generation + dirPath string + parts parts + body []byte } -func init() { - fcmd.DefaultPerm = 0755 // -rwxr-xr-x +type parts map[string][]byte + +// Creates an initiated generator +func newGenerator() (g generator) { + + // Read the command line arguments + flag.StringVar(&g.sourceDir, "sourceDir", "./source", "Path to the source directory.") + flag.StringVar(&g.outputDir, "outputDir", "./out", "Path to the output directory.") + flag.StringVar(&g.startWith, "startWith", "index", "Name without extension of the first file that will by parsed.") + flag.StringVar(&g.saveAs, "saveAs", "index.html", "Save compiled files as named.") + flag.StringVar(&g.wordSeparator, "wordSeparator", "-", "Word separator used to replace spaces in URLs.") + var parsableExts string + flag.StringVar(&parsableExts, "parsableExts", "html, txt, md", "Parsable file extensions separated by commas.") - // read settings - settings.mode = flag.String("mode", "compiled", "compiled|interactive|dynamic") - settings.sourceDir = flag.String("source", "./source", "Path to sources directory.") - settings.outputDir = flag.String("output", "./out", "[compiled mode] Path to output directory.") - settings.port = flag.String("port", "8080", "[dynamic mode] Port to listen.") - exts := flag.String("exts", "html, txt, md", "List parsable file extensions. Separated by commas.") - settings.saveAs = flag.String("saveAs", "index.html", "[compiled and interactive modes] Save compiled files as named.") flag.Parse() - settings.exts = strings.Split(*exts, ",") - for i, ext := range settings.exts { - settings.exts[i] = "." + strings.Trim(ext, ". ") + + g.sourceDir = path.Clean(g.sourceDir) + g.outputDir = path.Clean(g.outputDir) + for _, ext := range strings.Split(parsableExts, ",") { + g.parsableExts = append(g.parsableExts, "."+strings.Trim(ext, ". ")) + } + + return + +} + +func (g *generator) sanitizePath(filePath string) string { + sanitizedFilePath := strings.Replace(filePath, " ", g.wordSeparator, -1) + return sanitize.Path(sanitizedFilePath) +} + +func (g *generator) sourcePath(filePath string) string { + return path.Join(g.sourceDir, filePath) +} + +func (g *generator) outputPath(filePath string) string { + return path.Join(g.outputDir, g.sanitizePath(filePath)) +} + +func (g *generator) isFileParsable(fileName string) bool { + for _, ext := range g.parsableExts { + if path.Ext(fileName) == ext { + return true + } + } + return false +} + +func (g *generator) copyFile(filePath string) { + defer g.tasks.Done() + err := fcmd.Cp(g.sourcePath(filePath), g.outputPath(filePath)) + if err != nil { + fmt.Println(err) + } +} + +func (g *generator) parseFile(filePath string) []byte { + fileBody, err := ioutil.ReadFile(g.sourcePath(filePath)) + if err != nil { + fmt.Println(err) + return nil + } + if path.Ext(filePath) == ".md" { + fileBody = blackfriday.MarkdownCommon(fileBody) + } + return fileBody +} + +func (g *generator) mergeParts(parts parts) []byte { + merged := parts[g.startWith] + for pass := 0; bytes.Contains(merged, []byte("{{> ")) && pass < 1000; pass++ { + for partName, partBody := range parts { + merged = bytes.Replace(merged, []byte("{{> "+partName+"}}"), partBody, -1) + } + } + return merged +} + +func (g *generator) contextualize(page page) page { + _, page.Title = path.Split(page.dirPath) + if page.dirPath == "" { + page.IsRoot = true + page.AbsPath, page.Path = "/", "/" + } else { + page.AbsPath = g.sanitizePath("/" + page.dirPath) + _, page.Path = path.Split(page.AbsPath) + } + + page.IsCurrent = func(params []string, data string) string { + if page.Path == path.Clean(params[0]) { + return data + } + return "" + } + + page.IsParent = func(params []string, data string) string { + if strings.Contains(page.AbsPath, path.Clean(params[0])) { + return data + } + return "" + } + + return page +} + +func (g *generator) generate(page page) { + defer g.tasks.Done() + + dirs, files := fcmd.Ls(g.sourcePath(page.dirPath)) + + // Parse or copy files in the current directory + containsParsableFiles := false + for _, file := range files { + filePath := path.Join(page.dirPath, file) + if g.isFileParsable(file) { + containsParsableFiles = true + page.parts[file[:len(file)-len(path.Ext(file))]] = g.parseFile(filePath) + } else { + g.tasks.Add(1) + go g.copyFile(filePath) + } + } + + // Generate subpages in surdirectories + currentDirPath := page.dirPath + for _, dir := range dirs { + page.dirPath = path.Join(currentDirPath, dir) + g.tasks.Add(1) + go g.generate(page) + page.dirPath = currentDirPath + } + + // Generate the page at the current directory + if containsParsableFiles { + page.body = []byte(mustache.Render(string(g.mergeParts(page.parts)), g.contextualize(page))) + + err := fcmd.WriteFile(g.outputPath(path.Join(page.dirPath, g.saveAs)), page.body) + if err != nil { + fmt.Println(err) + } } - *settings.sourceDir = strings.TrimPrefix(*settings.sourceDir, "./") - *settings.outputDir = strings.TrimPrefix(*settings.outputDir, "./") } func main() { fmt.Println("FoldaWeb ") - fmt.Println("Mode: " + *settings.mode) - fmt.Println("Source: " + *settings.sourceDir) - fmt.Println("Output: " + *settings.outputDir) - fmt.Println("====================") - - switch *settings.mode { - case "compiled": - compiled(*settings.sourceDir, *settings.outputDir, settings.exts, *settings.saveAs) - case "interactive": - interactive(*settings.sourceDir, *settings.outputDir, settings.exts, *settings.saveAs) - case "dynamic": - dynamic(*settings.port) - default: - fmt.Println("Invalid mode.") + + g := newGenerator() + + // Remove previously generated site + err := fcmd.Rm(g.outputDir) + if err != nil { + fmt.Println(err) + return } + + // Generate everything + page := page{} + page.parts = make(parts) + g.tasks.Add(1) + go g.generate(page) + + // Wait until all tasks are completed + g.tasks.Wait() + fmt.Println("Done.") } -- cgit v1.2.3