feat: add xsl stylesheet to changelogs (#21930)
This commit is contained in:
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: sudo apt-get install -y python3-paramiko
|
run: sudo apt-get install -y python3-paramiko python3-lxml
|
||||||
|
|
||||||
- uses: actions/checkout@v3.6.0
|
- uses: actions/checkout@v3.6.0
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -10,16 +10,15 @@
|
|||||||
# $env:CHANGELOG_RSS_KEY=[System.IO.File]::ReadAllText($(gci "key"))
|
# $env:CHANGELOG_RSS_KEY=[System.IO.File]::ReadAllText($(gci "key"))
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import io
|
import io
|
||||||
import paramiko
|
|
||||||
import base64
|
import base64
|
||||||
import yaml
|
import yaml
|
||||||
import sys
|
|
||||||
import itertools
|
import itertools
|
||||||
import html
|
import html
|
||||||
import email.utils
|
import email.utils
|
||||||
from typing import Optional, List, Any, Tuple
|
from typing import List, Any, Tuple
|
||||||
import xml.etree.ElementTree as ET
|
from lxml import etree as ET
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
MAX_ITEM_AGE = timedelta(days=30)
|
MAX_ITEM_AGE = timedelta(days=30)
|
||||||
@@ -33,6 +32,7 @@ SSH_HOST = "centcomm.spacestation14.io"
|
|||||||
SSH_USER = "changelog-rss"
|
SSH_USER = "changelog-rss"
|
||||||
SSH_PORT = 22
|
SSH_PORT = 22
|
||||||
RSS_FILE = "changelog.xml"
|
RSS_FILE = "changelog.xml"
|
||||||
|
XSL_FILE = "stylesheet.xsl"
|
||||||
HOST_KEYS = [
|
HOST_KEYS = [
|
||||||
"AAAAC3NzaC1lZDI1NTE5AAAAIEE8EhnPjb3nIaAPTXAJHbjrwdGGxHoM0f1imCK0SygD"
|
"AAAAC3NzaC1lZDI1NTE5AAAAIEE8EhnPjb3nIaAPTXAJHbjrwdGGxHoM0f1imCK0SygD"
|
||||||
]
|
]
|
||||||
@@ -102,10 +102,21 @@ def main():
|
|||||||
|
|
||||||
et = ET.ElementTree(feed)
|
et = ET.ElementTree(feed)
|
||||||
with sftp.open(RSS_FILE, "wb") as f:
|
with sftp.open(RSS_FILE, "wb") as f:
|
||||||
et.write(f, encoding="utf-8", xml_declaration=True)
|
et.write(
|
||||||
|
f,
|
||||||
|
encoding="utf-8",
|
||||||
|
xml_declaration=True,
|
||||||
|
# This ensures our stylesheet is loaded
|
||||||
|
doctype="<?xml-stylesheet type='text/xsl' href='./stylesheet.xsl'?>",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy in the stylesheet
|
||||||
|
template_path = pathlib.Path(__file__, 'changelogs', XSL_FILE)
|
||||||
|
with sftp.open(XSL_FILE, "wb") as f, open(template_path) as fh:
|
||||||
|
f.write(fh)
|
||||||
|
|
||||||
|
|
||||||
def create_feed(changelog: Any, previous_items: List[ET.Element]) -> Tuple[ET.Element, bool]:
|
def create_feed(changelog: Any, previous_items: List[Any]) -> Tuple[Any, bool]:
|
||||||
rss = ET.Element("rss", attrib={"version": "2.0"})
|
rss = ET.Element("rss", attrib={"version": "2.0"})
|
||||||
channel = ET.SubElement(rss, "channel")
|
channel = ET.SubElement(rss, "channel")
|
||||||
|
|
||||||
@@ -128,7 +139,7 @@ def create_feed(changelog: Any, previous_items: List[ET.Element]) -> Tuple[ET.El
|
|||||||
|
|
||||||
return rss, any
|
return rss, any
|
||||||
|
|
||||||
def create_new_item_since(changelog: Any, channel: ET.Element, since: int, now: datetime) -> bool:
|
def create_new_item_since(changelog: Any, channel: Any, since: int, now: datetime) -> bool:
|
||||||
entries_for_item = [entry for entry in changelog["Entries"] if entry["id"] > since]
|
entries_for_item = [entry for entry in changelog["Entries"] if entry["id"] > since]
|
||||||
top_entry_id = max(map(lambda e: e["id"], entries_for_item), default=0)
|
top_entry_id = max(map(lambda e: e["id"], entries_for_item), default=0)
|
||||||
|
|
||||||
@@ -174,7 +185,7 @@ def generate_description_for_entries(entries: List[Any]) -> str:
|
|||||||
|
|
||||||
return desc.getvalue()
|
return desc.getvalue()
|
||||||
|
|
||||||
def copy_previous_items(channel: ET.Element, previous: List[ET.Element], now: datetime):
|
def copy_previous_items(channel: Any, previous: List[Any], now: datetime):
|
||||||
# Copy in previous items, if we have them.
|
# Copy in previous items, if we have them.
|
||||||
for item in previous:
|
for item in previous:
|
||||||
date_elem = item.find("./pubDate")
|
date_elem = item.find("./pubDate")
|
||||||
@@ -189,7 +200,7 @@ def copy_previous_items(channel: ET.Element, previous: List[ET.Element], now: da
|
|||||||
|
|
||||||
channel.append(item)
|
channel.append(item)
|
||||||
|
|
||||||
def find_last_changelog_id(items: List[ET.Element]) -> int:
|
def find_last_changelog_id(items: List[Any]) -> int:
|
||||||
return max(map(lambda i: int(i.get(XML_NS_B + "to-id", "0")), items), default=0)
|
return max(map(lambda i: int(i.get(XML_NS_B + "to-id", "0")), items), default=0)
|
||||||
|
|
||||||
def load_key(key_contents: str) -> paramiko.PKey:
|
def load_key(key_contents: str) -> paramiko.PKey:
|
||||||
@@ -204,7 +215,7 @@ def load_host_keys(host_keys: paramiko.HostKeys):
|
|||||||
host_keys.add(SSH_HOST, "ssh-ed25519", paramiko.Ed25519Key(data=base64.b64decode(key)))
|
host_keys.add(SSH_HOST, "ssh-ed25519", paramiko.Ed25519Key(data=base64.b64decode(key)))
|
||||||
|
|
||||||
|
|
||||||
def load_last_feed_items(client: paramiko.SFTPClient) -> List[ET.Element]:
|
def load_last_feed_items(client: paramiko.SFTPClient) -> List[Any]:
|
||||||
try:
|
try:
|
||||||
with client.open(RSS_FILE, "rb") as f:
|
with client.open(RSS_FILE, "rb") as f:
|
||||||
feed = ET.parse(f)
|
feed = ET.parse(f)
|
||||||
|
|||||||
82
Tools/changelogs/stylesheet.xsl
Normal file
82
Tools/changelogs/stylesheet.xsl
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ss14="https://spacestation14.com/changelog_rss">
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
<![CDATA[
|
||||||
|
body {
|
||||||
|
font-family: Arial;
|
||||||
|
font-size: 1.6em;
|
||||||
|
background-color: rgb(32, 32, 48);
|
||||||
|
max-width: 1024px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: rgb(155, 34, 54);
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
font-size: 10pt;
|
||||||
|
color: rgb(199, 199, 199);
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.changes li {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
li::before {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
li.Tweak::before {
|
||||||
|
content: '🔧';
|
||||||
|
}
|
||||||
|
li.Fix::before {
|
||||||
|
content: '🐛';
|
||||||
|
}
|
||||||
|
li.Add::before {
|
||||||
|
content: '➕';
|
||||||
|
}
|
||||||
|
li.Remove::before {
|
||||||
|
content: '➖';
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<xsl:for-each select="rss/channel/item">
|
||||||
|
<div class='title'>
|
||||||
|
<xsl:copy-of select="pubDate"/>
|
||||||
|
</div>
|
||||||
|
<div class='description'>
|
||||||
|
<xsl:for-each select="*[local-name()='entry']">
|
||||||
|
<div class='author'>
|
||||||
|
<span>
|
||||||
|
<xsl:value-of select="*[local-name()='author']"/>
|
||||||
|
</span> updated
|
||||||
|
</div>
|
||||||
|
<div class='changes'>
|
||||||
|
<ul>
|
||||||
|
<xsl:for-each select="*[local-name()='change']">
|
||||||
|
<li>
|
||||||
|
<xsl:attribute name="class">
|
||||||
|
<xsl:value-of select="@*" />
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:copy-of select="node()" />
|
||||||
|
</li>
|
||||||
|
</xsl:for-each>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</xsl:for-each>
|
||||||
|
</div>
|
||||||
|
</xsl:for-each>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user