commands/build.go (view raw)
1package commands
2
3import (
4 "os"
5 "path/filepath"
6 "sort"
7 "strings"
8 "time"
9
10 "git.icyphox.sh/vite/atom"
11 "git.icyphox.sh/vite/config"
12 "git.icyphox.sh/vite/markdown"
13 "git.icyphox.sh/vite/util"
14)
15
16const (
17 BUILD = "build"
18 PAGES = "pages"
19 TEMPLATES = "templates"
20 STATIC = "static"
21)
22
23type Pages struct {
24 Dirs []string
25 Files []string
26}
27
28// Populates a Pages object with dirs and files
29// found in 'pages/'.
30func (pgs *Pages) initPages() error {
31 files, err := os.ReadDir("./pages")
32 if err != nil {
33 return err
34 }
35
36 for _, f := range files {
37 if f.IsDir() {
38 pgs.Dirs = append(pgs.Dirs, f.Name())
39 } else {
40 pgs.Files = append(pgs.Files, f.Name())
41 }
42 }
43
44 return nil
45}
46
47func (pgs *Pages) processFiles() error {
48 for _, f := range pgs.Files {
49 if filepath.Ext(f) == ".md" {
50 // ex: pages/about.md
51 mdFile := filepath.Join(PAGES, f)
52 var htmlDir string
53 // ex: build/index.html (root index)
54 if f == "_index.md" {
55 htmlDir = BUILD
56 } else {
57 htmlDir = filepath.Join(
58 BUILD,
59 strings.TrimSuffix(f, ".md"),
60 )
61 }
62 os.Mkdir(htmlDir, 0755)
63 // ex: build/about/index.html
64 htmlFile := filepath.Join(htmlDir, "index.html")
65
66 fb, err := os.ReadFile(mdFile)
67 if err != nil {
68 return err
69 }
70
71 out := markdown.Output{}
72 out.RenderMarkdown(fb)
73 if err = out.RenderHTML(
74 htmlFile,
75 TEMPLATES,
76 struct {
77 Cfg config.ConfigYaml
78 Meta markdown.Matter
79 Body string
80 }{config.Config, out.Meta, string(out.HTML)},
81 ); err != nil {
82 return err
83 }
84 } else {
85 src := filepath.Join(PAGES, f)
86 dst := filepath.Join(BUILD, f)
87 if err := util.CopyFile(src, dst); err != nil {
88 return err
89 }
90 }
91 }
92 return nil
93}
94
95func (pgs *Pages) processDirs() error {
96 for _, d := range pgs.Dirs {
97 // ex: build/blog
98 dstDir := filepath.Join(BUILD, d)
99 // ex: pages/blog
100 srcDir := filepath.Join(PAGES, d)
101 os.Mkdir(dstDir, 0755)
102
103 entries, err := os.ReadDir(srcDir)
104 if err != nil {
105 return err
106 }
107
108 posts := []markdown.Output{}
109 // Collect all posts
110 for _, e := range entries {
111 // foo-bar.md -> foo-bar
112 slug := strings.TrimSuffix(e.Name(), filepath.Ext(e.Name()))
113
114 // ex: build/blog/foo-bar/
115 os.Mkdir(filepath.Join(dstDir, slug), 0755)
116 // ex: build/blog/foo-bar/index.html
117 htmlFile := filepath.Join(dstDir, slug, "index.html")
118
119 if e.Name() != "_index.md" {
120 ePath := filepath.Join(srcDir, e.Name())
121 fb, err := os.ReadFile(ePath)
122 if err != nil {
123 return err
124 }
125
126 out := markdown.Output{}
127 out.RenderMarkdown(fb)
128
129 if err = out.RenderHTML(
130 htmlFile,
131 TEMPLATES,
132 struct {
133 Cfg config.ConfigYaml
134 Meta markdown.Matter
135 Body string
136 }{config.Config, out.Meta, string(out.HTML)},
137 ); err != nil {
138 return err
139 }
140 posts = append(posts, out)
141 }
142
143 // Sort posts slice by date
144 sort.Slice(posts, func(i, j int) bool {
145 dateStr1 := posts[j].Meta["date"]
146 dateStr2 := posts[i].Meta["date"]
147 date1, _ := time.Parse("2006-01-02", dateStr1)
148 date2, _ := time.Parse("2006-01-02", dateStr2)
149 return date1.Before(date2)
150 })
151 }
152
153 // Render index using posts slice.
154 // ex: build/blog/index.html
155 indexHTML := filepath.Join(dstDir, "index.html")
156 // ex: pages/blog/_index.md
157 indexMd, err := os.ReadFile(filepath.Join(srcDir, "_index.md"))
158 if err != nil {
159 return err
160 }
161 out := markdown.Output{}
162 out.RenderMarkdown(indexMd)
163
164 out.RenderHTML(indexHTML, TEMPLATES, struct {
165 Cfg config.ConfigYaml
166 Meta markdown.Matter
167 Body string
168 Posts []markdown.Output
169 }{config.Config, out.Meta, string(out.HTML), posts})
170
171 // Create feeds
172 // ex: build/blog/feed.xml
173 xml, err := atom.NewAtomFeed(d, posts)
174 if err != nil {
175 return err
176 }
177 feedFile := filepath.Join(dstDir, "feed.xml")
178 os.WriteFile(feedFile, xml, 0755)
179 }
180 return nil
181}
182
183// Core builder function. Converts markdown to html,
184// copies over non .md files, etc.
185func Build() error {
186 pages := Pages{}
187 if err := pages.initPages(); err != nil {
188 return err
189 }
190
191 // Clean the build directory.
192 if err := util.Clean(BUILD); err != nil {
193 return err
194 }
195
196 // Deal with files.
197 // ex: pages/{_index,about,etc}.md
198 if err := pages.processFiles(); err != nil {
199 return err
200 }
201
202 // Deal with dirs -- i.e. dirs of markdown files.
203 // ex: pages/{blog,travel}/*.md
204 if err := pages.processDirs(); err != nil {
205 return err
206 }
207
208 // Copy the static directory into build
209 // ex: build/static/
210 buildStatic := filepath.Join(BUILD, STATIC)
211 os.Mkdir(buildStatic, 0755)
212 if err := util.CopyDir(STATIC, buildStatic); err != nil {
213 return err
214 }
215
216 return nil
217}