From 3be44b5e0e224ad2d19f5af479de58680ee905fa Mon Sep 17 00:00:00 2001 From: tdro Date: Sat, 3 Dec 2022 20:09:08 -0500 Subject: assets: Add schemas House keeping guardrails --- Makefile | 22 +- assets/schemas/atom-v1.0.rng | 605 ++++++++++++++++++++++++++++++++ assets/schemas/jsonfeed-v1.1.json | 297 ++++++++++++++++ assets/schemas/rss-v2.0.rng | 86 +++++ assets/schemas/rss-v2.0.xsd | 383 ++++++++++++++++++++ shell.nix | 8 + themes/default/layouts/_default/rss.xml | 4 +- 7 files changed, 1399 insertions(+), 6 deletions(-) create mode 100644 assets/schemas/atom-v1.0.rng create mode 100644 assets/schemas/jsonfeed-v1.1.json create mode 100644 assets/schemas/rss-v2.0.rng create mode 100644 assets/schemas/rss-v2.0.xsd diff --git a/Makefile b/Makefile index f438eb9..b952bf2 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,9 @@ server: test: make test-html make test-xsl + make test-xml + make test-rss + make test-jsonfeed test-html: validatornu \ @@ -31,12 +34,23 @@ test-html: public/default/index.html test-css: - validatornu --css \ - assets/css/default.css + validatornu --css \ + assets/css/default.css test-xsl: - xsltproc \ - public/rss.xsl + xsltproc \ + public/rss.xsl + +test-xml: + xmllint --noout \ + public/rss.xml + +test-rss: + xmllint --noout --relaxng assets/schemas/rss-v2.0.rng public/rss.xml +# xmllint --noout --schema assets/schemas/rss-v2.0.xsd public/rss.xml + +test-jsonfeed: + check-jsonschema --schemafile assets/schemas/jsonfeed-v1.1.json public/index.json icons: rm -rf static/icons diff --git a/assets/schemas/atom-v1.0.rng b/assets/schemas/atom-v1.0.rng new file mode 100644 index 0000000..da47078 --- /dev/null +++ b/assets/schemas/atom-v1.0.rng @@ -0,0 +1,605 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + html + + + + + + + + + xhtml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An atom:feed must have an atom:author unless all of its atom:entry children have an atom:author. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An atom:entry must have at least one atom:link element with a rel attribute of 'alternate' or an atom:content. + + + An atom:entry must have an atom:author if its feed does not. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + html + + + + + + + + + + + + + xhtml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + [^:]* + + + + + + .+/.+ + + + + + + [A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})* + + + + + + + + + + .+@.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xml:base + xml:lang + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/schemas/jsonfeed-v1.1.json b/assets/schemas/jsonfeed-v1.1.json new file mode 100644 index 0000000..0d6653d --- /dev/null +++ b/assets/schemas/jsonfeed-v1.1.json @@ -0,0 +1,297 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "JSON Feed", + "description": "JSON Feed Version 1.1", + "type": "object", + "properties": { + "version": { + "description": "The URL of the version of the format the feed uses. This should appear at the very top.", + "type": "string", + "format": "uri", + "enum": [ + "https://jsonfeed.org/version/1", + "https://jsonfeed.org/version/1.1" + ] + }, + "title": { + "description": "The name of the feed, which will often correspond to the name of the website (blog, for instance), though not necessarily.", + "type": "string" + }, + "home_page_url": { + "description": "The URL of the resource that the feed describes. This resource may or may not actually be a “home” page, but it should be an HTML page. If a feed is published on the public web, this should be considered as required.", + "type": "string", + "format": "uri" + }, + "feed_url": { + "description": "The URL of the feed, serves as the unique identifier for the feed. This should be considered required for feeds on the public web.", + "type": "string", + "format": "uri" + }, + "description": { + "description": "Provides more detail, beyond the title, on what the feed is about. A feed reader may display this text.", + "type": "string" + }, + "user_comment": { + "description": "A description of the purpose of the feed. This is for the use of people looking at the raw JSON, and should be ignored by feed readers.", + "type": "string" + }, + "next_url": { + "description": "The URL of a feed that provides the next n items, where n is determined by the publisher. This allows for pagination, but with the expectation that reader software is not required to use it and probably won’t use it very often.", + "type": "string", + "format": "uri" + }, + "icon": { + "description": "The URL of an image for the feed suitable to be used in a timeline, much the way an avatar might be used. It should be square and relatively large — such as 512 x 512 pixels — so that it can be scaled-down and so that it can look good on retina displays. It should use transparency where appropriate, since it may be rendered on a non-white background.", + "type": "string", + "format": "uri" + }, + "favicon": { + "description": "The URL of an image for the feed suitable to be used in a source list. It should be square and relatively small, but not smaller than 64 x 64 pixels (so that it can look good on retina displays). This image should use transparency where appropriate, since it may be rendered on a non-white background.", + "type": "string", + "format": "uri" + }, + "author": { + "description": "The feed author.", + "allOf": [ + { + "$ref": "#/definitions/author" + } + ] + }, + "authors": { + "description": "One or more feed authors.", + "type": "array", + "items": { + "$ref": "#/definitions/author" + } + }, + "language": { + "description": "The primary language for the feed in the format specified in RFC 5646. The value is usually a 2-letter language tag from ISO 639-1, optionally followed by a region tag.", + "type": "string" + }, + "expired": { + "description": "Whether or not the feed is finished — that is, whether or not it will ever update again. A feed for a temporary event, such as an instance of the Olympics, could expire. If the value is true, then it’s expired. Any other value, or the absence of expired, means the feed may continue to update.", + "type": "boolean" + }, + "hubs": { + "description": "Describes endpoints that can be used to subscribe to real-time notifications from the publisher of this feed.", + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "description": "Describes the protocol used to talk with the hub, such as “rssCloud” or “WebSub.”", + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "patternProperties": { + "^_[a-zA-Z][^.]*$": { + "$ref": "#/definitions/extension" + } + }, + "additionalProperties": false, + "required": [ + "type", + "url" + ] + } + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "description": "Unique for that item for that feed over time. If an item is ever updated, the id should be unchanged. New items should never use a previously-used id. Ideally, the id is the full URL of the resource described by the item, since URLs make great unique identifiers.", + "type": "string" + }, + "url": { + "description": "The URL of the resource described by the item. It’s the permalink. This may be the same as the id.", + "type": "string", + "format": "uri" + }, + "external_url": { + "description": "The URL of a page elsewhere. This is especially useful for linkblogs. If url links to where you’re talking about a thing, then external_url links to the thing you’re talking about.", + "type": "string", + "format": "uri" + }, + "title": { + "description": "Plain text. Microblog items in particular may omit titles.", + "type": "string" + }, + "content_html": { + "description": "The HTML of the item.", + "type": "string" + }, + "content_text": { + "description": "The plain text of the item.", + "type": "string" + }, + "summary": { + "description": "A plain text sentence or two describing the item. This might be presented in a timeline, for instance, where a detail view would display all of content_html or content_text.", + "type": "string" + }, + "image": { + "description": "The URL of the main image for the item. This image may also appear in the content_html — if so, it’s a hint to the feed reader that this is the main, featured image. Feed readers may use the image as a preview (probably resized as a thumbnail and placed in a timeline).", + "type": "string", + "format": "uri" + }, + "banner_image": { + "description": "The URL of an image to use as a banner. Some blogging systems (such as Medium) display a different banner image chosen to go with each post, but that image wouldn’t otherwise appear in the content_html. A feed reader with a detail view may choose to show this banner image at the top of the detail view, possibly with the title overlaid.", + "type": "string", + "format": "uri" + }, + "date_published": { + "description": "The date in RFC 3339 format.", + "type": "string", + "format": "date-time" + }, + "date_modified": { + "description": "The modification date in RFC 3339 format.", + "type": "string", + "format": "date-time" + }, + "author": { + "description": "The author of the item.", + "allOf": [ + { + "$ref": "#/definitions/author" + } + ] + }, + "authors": { + "description": "The authors of the item.", + "type": "array", + "items": { + "$ref": "#/definitions/author" + } + }, + "tags": { + "description": "Can have any plain text values you want. Tags tend to be just one word, but they may be anything. Note: they are not the equivalent of Twitter hashtags. Some blogging systems and other feed formats call these categories.", + "type": "array", + "items": { + "type": "string" + } + }, + "language": { + "description": "The language for this item, using the format specified in RFC 5646. The value can be different than the primary language for the feed when a specific item is written in a different language than other items in the feed.", + "type": "string" + }, + "attachments": { + "description": "Lists related resources. Podcasts, for instance, would include an attachment that’s an audio or video file.", + "type": "array", + "items": { + "type": "object", + "properties": { + "url": { + "description": "The location of the attachment.", + "type": "string", + "format": "uri" + }, + "mime_type": { + "description": "The type of the attachment, such as “audio/mpeg.”", + "type": "string" + }, + "title": { + "description": "A name for the attachment. Important: if there are multiple attachments, and two or more have the exact same title (when title is present), then they are considered as alternate representations of the same thing. In this way a podcaster, for instance, might provide an audio recording in different formats.", + "type": "string" + }, + "size_in_bytes": { + "description": "How large the file is.", + "type": "number" + }, + "duration_in_seconds": { + "description": "How long it takes to listen to or watch, when played at normal speed.", + "type": "number" + } + }, + "patternProperties": { + "^_[a-zA-Z][^.]*$": { + "$ref": "#/definitions/extension" + } + }, + "additionalProperties": false, + "required": [ + "url", + "mime_type" + ] + } + } + }, + "patternProperties": { + "^_[a-zA-Z][^.]*$": { + "$ref": "#/definitions/extension" + } + }, + "additionalProperties": false, + "anyOf": [ + { + "required": [ + "content_html" + ] + }, + { + "required": [ + "content_text" + ] + } + ], + "required": [ + "id" + ] + } + } + }, + "patternProperties": { + "^_[a-zA-Z][^.]*$": { + "$ref": "#/definitions/extension" + } + }, + "additionalProperties": false, + "required": [ + "version", + "title", + "items" + ], + "definitions": { + "author": { + "title": "Author", + "type": "object", + "properties": { + "name": { + "description": "The author’s name.", + "type": "string" + }, + "url": { + "description": "The URL of a site owned by the author. It could be a blog, micro-blog, Twitter account, and so on. Ideally the linked-to page provides a way to contact the author, but that’s not required. The URL could be a mailto: link.", + "type": "string", + "format": "uri" + }, + "avatar": { + "description": "The URL for an image for the author. It should be square and relatively large — such as 512 x 512 pixels — and should use transparency where appropriate, since it may be rendered on a non-white background.", + "type": "string", + "format": "uri" + } + }, + "minProperties": 1, + "patternProperties": { + "^_[a-zA-Z][^.]*$": { + "$ref": "#/definitions/extension" + } + }, + "additionalProperties": false + }, + "extension": { + "title": "Extension", + "type": "object", + "patternProperties": { + "^[^.]*$": {} + }, + "additionalProperties": false + } + } +} diff --git a/assets/schemas/rss-v2.0.rng b/assets/schemas/rss-v2.0.rng new file mode 100644 index 0000000..31708ca --- /dev/null +++ b/assets/schemas/rss-v2.0.rng @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/schemas/rss-v2.0.xsd b/assets/schemas/rss-v2.0.xsd new file mode 100644 index 0000000..5b1ce1a --- /dev/null +++ b/assets/schemas/rss-v2.0.xsd @@ -0,0 +1,383 @@ + + + + + + + + + + + + + + + + + + + + The name of the channel. It's how people refer + to your service. If you have an HTML website + that contains the same information as your RSS + file, the title of your channel should be the + same as the title of your website. + + + + + + + The URL to the HTML website corresponding to the + channel. + + + + + + + Phrase or sentence describing the channel. + + + + + + + The language the channel is written in. This + allows aggregators to group all Italian language + sites, for example, on a single page. A list of + allowable values for this element, as provided + by Netscape, is here + [http://www.rssboard.org/rss-language-codes]. + You may also use values defined by the W3C + [http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes]. + + + + + + + Copyright notice for content in the channel. + + + + + + + Email address for person responsible for + editorial content. + + + + + + + Email address for person responsible for + technical issues relating to channel. + + + + + + + The publication date for the content in the + channel. For example, the New York Times + publishes on a daily basis, the publication date + flips once every 24 hours. That's when the + pubDate of the channel changes. All date-times + in RSS conform to the Date and Time + Specification of RFC 822, with the exception + that the year may be expressed with two + characters or four characters (four preferred). + + + + + + + Specify one or more categories that the channel + belongs to. Follows the same rules as the + <item>-level category element. + + + + + + + A string indicating the program used to generate + the channel. + + + + + + + The last time the content of the channel + changed. + + + + + + + A URL that points to the documentation for the + format used in the RSS file. It's probably a + pointer to this page. It's for people who might + stumble across an RSS file on a Web server 25 + years from now and wonder what it is. + + + + + + + Allows processes to register with a cloud to be + notified of updates to the channel, implementing + a lightweight publish-subscribe protocol for RSS + feeds. More info here. + + + + + + + ttl stands for time to live. It's a number of + minutes that indicates how long a channel can be + cached before refreshing from the source. + + + + + + + Specifies a GIF, JPEG or PNG image that can be + displayed with the channel. + + + + + + + The PICS [http://www.w3.org/PICS/] rating for + the channel. + + + + + + + Specifies a text input box that can be displayed + with the channel. + + + + + + + A hint for aggregators telling them which hours + they can skip. This element contains up to 24 + <hour> sub-elements whose value is a + number between 0 and 23, representing a time in + GMT, when aggregators, if they support the + feature, may not read the channel on hours + listed in the <skipHours> element. The + hour beginning at midnight is hour zero. + + + + + + + A hint for aggregators telling them which days + they can skip. This element contains up to seven + <day> + sub-elements whose value is Monday, Tuesday, + Wednesday, Thursday, Friday, Saturday or + Sunday. Aggregators may not read the channel + during days listed in the + <skipDays>element. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The title of the item. + + + + + + The URL of the item. + + + + + The item synopsis. + + + + + + Email address of the author of the item. + + + + + + + Includes the item in one or more categories. + + + + + + + URL of a page for comments relating to the item. + + + + + + + Describes a media object that is attached to the + item. + + + + + + + A string that uniquely identifies the item. + + + + + + + Indicates when the item was published. + + + + + + + The RSS channel that the item came from. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shell.nix b/shell.nix index 76d3906..151c411 100644 --- a/shell.nix +++ b/shell.nix @@ -17,6 +17,11 @@ let sha256 = "1ciwifsx2hrp0ymm077zfb5q8ravrk545bda1q249y2spw9np4ms"; }) { }; + check-jsonschema = pkgs.callPackage (pkgs.fetchurl { + url = "https://raw.githubusercontent.com/NixOS/nixpkgs/82e9e32436d5886102f1e5f7c17aed8475504991/pkgs/development/tools/check-jsonschema/default.nix"; + sha256 = "sha256-PImZIMGa6+DE2f4tXiPS8ck7enThJvi9uiyxHvXm9WQ"; + }) { }; + hugo = pkgs.callPackage ({ lib, buildGo118Module, fetchgit, installShellFiles }: buildGo118Module rec { @@ -55,6 +60,7 @@ in mkShellPure { inherit hugo; packages = [ + check-jsonschema hugo validatornu pkgs.busybox @@ -62,6 +68,8 @@ in mkShellPure { pkgs.entr pkgs.git pkgs.gnumake + pkgs.jing + pkgs.libxml2 pkgs.libxslt pkgs.php pkgs.subversion diff --git a/themes/default/layouts/_default/rss.xml b/themes/default/layouts/_default/rss.xml index 562da9e..8248cc1 100644 --- a/themes/default/layouts/_default/rss.xml +++ b/themes/default/layouts/_default/rss.xml @@ -65,13 +65,13 @@ {{ $description }} {{ .Site.LanguageCode }} {{ partial "author-user.html" . }} + Hugo {{ hugo.Version }} + {{ $lastBuildDate }} {{ $title }} {{ $image.Permalink }} {{ .Permalink }} - Hugo {{ hugo.Version }} - {{ $lastBuildDate }} {{ $atomSelf }} {{ $atomPrevious }} -- cgit v1.2.3