// https://gobyexample.com/ package main import ( "bufio" "fmt" "io" "log" "net/http" "os" "path/filepath" "strconv" "strings" "text/template" "time" ) type PageProperties struct { Title string StylePath string HeaderLinks [][]*HeaderLink } type HeaderLink struct { Name string Active bool } type Page struct { Name string Parent *Page Children []*Page } var rootPath = "." var now = time.Now() func main() { serve := false args := os.Args[1:] if len(args) != 0 { rootPath = args[0] if len(args) == 2 { flag := args[1] switch flag { case "--serve": case "-s": serve = true default: fmt.Println(flag + " is not a recognized flag.") os.Exit(1) } } } fmt.Println("Starting build...") os.RemoveAll(filepath.Join("out")) os.Mkdir(filepath.Join("out"), 0777) fmt.Print("Building wiki pages...") buildWiki(buildWikiLinkedList()) fmt.Println("Done") fmt.Print("Copying and renaming wiki CSS...") wikiCssOutPath := filepath.Join("out", "wiki", "css") copyDir( filepath.Join(rootPath, "wiki", "css"), wikiCssOutPath, ) os.Rename( filepath.Join(wikiCssOutPath, "style.css"), filepath.Join(wikiCssOutPath, "style."+strconv.FormatInt(now.Unix(), 10)+".css"), ) fmt.Println("Done") fmt.Print("Copying assets/...") copyDir( filepath.Join(rootPath, "assets"), filepath.Join("out", "assets"), ) fmt.Println("Done") fmt.Print("Copying index.html...") copyFile( filepath.Join(rootPath, "index.html"), filepath.Join("out", "index.html"), ) fmt.Println("Done") fmt.Print("copying etc/...") copyDir( filepath.Join(rootPath, "etc"), filepath.Join("out", "etc"), ) fmt.Println("Done") fmt.Println("Build complete.") if serve { http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("out")))) http.HandleFunc("GET /wiki/{page}", pageHandler(filepath.Join("out", "wiki"))) http.HandleFunc("GET /etc/{page}", pageHandler(filepath.Join("out", "etc"))) fmt.Println("Serving on http://localhost:8000") log.Fatal(http.ListenAndServe(":8000", nil)) } } func pageHandler(rootPath string) func(http.ResponseWriter, *http.Request) { return func(responseWriter http.ResponseWriter, request *http.Request) { pageName := request.PathValue("page") pageFile, err := os.ReadFile(filepath.Join(rootPath, pageName+".html")) if err == nil { responseWriter.Write(pageFile) return } http.NotFound(responseWriter, request) } } func buildWikiLinkedList() *Page { rootNode := &Page{ Name: "index", Parent: nil, } parentNode := rootNode tailNode := rootNode currentDepth := 0 tabFile, _ := os.Open(filepath.Join(rootPath, "wiki", "wiki.tab")) scanner := bufio.NewScanner(tabFile) scanner.Split(bufio.ScanLines) for scanner.Scan() { depth := strings.Count(strings.TrimRight(scanner.Text(), " "), " ") pageName := strings.TrimLeft(scanner.Text(), " ") if len(pageName) > 0 { if depth == currentDepth { newNode := &Page{ Name: pageName, Parent: parentNode, } parentNode.Children = append(parentNode.Children, newNode) tailNode = newNode } else if depth > currentDepth { currentDepth = depth parentNode = tailNode newNode := &Page{ Name: pageName, Parent: parentNode, } parentNode.Children = append(parentNode.Children, newNode) tailNode = newNode } else if depth < currentDepth { unwind := (currentDepth - depth) + 1 currentDepth = depth for range unwind { tailNode = tailNode.Parent } parentNode = tailNode newNode := &Page{ Name: pageName, Parent: parentNode, } parentNode.Children = append(parentNode.Children, newNode) tailNode = newNode } } } return rootNode } func parentLinks(page *Page) []*HeaderLink { if page.Parent == nil { return nil } if page.Parent.Parent == nil { return nil } parentPages := page.Parent.Parent.Children parentLinks := make([]*HeaderLink, len(parentPages)) for i, parentPage := range parentPages { parentLinks[i] = &HeaderLink{ Name: parentPage.Name, Active: page.Parent == parentPage, } } return parentLinks } func siblingLinks(page *Page) []*HeaderLink { if page.Parent == nil { return nil } siblingPages := page.Parent.Children siblingLinks := make([]*HeaderLink, len(siblingPages)) for i, siblingPage := range siblingPages { siblingLinks[i] = &HeaderLink{ Name: siblingPage.Name, Active: page == siblingPage, } } return siblingLinks } func childLinks(page *Page) []*HeaderLink { childPages := page.Children childLinks := make([]*HeaderLink, len(childPages)) for i, childPage := range childPages { childLinks[i] = &HeaderLink{ Name: childPage.Name, Active: false, } } return childLinks } func buildWiki(page *Page) { wikiSrcPath := filepath.Join(rootPath, "wiki") wikiOutPath := filepath.Join("out", "wiki") parents := parentLinks(page) siblings := siblingLinks(page) children := childLinks(page) headerLinks := [][]*HeaderLink{parents, siblings, children} // https://abhinavg.net/2019/07/11/zero-alloc-slice-filter/ filteredHeaderLinks := headerLinks[:0] for _, headerLink := range headerLinks { if headerLink != nil { filteredHeaderLinks = append(filteredHeaderLinks, headerLink) } } pageFilePath := filepath.Join(wikiSrcPath, "pages", page.Name+".html") pageFileInfo, _ := os.Stat(pageFilePath) if pageFileInfo == nil { copyFile( filepath.Join(wikiSrcPath, "templates", "page.html"), pageFilePath, ) } os.Mkdir(wikiOutPath, 0777) outfile, _ := os.Create(filepath.Join(wikiOutPath, page.Name+".html")) template, _ := template.ParseFiles( filepath.Join(wikiSrcPath, "templates", "document.html"), filepath.Join(wikiSrcPath, "templates", "header.html"), filepath.Join(wikiSrcPath, "pages", page.Name+".html"), ) template.Execute( outfile, &PageProperties{ Title: page.Name, StylePath: "/wiki/css/style." + strconv.FormatInt(now.Unix(), 10) + ".css", HeaderLinks: filteredHeaderLinks, }, ) for _, child := range page.Children { buildWiki(child) } } func copyFile(srcPath string, dstPath string) { dst, _ := os.Create(dstPath) src, _ := os.Open(srcPath) io.Copy(dst, src) dst.Sync() } func copyDir(srcPath string, dstPath string) { srcFS := os.DirFS(srcPath) os.CopyFS(dstPath, srcFS) } func __(foo any) {}