Skip to content

Commit 4f6906b

Browse files
committed
cleanup: small changes and new description
1 parent f94170b commit 4f6906b

File tree

5 files changed

+58
-72
lines changed

5 files changed

+58
-72
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<source media="(prefers-color-scheme: light)" srcset="https://github.com/user-attachments/assets/c76ceb71-6ad5-49a8-89df-5084d7a713ba">
55
<img alt="Feed logo" src="https://github.com/user-attachments/assets/c76ceb71-6ad5-49a8-89df-5084d7a713ba" width="220">
66
</picture>
7-
<p>A modern, fast, and easy-to-use RSS and Atom feed generator for Deno.
7+
<p>A modern, fast, and easy-to-use RSS, JSON and Atom feed generator for Deno.
88
<br><small>Replacement for <a href="https://npmjs.com/package/feed">feed</a> package.</small></p>
99
</div>
1010

src/atom.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ export class AtomFeed extends BaseFeed<AtomEntry> {
2929
` <generator>${this.options.generator || "Feed for Deno"}</generator>\n`,
3030
];
3131

32-
this.options.authors.forEach((author) => {
32+
for (const author of this.options.authors) {
3333
xmlParts.push(
3434
` <author>\n`,
3535
` <name>${escapeXml(author.name)}</name>\n`,
3636
` <email>${escapeXml(author.email)}</email>\n`,
3737
` </author>\n`,
3838
);
39-
});
39+
}
4040

4141
if (this.options.icon) {
4242
xmlParts.push(` <icon>${escapeXml(this.options.icon)}</icon>\n`);
@@ -49,7 +49,7 @@ export class AtomFeed extends BaseFeed<AtomEntry> {
4949
);
5050
}
5151

