github.com/elves/elvish@v0.15.0/website/cmd/genblog/main.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "path/filepath" 8 "sort" 9 "time" 10 ) 11 12 func main() { 13 args := os.Args[1:] 14 if len(args) != 2 { 15 log.Fatal("Usage: genblog <src dir> <dst dir>") 16 } 17 srcDir, dstDir := args[0], args[1] 18 srcFile := func(elem ...string) string { 19 elem = append([]string{srcDir}, elem...) 20 return filepath.Join(elem...) 21 } 22 dstFile := func(elem ...string) string { 23 elem = append([]string{dstDir}, elem...) 24 return filepath.Join(elem...) 25 } 26 27 // Read blog configuration. 28 conf := &blogConf{} 29 decodeTOML(srcFile("index.toml"), conf) 30 if conf.RootURL == "" { 31 log.Fatal("RootURL must be specified; needed by feed and sitemap") 32 } 33 if conf.Template == "" { 34 log.Fatal("Template must be specified") 35 } 36 if conf.BaseCSS == nil { 37 log.Fatal("BaseCSS must be specified") 38 } 39 40 template := readFile(srcFile(conf.Template)) 41 baseCSS := catInDir(srcDir, conf.BaseCSS) 42 43 // Initialize templates. They are all initialized from the same source code, 44 // plus a snippet to fix the "content" reference. 45 categoryTmpl := newTemplate("category", "..", template, contentIs("category")) 46 articleTmpl := newTemplate("article", "..", template, contentIs("article")) 47 homepageTmpl := newTemplate("homepage", ".", template, contentIs("article")) 48 feedTmpl := newTemplate("feed", ".", feedTemplText) 49 50 // Base for the {{ . }} object used in all templates. 51 base := newBaseDot(conf, baseCSS) 52 53 // Up to conf.FeedPosts recent posts, used in the feed. 54 recents := recentArticles{nil, conf.FeedPosts} 55 // Last modified time of the newest post, used in the feed. 56 var lastModified time.Time 57 58 // Whether the "all" category has been requested. 59 hasAllCategory := false 60 // Meta of all articles, used to generate the index of the "all", if if is 61 // requested. 62 allArticleMetas := []articleMeta{} 63 64 // Paths of all generated URLs, relative to the destination directory, 65 // always without "index.html". Used to generate the sitemap. 66 allPaths := []string{""} 67 68 // Render a category index. 69 renderCategoryIndex := func(name, prelude, css, js string, articles []articleMeta) { 70 // Add category index to the sitemap, without "/index.html" 71 allPaths = append(allPaths, name) 72 // Create directory 73 catDir := dstFile(name) 74 err := os.MkdirAll(catDir, 0755) 75 if err != nil { 76 log.Fatal(err) 77 } 78 79 // Generate index 80 cd := &categoryDot{base, name, prelude, articles, css, js} 81 executeToFile(categoryTmpl, cd, filepath.Join(catDir, "index.html")) 82 } 83 84 for _, cat := range conf.Categories { 85 if cat.Name == "all" { 86 // The "all" category has been requested. It is a pseudo-category in 87 // that it doesn't need to have any associated category 88 // configuration file. We cannot render the category index now 89 // because we haven't seen all articles yet. Render it later. 90 hasAllCategory = true 91 continue 92 } 93 94 catConf := &categoryConf{} 95 decodeTOML(srcFile(cat.Name, "index.toml"), catConf) 96 97 prelude := "" 98 if catConf.Prelude != "" { 99 prelude = readFile(srcFile(cat.Name, catConf.Prelude+".html")) 100 } 101 css := catInDir(srcFile(cat.Name), catConf.ExtraCSS) 102 js := catInDir(srcFile(cat.Name), catConf.ExtraJS) 103 var articles []articleMeta 104 if catConf.AutoIndex { 105 articles = catConf.Articles 106 } 107 renderCategoryIndex(cat.Name, prelude, css, js, articles) 108 109 // Generate articles 110 for _, am := range catConf.Articles { 111 // Add article URL to sitemap. 112 p := filepath.Join(cat.Name, am.Name+".html") 113 allPaths = append(allPaths, p) 114 115 a := getArticle(article{Category: cat.Name}, am, srcFile(cat.Name)) 116 modTime := time.Time(a.LastModified) 117 if modTime.After(lastModified) { 118 lastModified = modTime 119 } 120 121 // Generate article page. 122 ad := &articleDot{base, a} 123 executeToFile(articleTmpl, ad, dstFile(p)) 124 125 allArticleMetas = append(allArticleMetas, a.articleMeta) 126 recents.insert(a) 127 } 128 } 129 130 // Generate "all category" 131 if hasAllCategory { 132 sort.Slice(allArticleMetas, func(i, j int) bool { 133 return allArticleMetas[i].Timestamp > allArticleMetas[j].Timestamp 134 }) 135 renderCategoryIndex("all", "", "", "", allArticleMetas) 136 } 137 138 // Generate index page. XXX(xiaq): duplicated code with generating ordinary 139 // article pages. 140 a := getArticle(article{IsHomepage: true, Category: "homepage"}, conf.Index, srcDir) 141 ad := &articleDot{base, a} 142 executeToFile(homepageTmpl, ad, dstFile("index.html")) 143 144 // Generate feed. 145 feedArticles := recents.articles 146 fd := feedDot{base, feedArticles, rfc3339Time(lastModified)} 147 executeToFile(feedTmpl, fd, dstFile("feed.atom")) 148 149 // Generate site map. 150 file := openForWrite(dstFile("sitemap.txt")) 151 defer file.Close() 152 for _, p := range allPaths { 153 fmt.Fprintf(file, "%s/%s\n", conf.RootURL, p) 154 } 155 }