Skip to content

Commit 5171b88

Browse files
committed
feat(website): create RSS feed for releases
1 parent 192e419 commit 5171b88

File tree

6 files changed

+147
-9
lines changed

6 files changed

+147
-9
lines changed

website/public/releases/feed.ejs.xml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?xml version="1.0"?>
2+
<!-- Copyright (C) 2020 Matthew "strager" Glazar -->
3+
<!-- See end of file for extended copyright information. -->
4+
<rss version="2.0">
5+
<%
6+
let fs = await import("fs");
7+
let url = await import("url");
8+
let { datestampToRFC822 } = await importFileAsync("../../src/timestamp.mjs");
9+
let { parseReleaseVersionsFromMarkdown } = await importFileAsync("../../src/release-documentation.mjs");
10+
11+
let releaseVersions = parseReleaseVersionsFromMarkdown(await fs.promises.readFile(absoluteFilePath("../../../docs/CHANGELOG.md"), "utf-8"));
12+
%>
13+
14+
<channel>
15+
<title>quick-lint-js releases</title>
16+
<link>https://quick-lint-js.com/releases/</link>
17+
<description>Change log and release notes for new quick-lint-js versions.</description>
18+
19+
<language>en-US</language>
20+
<copyright>Copyright (C) 2020 Matthew "strager" Glazar</copyright>
21+
<managingEditor>[email protected] (strager)</managingEditor>
22+
<webMaster>[email protected] (strager)</webMaster>
23+
24+
<image>
25+
<url>https://quick-lint-js.com/dusty-right-256x256.png</url>
26+
<title>Dusty, the quick-lint-js mascot</title>
27+
<description>Dusty, the quick-lint-js mascot</description>
28+
<link>https://quick-lint-js.com/releases/</link>
29+
<!-- FIXME(strager): These numbers are out of spec. -->
30+
<width>256</width>
31+
<height>256</height>
32+
</image>
33+
34+
<% for (let versionInfo of releaseVersions) { %>
35+
<item>
36+
<title>quick-lint-js release: version <%= versionInfo.version %></title>
37+
<link>https://quick-lint-js.com/releases/#<%= versionInfo.version %></link>
38+
<author>strager.nds@gmail.com (strager)</author>
39+
<pubDate><%= datestampToRFC822(versionInfo.date) %></pubDate>
40+
</item>
41+
<% } %>
42+
</channel>
43+
</rss>
44+
45+
<!--
46+
quick-lint-js finds bugs in JavaScript programs.
47+
Copyright (C) 2020 Matthew "strager" Glazar
48+
49+
This file is part of quick-lint-js.
50+
51+
quick-lint-js is free software: you can redistribute it and/or modify
52+
it under the terms of the GNU General Public License as published by
53+
the Free Software Foundation, either version 3 of the License, or
54+
(at your option) any later version.
55+
56+
quick-lint-js is distributed in the hope that it will be useful,
57+
but WITHOUT ANY WARRANTY; without even the implied warranty of
58+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
59+
GNU General Public License for more details.
60+
61+
You should have received a copy of the GNU General Public License
62+
along with quick-lint-js. If not, see <https://www.gnu.org/licenses/>.
63+
-->

website/public/releases/index.ejs.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
//%>
2121
</script>
2222
<link href="../main.css" rel="stylesheet" />
23+
<link
24+
rel="alternate"
25+
type="application/rss+xml"
26+
href="<%= makeRelativeURI('/releases/feed.xml') %>"
27+
title="quick-lint-js releases"
28+
/>
2329
<style>
2430
/* Clearly separate releases. */
2531
h3 {

website/public/releases/index.mjs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (C) 2020 Matthew "strager" Glazar
2+
// See end of file for extended copyright information.
3+
4+
export let routes = {
5+
"/releases/feed.xml": {
6+
type: "build-ejs",
7+
path: "releases/feed.ejs.xml",
8+
contentType: "application/rss+xml",
9+
},
10+
};
11+
12+
// quick-lint-js finds bugs in JavaScript programs.
13+
// Copyright (C) 2020 Matthew "strager" Glazar
14+
//
15+
// This file is part of quick-lint-js.
16+
//
17+
// quick-lint-js is free software: you can redistribute it and/or modify
18+
// it under the terms of the GNU General Public License as published by
19+
// the Free Software Foundation, either version 3 of the License, or
20+
// (at your option) any later version.
21+
//
22+
// quick-lint-js is distributed in the hope that it will be useful,
23+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
24+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25+
// GNU General Public License for more details.
26+
//
27+
// You should have received a copy of the GNU General Public License
28+
// along with quick-lint-js. If not, see <https://www.gnu.org/licenses/>.

website/src/release-documentation.mjs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,24 @@ export function releasesMarkdownToHTML(releasesMarkdown) {
2121
return html;
2222
}
2323

24+
export function parseReleaseVersionsFromMarkdown(releasesMarkdown) {
25+
let env = {};
26+
let tokens = markdownParser.parse(releasesMarkdown, env);
27+
28+
let versionHeadingSpanIndexes = getVersionHeadingSpans(tokens, "h2");
29+
let versions = [];
30+
for (let [headingBeginIndex, headingEndIndex] of versionHeadingSpanIndexes) {
31+
let textTokens = tokens.slice(headingBeginIndex + 1, headingEndIndex);
32+
let text = textTokens.map((token) => token.content).join("");
33+
let versionInfo = extractVersionInfoFromHeadingText(text);
34+
if (versionInfo === null) {
35+
continue;
36+
}
37+
versions.push(versionInfo);
38+
}
39+
return versions;
40+
}
41+
2442
function removeTitle(markdownTokens) {
2543
let filteredTokens = [];
2644
let inTitle = false;
@@ -56,23 +74,26 @@ function demoteHeadings(markdownTokens) {
5674
});
5775
}
5876

