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  }