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 util.CopyFile(src, BUILD)
87 }
88 }
89 return nil
90}
91
92func (pgs *Pages) processDirs() error {
93 for _, d := range pgs.Dirs {
94 // ex: build/blog
95 dstDir := filepath.Join(BUILD, d)
96 // ex: pages/blog
97 srcDir := filepath.Join(PAGES, d)
98 os.Mkdir(dstDir, 0755)
99
100 entries, err := os.ReadDir(srcDir)
101 if err != nil {
102 return err
103 }
104
105 posts := []markdown.Output{}
106 // Collect all posts
107 for _, e := range entries {
108 // foo-bar.md -> foo-bar
109 slug := strings.TrimSuffix(e.Name(), filepath.Ext(e.Name()))
110
111 // ex: build/blog/foo-bar/
112 os.Mkdir(filepath.Join(dstDir, slug), 0755)
113 // ex: build/blog/foo-bar/index.html
114 htmlFile := filepath.Join(dstDir, slug, "index.html")
115
116 if e.Name() != "_index.md" {
117 ePath := filepath.Join(srcDir, e.Name())
118 fb, err := os.ReadFile(ePath)
119 if err != nil {
120 return err
121 }
122
123 out := markdown.Output{}
124 out.RenderMarkdown(fb)
125
126 if err = out.RenderHTML(
127 htmlFile,
128 TEMPLATES,
129 struct {
130 Cfg config.ConfigYaml
131 Meta markdown.Matter
132 Body string
133 }{config.Config, out.Meta, string(out.HTML)},
134 ); err != nil {
135 return err
136 }
137 posts = append(posts, out)
138 }
139
140 // Sort posts slice by date
141 sort.Slice(posts, func(i, j int) bool {
142 dateStr1 := posts[j].Meta["date"]
143 dateStr2 := posts[i].Meta["date"]
144 date1, _ := time.Parse("2006-01-02", dateStr1)
145 date2, _ := time.Parse("2006-01-02", dateStr2)
146 return date1.Before(date2)
147 })
148 }
149
150 // Render index using posts slice.
151 // ex: build/blog/index.html
152 indexHTML := filepath.Join(dstDir, "index.html")
153 // ex: pages/blog/_index.md
154 indexMd, err := os.ReadFile(filepath.Join(srcDir, "_index.md"))
155 if err != nil {
156 return err
157 }
158 out := markdown.Output{}
159 out.RenderMarkdown(indexMd)
160
161 out.RenderHTML(indexHTML, TEMPLATES, struct {
162 Cfg config.ConfigYaml
163 Meta markdown.Matter
164 Body string
165 Posts []markdown.Output
166 }{config.Config, out.Meta, string(out.HTML), posts})
167
168 // Create feeds
169 // ex: build/blog/feed.xml
170 xml, err := atom.NewAtomFeed(d, posts)
171 if err != nil {
172 return err
173 }
174 feedFile := filepath.Join(dstDir, "feed.xml")
175 os.WriteFile(feedFile, xml, 0755)
176 }
177 return nil
178}
179
180// Core builder function. Converts markdown to html,
181// copies over non .md files, etc.
182func Build() error {
183 pages := Pages{}
184 if err := pages.initPages(); err != nil {
185 return err
186 }
187
188 // Clean the build directory.
189 if err := util.Clean(BUILD); err != nil {
190 return err
191 }
192
193 // Deal with files.
194 // ex: pages/{_index,about,etc}.md
195 if err := pages.processFiles(); err != nil {
196 return err
197 }
198
199 // Deal with dirs -- i.e. dirs of markdown files.
200 // ex: pages/{blog,travel}/*.md
201 if err := pages.processDirs(); err != nil {
202 return err
203 }
204
205 // Copy the static directory into build
206 // ex: build/static/
207 buildStatic := filepath.Join(BUILD, STATIC)
208 os.Mkdir(buildStatic, 0755)
209 if err := util.CopyDir(STATIC, buildStatic); err != nil {
210 return err
211 }
212
213 return nil
214}