59-
function linkifyVersions(markdownTokens) {
60-
markdownTokens = [...markdownTokens];
61-
62-
// @type Array<[number, number]>
63-
let versionHeadingSpanIndexes = [];
77+
// @returns Array<[number, number]>
78+
function getVersionHeadingSpans(markdownTokens, headingTag) {
79+
let spans = [];
6480
for (let i = 0; i < markdownTokens.length; ++i) {
6581
let token = markdownTokens[i];
66-
if (token.tag === "h3") {
82+
if (token.tag === headingTag) {
6783
if (token.type === "heading_open") {
68-
versionHeadingSpanIndexes.push([i]);
84+
spans.push([i]);
6985
}
7086
if (token.type === "heading_close") {
71-
versionHeadingSpanIndexes[versionHeadingSpanIndexes.length - 1].push(i);
87+
spans[spans.length - 1].push(i);
7288
}
7389
}
7490
}
91+
return spans;
92+
}
7593

94+
function linkifyVersions(markdownTokens) {
95+
markdownTokens = [...markdownTokens];
96+
let versionHeadingSpanIndexes = getVersionHeadingSpans(markdownTokens, "h3");
7697
versionHeadingSpanIndexes.reverse();
7798
for (let [headingBeginIndex, headingEndIndex] of versionHeadingSpanIndexes) {
7899
let textTokens = markdownTokens.slice(

website/src/timestamp.mjs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ export function parseTimestamp(timestamp) {
3838
};
3939
}
4040

41+
// Parses a subset of ISO 8601 datestamps.
42+
//
43+
// Example: "2022-05-25"
44+
export function datestampToRFC822(datestamp) {
45+
let match = datestamp.match(/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/);
46+
if (!match) {
47+
throw new Error(`failed to parse timestamp: ${datestamp}`);
48+
}
49+
let { year, month, day } = match.groups;
50+
let monthNumber = parseInt(month, 10);
51+
// See: https://www.w3.org/Protocols/rfc822/#z28
52+
return `${day} ${rfc822Months[monthNumber - 1]} ${year} 00:00:00 Z`;
53+
}
54+
4155
// quick-lint-js finds bugs in JavaScript programs.
4256
// Copyright (C) 2020 Matthew "strager" Glazar
4357
//

website/test/test-timestamp.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (C) 2020 Matthew "strager" Glazar
22
// See end of file for extended copyright information.
33

4-
import { parseTimestamp } from "../src/timestamp.mjs";
4+
import { datestampToRFC822, parseTimestamp } from "../src/timestamp.mjs";
55

66
describe("parse timestamp", () => {
77
it("gives date in local timezone", () => {
@@ -19,6 +19,12 @@ describe("parse timestamp", () => {
1919
});
2020
});
2121

22+
describe("datestampToRFC822", () => {
23+
it("converts date to RFC 822 format in UTC timezone without day of week", () => {
24+
expect(datestampToRFC822("2022-05-25")).toEqual("25 May 2022 00:00:00 Z");
25+
});
26+
});
27+
2228
// quick-lint-js finds bugs in JavaScript programs.
2329
// Copyright (C) 2020 Matthew "strager" Glazar
2430
//

0 commit comments

Comments
 (0)