all repos — vite @ 137b16164bd5fac82d5fbdc17e4db0717fc82cab

a fast (this time, actually) and minimal static site generator

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}