52-
this.items.forEach((entry) => {
52+
for (const entry of this.items) {
5353
xmlParts.push(
5454
` <entry>\n`,
5555
` <title>${escapeXml(entry.title)}</title>\n`,
@@ -65,11 +65,11 @@ export class AtomFeed extends BaseFeed<AtomEntry> {
6565
: "",
6666
` </entry>\n`,
6767
);
68-
});
68+
}
6969

70-
this.categories.forEach((category) => {
70+
for (const category of this.categories) {
7171
xmlParts.push(` <category term="${escapeXml(category)}"/>\n`);
72-
});
72+
}
7373

7474
xmlParts.push(`</feed>\n`);
7575
return xmlParts.join("");

src/common.ts

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,36 +20,28 @@ export interface FeedOptions {
2020
}
2121

2222
export function escapeXml(unsafe: string): string {
23-
return unsafe.replace(/[<>&'"]/g, (c) => {
24-
switch (c) {
25-
case "<":
26-
return "&lt;";
27-
case ">":
28-
return "&gt;";
29-
case "&":
30-
return "&amp;";
31-
case "'":
32-
return "&apos;";
33-
case '"':
34-
return "&quot;";
35-
default:
36-
return c;
37-
}
38-
});
23+
const escapeMap: { [key: string]: string } = {
24+
"<": "&lt;",
25+
">": "&gt;",
26+
"&": "&amp;",
27+
"'": "&apos;",
28+
'"': "&quot;",
29+
};
30+
return unsafe.replace(/[<>&'"]/g, (c) => escapeMap[c] || c);
3931
}
4032

4133
export abstract class BaseFeed<T> {
4234
protected options: FeedOptions;
4335
protected items: Array<T>;
44-
protected categories: Array<string>;
36+
protected categories: Set<string>;
4537

4638
constructor(options: FeedOptions) {
4739
this.options = {
4840
...options,
4941
updated: options.updated || new Date(),
5042
};
5143
this.items = [];
52-
this.categories = [];
44+
this.categories = new Set();
5345
}
5446

5547
abstract build(): string;
@@ -59,7 +51,7 @@ export abstract class BaseFeed<T> {
5951
}
6052

6153
addCategory(category: string) {
62-
this.categories.push(category);
54+
this.categories.add(category);
6355
}
6456

6557
addContributor(contributor: { name: string; email: string; link?: string }) {

src/json.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,15 @@ export class JsonFeed extends BaseFeed<JsonItem> {
2121
feed_url: this.options.feed,
2222
icon: this.options.icon,
2323
updated: this.options.updated?.toISOString(),
24-
items: this.items.map((item) => {
25-
const jsonItem: Record<string, unknown> = {
26-
id: item.id,
27-
title: item.title,
28-
url: item.url,
29-
date_published: item.date_published.toISOString(),
30-
};
31-
if (item.content_html) {
32-
jsonItem.content_html = item.content_html;
33-
}
34-
return jsonItem;
35-
}),
24+
items: this.items.map((
25+
{ id, title, url, date_published, content_html },
26+
) => ({
27+
id,
28+
title,
29+
url,
30+
date_published: date_published.toISOString(),
31+
...(content_html && { content_html }),
32+
})),
3633
};
3734
return JSON.stringify(json, null, 2);
3835
}

src/rss.ts

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,44 +28,41 @@ export class RssFeed extends BaseFeed<RssItem> {
2828
` <lastBuildDate>${this.options.updated?.toUTCString()}</lastBuildDate>\n`,
2929
` <language>${this.options.language || "en"}</language>\n`,
3030
` <generator>${
31-
this.options.generator || "Feed for Deno"
31+
escapeXml(this.options.generator || "Feed for Deno")
3232
}</generator>\n`,
3333
];
3434

35-
this.options.authors.forEach((author) => {
36-
xmlParts.push(
37-
` <webMaster>${escapeXml(author.email)} (${
38-
escapeXml(author.name)
39-
})</webMaster>\n`,
40-
` <author>${escapeXml(author.email)} (${
41-
escapeXml(author.name)
42-
})</author>\n`,
43-
` <managingEditor>${escapeXml(author.email)} (${
44-
escapeXml(author.name)
45-
})</managingEditor>\n`,
46-
);
47-
});
35+
const authorXml = this.options.authors.map((author) =>
36+
` <webMaster>${escapeXml(author.email)} (${
37+
escapeXml(author.name)
38+
})</webMaster>\n` +
39+
` <author>${escapeXml(author.email)} (${
40+
escapeXml(author.name)
41+
})</author>\n` +
42+
` <managingEditor>${escapeXml(author.email)} (${
43+
escapeXml(author.name)
44+
})</managingEditor>\n`
45+
).join("");
46+
xmlParts.push(authorXml);
4847

49-
this.items.forEach((item) => {
50-
const itemParts: string[] = [
51-
` <item>\n`,
52-
` <title>${escapeXml(item.title)}</title>\n`,
53-
` <link>${escapeXml(item.link)}</link>\n`,
54-
` <guid>${escapeXml(item.id)}</guid>\n`,
55-
` <pubDate>${item.updated.toUTCString()}</pubDate>\n`,
56-
` <description>${escapeXml(item.description)}</description>\n`,
57-
];
58-
if (item.content) {
59-
const contentType = item.content.type || "text";
60-
itemParts.push(
61-
` <content:encoded type="${contentType}">${
62-
escapeXml(item.content.body)
63-
}</content:encoded>\n`,
64-
);
65-
}
66-
itemParts.push(` </item>\n`);
67-
xmlParts.push(...itemParts);
68-
});
48+
const itemsXml = this.items.map((item) => {
49+
const contentXml = item.content
50+
? ` <content:encoded type="${
51+
escapeXml(item.content.type || "text")
52+
}">${escapeXml(item.content.body)}</content:encoded>\n`
53+
: "";
54+
return (
55+
` <item>\n` +
56+
` <title>${escapeXml(item.title)}</title>\n` +
57+
` <link>${escapeXml(item.link)}</link>\n` +
58+
` <guid>${escapeXml(item.id)}</guid>\n` +
59+
` <pubDate>${item.updated.toUTCString()}</pubDate>\n` +
60+
` <description>${escapeXml(item.description)}</description>\n` +
61+
contentXml +
62+
` </item>\n`
63+
);
64+
}).join("");
65+
xmlParts.push(itemsXml);
6966

7067
xmlParts.push(` </channel>\n`, `</rss>\n`);
7168
return xmlParts.join("");

0 commit comments

Comments
 (0)