github.com/elves/elvish@v0.15.0/website/cmd/genblog/blog.go (about) 1 package main 2 3 import ( 4 "io/ioutil" 5 "log" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/BurntSushi/toml" 11 ) 12 13 // This file contains functions and types for parsing and manipulating the 14 // in-memory representation of the blog. 15 16 // blogConf represents the global blog configuration. 17 type blogConf struct { 18 Title string 19 Author string 20 Categories []categoryMeta 21 Index articleMeta 22 FeedPosts int 23 RootURL string 24 Template string 25 BaseCSS []string 26 } 27 28 // categoryMeta represents the metadata of a cateogory, found in the global 29 // blog configuration. 30 type categoryMeta struct { 31 Name string 32 Title string 33 } 34 35 // categoryConf represents the configuration of a category. Note that the 36 // metadata is found in the global blog configuration and not duplicated here. 37 type categoryConf struct { 38 Prelude string 39 AutoIndex bool 40 ExtraCSS []string 41 ExtraJS []string 42 Articles []articleMeta 43 } 44 45 // articleMeta represents the metadata of an article, found in a category 46 // configuration. 47 type articleMeta struct { 48 Name string 49 Title string 50 Timestamp string 51 ExtraCSS []string 52 ExtraJS []string 53 } 54 55 // article represents an article, including all information that is needed to 56 // render it. 57 type article struct { 58 articleMeta 59 IsHomepage bool 60 Category string 61 Content string 62 ExtraCSS string 63 ExtraJS string 64 LastModified rfc3339Time 65 } 66 67 type recentArticles struct { 68 articles []article 69 max int 70 } 71 72 func (ra *recentArticles) insert(a article) { 73 // Find a place to insert. 74 var i int 75 for i = len(ra.articles); i > 0; i-- { 76 if ra.articles[i-1].Timestamp > a.Timestamp { 77 break 78 } 79 } 80 // If we are at the end, insert only if we haven't reached the maximum 81 // number of articles. 82 if i == len(ra.articles) { 83 if i < ra.max { 84 ra.articles = append(ra.articles, a) 85 } 86 return 87 } 88 // If not, make space and insert. 89 if len(ra.articles) < ra.max { 90 ra.articles = append(ra.articles, article{}) 91 } 92 copy(ra.articles[i+1:], ra.articles[i:]) 93 ra.articles[i] = a 94 } 95 96 // decodeFile decodes the named file in TOML into a pointer. 97 func decodeTOML(fname string, v interface{}) { 98 _, err := toml.DecodeFile(fname, v) 99 if err != nil { 100 log.Fatalln(err) 101 } 102 } 103 104 func readFile(fname string) string { 105 content, err := ioutil.ReadFile(fname) 106 if err != nil { 107 log.Fatal(err) 108 } 109 return string(content) 110 } 111 112 func catInDir(dirname string, fnames []string) string { 113 var sb strings.Builder 114 for _, fname := range fnames { 115 sb.WriteString(readFile(filepath.Join(dirname, fname))) 116 } 117 return sb.String() 118 } 119 120 func getArticle(a article, am articleMeta, dir string) article { 121 fname := filepath.Join(dir, am.Name+".html") 122 content := readFile(fname) 123 fileInfo, err := os.Stat(fname) 124 if err != nil { 125 log.Fatal(err) 126 } 127 modTime := fileInfo.ModTime() 128 css := catInDir(dir, am.ExtraCSS) 129 js := catInDir(dir, am.ExtraJS) 130 return article{ 131 am, a.IsHomepage, a.Category, content, css, js, rfc3339Time(modTime)} 132 }