diff options
| author | Himanshu Sardana <himanshusardana2005@gmail.com> | 2026-03-26 21:26:35 +0000 |
|---|---|---|
| committer | Himanshu Sardana <himanshusardana2005@gmail.com> | 2026-03-26 21:26:35 +0000 |
| commit | 103e84d847262830bbaa550b37218e9ca8b317d3 (patch) | |
| tree | e19d3bfd6594600fb28be1ccac1a3869207bc49c /internal | |
| parent | 5c631f0cdb8ee3238ff054d171dd8babd158047b (diff) | |
refactor: split into cmd, pkg
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/build/build.go | 290 | ||||
| -rw-r--r-- | internal/build/parser.go | 82 | ||||
| -rw-r--r-- | internal/build/renderer.go | 104 |
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 +} |
