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