summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/build/build.go290
-rw-r--r--internal/build/parser.go82
-rw-r--r--internal/build/renderer.go104
3 files changed, 259 insertions, 217 deletions
diff --git a/internal/build/build.go b/internal/build/build.go
index 4511745..aadae66 100644
--- a/internal/build/build.go
+++ b/internal/build/build.go
@@ -3,260 +3,116 @@ package build
import (
"fmt"
"html/template"
- "io/fs"
"log"
- "os"
- "path/filepath"
- "sort"
- "strings"
- "time"
- "gopkg.in/yaml.v2"
-
- "github.com/adrg/frontmatter"
- "github.com/gomarkdown/markdown"
- "github.com/gomarkdown/markdown/ast"
- "github.com/gomarkdown/markdown/html"
- "github.com/gomarkdown/markdown/parser"
+ "github.com/HimanshuSardana/kite/pkg/content"
+ "github.com/HimanshuSardana/kite/pkg/themes"
)
-var (
- themeName = "gruvbox"
- contentDir = "./content"
- outputDir = "./output"
+const (
+ DefaultContentDir = "./content"
+ DefaultOutputDir = "./output"
+ DefaultThemesDir = "./themes"
+ DefaultConfigPath = "./config.yaml"
+ DefaultThemeName = "modern-light"
)
-type Frontmatter struct {
- Title string `yaml:"title"`
- Date string `yaml:"date"`
- Tags []string `yaml:"tags"`
-}
-
-type Page struct {
- Title string
- Content template.HTML
- TOC []TOCItem
-}
-
-type TOCItem struct {
- Level int
- Text string
- ID string
-}
-
-type PostSummary struct {
- Title string
- Slug string
- Date string
- Tags []string
-}
-
-type HomePage struct {
- SiteTitle string `yaml:"siteTitle"`
- AuthorName string `yaml:"authorName"`
- AuthorRole string `yaml:"authorRole"`
- AuthorBio string `yaml:"authorBio"`
- Year int
- Posts []PostSummary
-}
-
-var posts = make([]Post, 0)
-
-type Post struct {
- Title string
+type BuildOptions struct {
+ ThemeName string
+ ContentDir string
+ OutputDir string
+ ThemesDir string
+ ConfigPath string
}
-func Build(themeName string) {
- if themeName == "" {
- themeName = "modern-light"
+func Build(opts BuildOptions) error {
+ if opts.ThemeName == "" {
+ opts.ThemeName = DefaultThemeName
}
-
- summaries := make([]PostSummary, 0)
-
- err := filepath.WalkDir(contentDir, func(path string, d fs.DirEntry, err error) error {
- if err != nil {
- return err
- }
-
- if d.IsDir() {
- return nil
- }
-
- if strings.HasSuffix(d.Name(), ".md") {
- fmt.Println("Processing:", path)
-
- matter, htmlContent, toc := convertToHtml(path)
- slug := strings.TrimSuffix(d.Name(), ".md")
- summaries = append(summaries, PostSummary{
- Title: matter.Title,
- Slug: slug,
- Date: matter.Date,
- Tags: matter.Tags,
- })
-
- posts = append(posts, Post{Title: matter.Title})
- // fmt.Println("Appended post", posts)
- newPage := Page{
- Title: matter.Title,
- Content: template.HTML(htmlContent),
- TOC: toc,
- }
-
- tmpl, err := template.ParseFiles("./themes/" + themeName + "/layout.html")
- if err != nil {
- log.Fatalf("Error parsing template: %s", err)
- }
-
- relPath, err := filepath.Rel(contentDir, path)
- if err != nil {
- log.Fatalf("Error computing relative path: %s", err)
- }
- // test.md -> test.html
- // test.md -> test/index.html
- outputFilePath := filepath.Join(outputDir, strings.Replace(relPath, ".md", "/index.html", 1))
-
- err = os.MkdirAll(filepath.Dir(outputFilePath), 0o755)
- if err != nil {
- log.Fatalf("Error creating directories: %s", err)
- }
-
- outputFile, err := os.Create(outputFilePath)
- if err != nil {
- log.Fatalf("Error creating output file: %s", err)
- }
- defer outputFile.Close()
-
- err = tmpl.Execute(outputFile, newPage)
- if err != nil {
- log.Fatalf("Error generating output content: %s", err)
- }
- }
-
- return nil
- })
- if err != nil {
- log.Fatalf("Error walking directory: %s", err)
+ if opts.ContentDir == "" {
+ opts.ContentDir = DefaultContentDir
}
-
- fmt.Println("All files processed!")
-
- fmt.Println(posts)
- fmt.Println(themeName)
-
- renderHomePage(themeName, summaries, outputDir)
-}
-
-func convertToHtml(path string) (Frontmatter, []byte, []TOCItem) {
- md, err := os.ReadFile(path)
- if err != nil {
- log.Fatalf("Error reading %s: %s", path, err)
+ if opts.OutputDir == "" {
+ opts.OutputDir = DefaultOutputDir
}
-
- var matter Frontmatter
- rest, err := frontmatter.Parse(strings.NewReader(string(md)), &matter)
- if err != nil {
- log.Fatalf("Error parsing frontmatter: %s", err)
+ if opts.ThemesDir == "" {
+ opts.ThemesDir = DefaultThemesDir
+ }
+ if opts.ConfigPath == "" {
+ opts.ConfigPath = DefaultConfigPath
}
- extensions := parser.CommonExtensions | parser.AutoHeadingIDs
- p := parser.NewWithExtensions(extensions)
+ themePath := themes.GetThemePath(opts.ThemesDir, opts.ThemeName)
- doc := p.Parse(rest)
+ files, err := content.ListContentFiles(opts.ContentDir)
+ if err != nil {
+ return fmt.Errorf("listing content files: %w", err)
+ }
- var toc []TOCItem
+ summaries := make([]content.PostSummary, 0, len(files))
- ast.WalkFunc(doc, func(node ast.Node, entering bool) ast.WalkStatus {
- if heading, ok := node.(*ast.Heading); ok && entering {
- text := extractText(heading)
- id := string(heading.HeadingID)
+ for _, file := range files {
+ fmt.Println("Processing:", file.Path)
- toc = append(toc, TOCItem{
- Level: heading.Level,
- Text: text,
- ID: id,
- })
+ parsed, err := ParseMarkdown(file.Path)
+ if err != nil {
+ log.Printf("Error parsing %s: %v", file.Path, err)
+ continue
}
- return ast.GoToNext
- })
-
- renderer := html.NewRenderer(html.RendererOptions{
- Flags: html.CommonFlags,
- })
-
- output := markdown.Render(doc, renderer)
- return matter, output, toc
-}
+ summaries = append(summaries, content.PostSummary{
+ Title: parsed.Frontmatter.Title,
+ Slug: file.Slug,
+ Date: parsed.Frontmatter.Date,
+ Tags: parsed.Frontmatter.Tags,
+ })
-func extractText(h *ast.Heading) string {
- var text string
- ast.WalkFunc(h, func(node ast.Node, entering bool) ast.WalkStatus {
- if leaf, ok := node.(*ast.Text); ok && entering {
- text += string(leaf.Literal)
+ outputPath, err := content.GetOutputPath(opts.ContentDir, file.Path, opts.OutputDir)
+ if err != nil {
+ log.Printf("Error computing output path: %v", err)
+ continue
}
- return ast.GoToNext
- })
- return text
-}
-func renderHomePage(themeName string, summaries []PostSummary, outputDir string) {
- sort.Slice(summaries, func(i, j int) bool {
- return summaries[i].Date > summaries[j].Date
- })
+ tmpl, err := LoadTemplate(themePath, "layout.html")
+ if err != nil {
+ log.Fatalf("Error loading template: %v", err)
+ }
- for i, p := range summaries {
- if t, err := time.Parse("2006-01-02", p.Date); err == nil {
- summaries[i].Date = t.Format("Jan 2006")
+ page := Page{
+ Title: parsed.Frontmatter.Title,
+ Content: template.HTML(parsed.Content),
+ TOC: parsed.TOC,
}
- }
- config, err := os.ReadFile("config.yaml")
- if err != nil {
- panic(err)
+ if err := RenderPage(tmpl, outputPath, page); err != nil {
+ log.Printf("Error rendering page: %v", err)
+ }
}
- var data HomePage
- err = yaml.Unmarshal(config, &data)
- data.Posts = summaries
- data.Year = time.Now().Year()
- if err != nil {
- panic(err)
- }
+ fmt.Println("All files processed!")
- tmpl, err := template.ParseFiles("./themes/" + themeName + "/home.html")
- if err != nil {
- log.Fatalf("Error parsing home template: %s", err)
+ if err := RenderHomePage(themePath, opts.OutputDir, opts.ConfigPath, summaries); err != nil {
+ log.Printf("Error rendering home page: %v", err)
}
- outPath := filepath.Join(outputDir, "index.html")
- if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil {
- log.Fatalf("Error creating output dir: %s", err)
- }
- f, err := os.Create(outPath)
- if err != nil {
- log.Fatalf("Error creating index.html: %s", err)
- }
- defer f.Close()
+ return nil
+}
- if err := tmpl.Execute(f, data); err != nil {
- log.Fatalf("Error rendering home page: %s", err)
+func ListThemes(themesDir string) []string {
+ if themesDir == "" {
+ themesDir = DefaultThemesDir
}
- fmt.Println("Home page written to", outPath)
-}
-func ListThemes() []string {
- themeList := make([]string, 0)
- themes, err := os.ReadDir("./themes")
+ themeList, err := themes.List(themesDir)
if err != nil {
log.Fatal("Error:", err)
}
- for _, theme := range themes {
- if theme.IsDir() {
- themeList = append(themeList, string(theme.Name()))
- }
- }
- return themeList
+ result := make([]string, len(themeList))
+ for i, t := range themeList {
+ result[i] = t.Name
+ }
+ return result
}
func ShowHelpMessage() {
diff --git a/internal/build/parser.go b/internal/build/parser.go
new file mode 100644
index 0000000..e1ef3b0
--- /dev/null
+++ b/internal/build/parser.go
@@ -0,0 +1,82 @@
+package build
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/adrg/frontmatter"
+ "github.com/gomarkdown/markdown"
+ "github.com/gomarkdown/markdown/ast"
+ "github.com/gomarkdown/markdown/html"
+ "github.com/gomarkdown/markdown/parser"
+
+ "github.com/HimanshuSardana/kite/pkg/content"
+)
+
+type TOCItem struct {
+ Level int
+ Text string
+ ID string
+}
+
+type ParsedPage struct {
+ Frontmatter content.Frontmatter
+ Content []byte
+ TOC []TOCItem
+}
+
+func ParseMarkdown(path string) (*ParsedPage, error) {
+ md, err := os.ReadFile(path)
+ if err != nil {
+ return nil, fmt.Errorf("reading %s: %w", path, err)
+ }
+
+ var matter content.Frontmatter
+ rest, err := frontmatter.Parse(strings.NewReader(string(md)), &matter)
+ if err != nil {
+ return nil, fmt.Errorf("parsing frontmatter: %w", err)
+ }
+
+ extensions := parser.CommonExtensions | parser.AutoHeadingIDs
+ p := parser.NewWithExtensions(extensions)
+ doc := p.Parse(rest)
+
+ var toc []TOCItem
+ ast.WalkFunc(doc, func(node ast.Node, entering bool) ast.WalkStatus {
+ if heading, ok := node.(*ast.Heading); ok && entering {
+ text := extractText(heading)
+ id := string(heading.HeadingID)
+
+ toc = append(toc, TOCItem{
+ Level: heading.Level,
+ Text: text,
+ ID: id,
+ })
+ }
+ return ast.GoToNext
+ })
+
+ renderer := html.NewRenderer(html.RendererOptions{
+ Flags: html.CommonFlags,
+ })
+
+ output := markdown.Render(doc, renderer)
+
+ return &ParsedPage{
+ Frontmatter: matter,
+ Content: output,
+ TOC: toc,
+ }, nil
+}
+
+func extractText(h *ast.Heading) string {
+ var text string
+ ast.WalkFunc(h, func(node ast.Node, entering bool) ast.WalkStatus {
+ if leaf, ok := node.(*ast.Text); ok && entering {
+ text += string(leaf.Literal)
+ }
+ return ast.GoToNext
+ })
+ return text
+}
diff --git a/internal/build/renderer.go b/internal/build/renderer.go
new file mode 100644
index 0000000..0ef5386
--- /dev/null
+++ b/internal/build/renderer.go
@@ -0,0 +1,104 @@
+package build
+
+import (
+ "fmt"
+ "html/template"
+ "os"
+ "path/filepath"
+ "sort"
+ "time"
+
+ "github.com/HimanshuSardana/kite/pkg/config"
+ "github.com/HimanshuSardana/kite/pkg/content"
+)
+
+type Page struct {
+ Title string
+ Content template.HTML
+ TOC []TOCItem
+}
+
+type HomePageData struct {
+ SiteTitle string
+ AuthorName string
+ AuthorRole string
+ AuthorBio string
+ DefaultTheme string
+ Year int
+ Posts []content.PostSummary
+}
+
+func RenderPage(tmpl *template.Template, outputPath string, data Page) error {
+ if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil {
+ return fmt.Errorf("creating directories: %w", err)
+ }
+
+ outputFile, err := os.Create(outputPath)
+ if err != nil {
+ return fmt.Errorf("creating output file: %w", err)
+ }
+ defer outputFile.Close()
+
+ if err := tmpl.Execute(outputFile, data); err != nil {
+ return fmt.Errorf("executing template: %w", err)
+ }
+
+ return nil
+}
+
+func LoadTemplate(themePath, templateFile string) (*template.Template, error) {
+ tmpl, err := template.ParseFiles(filepath.Join(themePath, templateFile))
+ if err != nil {
+ return nil, fmt.Errorf("parsing template: %w", err)
+ }
+ return tmpl, nil
+}
+
+func RenderHomePage(themePath, outputDir, configPath string, summaries []content.PostSummary) error {
+ sort.Slice(summaries, func(i, j int) bool {
+ return summaries[i].Date > summaries[j].Date
+ })
+
+ for i, p := range summaries {
+ if t, err := time.Parse("2006-01-02", p.Date); err == nil {
+ summaries[i].Date = t.Format("Jan 2006")
+ }
+ }
+
+ cfg, err := config.Load(configPath)
+ if err != nil {
+ return fmt.Errorf("loading config: %w", err)
+ }
+
+ data := HomePageData{
+ SiteTitle: cfg.SiteTitle,
+ AuthorName: cfg.AuthorName,
+ AuthorRole: cfg.AuthorRole,
+ AuthorBio: cfg.AuthorBio,
+ DefaultTheme: cfg.DefaultTheme,
+ Year: time.Now().Year(),
+ Posts: summaries,
+ }
+
+ tmpl, err := LoadTemplate(themePath, "home.html")
+ if err != nil {
+ return err
+ }
+
+ outPath := filepath.Join(outputDir, "index.html")
+ if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil {
+ return fmt.Errorf("creating output dir: %w", err)
+ }
+ f, err := os.Create(outPath)
+ if err != nil {
+ return fmt.Errorf("creating index.html: %w", err)
+ }
+ defer f.Close()
+
+ if err := tmpl.Execute(f, data); err != nil {
+ return fmt.Errorf("rendering home page: %w", err)
+ }
+ fmt.Println("Home page written to", outPath)
+
+ return nil
+}