summaryrefslogtreecommitdiff
path: root/internal/build/rss.go
blob: ad22615f9f2b2d8f543c628b0c1d22bb56b09801 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package build

import (
	"fmt"
	"os"
	"time"

	"github.com/HimanshuSardana/kite/pkg/config"
	"github.com/HimanshuSardana/kite/pkg/content"
)

type RSSItem struct {
	Title   string
	Link    string
	Date    string
	Content string
}

type RSSFeed struct {
	Title       string
	Link        string
	Description string
	Items       []RSSItem
}

func GenerateRSS(outputDir, configPath, siteURL string, posts []content.PostSummary) error {
	if configPath == "" {
		configPath = "config.yaml"
	}

	cfg, err := config.Load(configPath)
	if err != nil {
		return fmt.Errorf("loading config: %w", err)
	}

	feed := RSSFeed{
		Title:       cfg.SiteTitle,
		Link:        siteURL,
		Description: cfg.AuthorBio,
		Items:       make([]RSSItem, 0, len(posts)),
	}

	for _, post := range posts {
		dateStr := post.Date
		var pubDate time.Time

		if t, err := time.Parse("2006-01-02", post.Date); err == nil {
			pubDate = t
			dateStr = pubDate.Format(time.RFC1123)
		} else if t, err := time.Parse("Jan 2006", post.Date); err == nil {
			pubDate = t
			dateStr = pubDate.Format(time.RFC1123)
		}

		feed.Items = append(feed.Items, RSSItem{
			Title:   post.Title,
			Link:    fmt.Sprintf("%s/%s/", siteURL, post.Slug),
			Date:    dateStr,
			Content: fmt.Sprintf("Read more at %s/%s/", siteURL, post.Slug),
		})
	}

	rssContent := renderRSS(feed)

	feedPath := "feed.xml"
	if outputDir != "" && outputDir != "." {
		feedPath = outputDir + "/feed.xml"
	}

	if err := os.WriteFile(feedPath, []byte(rssContent), 0644); err != nil {
		return fmt.Errorf("writing feed: %w", err)
	}

	fmt.Println("Feed written to", feedPath)
	return nil
}

func renderRSS(feed RSSFeed) string {
	updated := time.Now().Format(time.RFC1123)

	s := `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>` + escapeXML(feed.Title) + `</title>
    <link>` + escapeXML(feed.Link) + `</link>
    <description>` + escapeXML(feed.Description) + `</description>
    <language>en-us</language>
    <lastBuildDate>` + updated + `</lastBuildDate>
    <atom:link href="` + escapeXML(feed.Link) + `/feed.xml" rel="self" type="application/rss+xml"/>
`

	for _, item := range feed.Items {
		s += `    <item>
      <title>` + escapeXML(item.Title) + `</title>
      <link>` + escapeXML(item.Link) + `</link>
      <guid>` + escapeXML(item.Link) + `</guid>
      <pubDate>` + item.Date + `</pubDate>
      <description>` + escapeXML(item.Content) + `</description>
    </item>
`
	}

	s += `  </channel>
</rss>`

	return s
}

func escapeXML(s string) string {
	s = replaceAll(s, "&", "&amp;")
	s = replaceAll(s, "<", "&lt;")
	s = replaceAll(s, ">", "&gt;")
	s = replaceAll(s, "\"", "&quot;")
	s = replaceAll(s, "'", "&apos;")
	return s
}

func replaceAll(s, old, new string) string {
	result := ""
	for i := 0; i < len(s); i++ {
		if i+len(old) <= len(s) && s[i:i+len(old)] == old {
			result += new
			i += len(old) - 1
		} else {
			result += string(s[i])
		}
	}
	return result
}