feed_gen

RSS 2.0 and Atom 1.0 feed generation library

rss
atom
xml
feed
syndication
moon add tkancf/feed_gen@0.3.1
Download zip
Author
Version
0.3.1
License
Apache-2.0
Last updated
16 days ago
Downloads
36
README

#tkancf/feed_gen

RSS 2.0 and Atom 1.0 feed generation library for MoonBit.

It supports channel and item metadata, authors, categories, dates, enclosures with MIME type detection, GeoRSS coordinates, PubSubHubbub links, feed images, custom XML namespaces, and custom XML elements.

#Installation

moon add tkancf/feed_gen

#Quick Start

///|
let item = @feed_gen.FeedItem::new(
"First Post",
description="This is the summary of my first post.",
)
.set_content(Some("<p>This is the full post.</p>"))
.set_url(Some("https://example.com/posts/first"))
.set_pub_date(Some(@feed_gen.FeedDate::utc(2026, 6, 27, hour=12)))

///|
let config = @feed_gen.FeedConfig::new(
"My Blog",
"A blog about technology and programming",
"https://example.com",
items=[item],
)
.set_feed_url(Some("https://example.com/rss.xml"))
.set_last_build_date(Some(@feed_gen.FeedDate::utc(2026, 6, 27, hour=12)))
.set_author(Some(@feed_gen.Author::new("Jane Doe", email="jane@example.com")))

///|
let rss = config.to_xml(indent=true).unwrap()

///|
let atom = config.to_atom(indent=true).unwrap()

#Sample Command

Run the wasm-compatible sample command:

moon run cmd/main

It prints both RSS 2.0 and Atom 1.0 XML for a small in-memory feed.

#Core Types

FeedConfig describes the feed/channel. Construct it with FeedConfig::new and chain the fluent setters for construction and updates. Record fields are read-only to external users, so field renames or removals are treated as breaking changes. The config covers the required title, description, and site URL, plus optional feed URL, authorship, dates, image metadata, categories, RSS metadata, custom namespaces, custom elements, and items.

Create one with:

///|
let config = @feed_gen.FeedConfig::new(
"My Feed", "Feed description", "https://example.com",
)

FeedItem describes each entry:

///|
let item = @feed_gen.FeedItem::new(
"Article",
description="Short summary",
)
.set_content(Some("<p>Full HTML content.</p>"))
.set_url(Some("https://example.com/article"))
.set_guid(Some("article-1"))
.set_pub_date(Some(@feed_gen.FeedDate::utc(2026, 6, 27)))

Item titles are required; rendering returns MissingRequiredField("item.title") when an item title is empty. RSS renders authors as dc:creator using the display name only. Author email addresses are emitted by Atom output, not RSS output.

#Rendering

Render RSS 2.0:

///|
let xml = config.to_xml(indent=true).unwrap()

Render Atom 1.0:

///|
let xml = config.to_atom(indent=true).unwrap()

Atom requires an updated value, supplied by last_build_date or pub_date. When the feed has entries, Atom also requires either a feed-level author or an author on every item. Each Atom item must have a guid or url; guid is used as the entry <id> when present, otherwise url is used.

Both renderers return Result[String, Array[FeedError]]. set_hub renders WebSub/PubSubHubbub hub links in both RSS and Atom output.

Sort items with FeedConfig::sort_by; use item getters inside the comparator instead of relying on record fields:

///|
let newest_first = config.sort_by(fn(a, b) {
match (a.pub_date(), b.pub_date()) {
(Some(x), Some(y)) => y.compare(x)
(Some(_), None) => -1
(None, Some(_)) => 1
(None, None) => a.title().compare(b.title())
}
})

#Dates

///|
let utc = @feed_gen.FeedDate::utc(2026, 6, 27, hour=12, minute=30)

///|
let jst = @feed_gen.FeedDate::new(
2026,
6,
27,
hour=21,
minute=30,
offset_minutes=540,
)

Dates are validated when rendering. Years must be in 1..9999; invalid date/time fields return InvalidDate instead of producing XML.

Geo coordinates are validated when rendering. Latitude and longitude must be provided together; either coordinate on its own returns InvalidGeo.

#Enclosures

///|
let episode = @feed_gen.FeedItem::new(
"Episode 1",
description="The first episode",
)
.set_enclosure(
Some(
@feed_gen.FeedEnclosure::new(
"https://example.com/media/episode-1.m4a", 12_345_678L,
),
),
)

The MIME type is detected from the URL extension. Use set_mime_type to override detection.

#Images

///|
let image = @feed_gen.FeedImage::new(
"https://example.com/logo.png",
"Example Feed",
"https://example.com",
)
.set_width(Some(144))
.set_height(Some(144))
.set_description(Some("Feed artwork"))

///|
let config = @feed_gen.FeedConfig::new(
"Example Feed",
"A feed with artwork",
"https://example.com",
)
.set_image(Some(image))

#Custom XML

///|
let config = @feed_gen.FeedConfig::new("Podcast", "A show", "https://example.com")
.add_custom_namespace("itunes", "http://www.itunes.com/dtds/podcast-1.0.dtd")
.add_custom_element(@feed_gen.Text("itunes:author", "Jane Doe"))
.add_custom_element(@feed_gen.Element("itunes:owner", [], [
@feed_gen.Text("itunes:name", "Jane Doe"),
@feed_gen.Text("itunes:email", "jane@example.com"),
]))

Custom tags and attribute names are validated before successful rendering. Custom element names, attribute names, and namespace prefixes intentionally support ASCII XML names only. CDATA content is sanitized and embedded ]]> markers are split so generated XML remains well-formed. When validation fails, renderers return all collected FeedError values. Custom namespace prefixes must be valid XML NCNames. RSS reserves the built-in prefixes dc, content, atom, and geo; Atom reserves atom and geo. Those prefixes cannot be redefined in custom_namespaces for the matching renderer. A custom namespace prefix can only be declared once.

#License

Apache-2.0

#
FeedError

pub suberror FeedError {
InvalidTag(String)
MissingRequiredField(String)
InvalidAttrName(String)
DuplicateNamespacePrefix(String)
ReservedNamespacePrefix(String)
DuplicateAttrName(String)
InvalidValue(String)
InvalidEnclosure(String)
InvalidGeo(String)
InvalidDate(String)
} derive(Compare, Eq,
Debug
)

Errors produced by feed rendering.

#
Author

pub struct Author {
name : String
email : String?
} derive(Compare, Eq,
Debug
)

An author with display name and optional email address.

#
Author::email

fn Author::email(self : Author) -> String?

Return the author email address.

#
Author::name

fn Author::name(self : Author) -> String

Return the author display name.

#
Author::new

fn Author::new(name : String, email? : String) -> Author

#
CustomElement

pub(all) enum CustomElement {
Text(String, String)
Cdata(String, String)
Element(String, Array[(String, String)], Array[CustomElement])
} derive(Compare, Eq,
Debug
)

An arbitrary XML element to embed in the channel or an item. Text is a leaf element with text content; Element is a container with attributes and child elements.

#
FeedCategory

pub struct FeedCategory {
value : String
domain : String?
} derive(Compare, Eq,
Debug
)

A category with an optional classification scheme URI (the domain attribute on the RSS <category> element).

#
FeedCategory::domain

fn FeedCategory::domain(self : FeedCategory) -> String?

Return the category domain.

#
FeedCategory::new

fn FeedCategory::new(value : String, domain? : String) -> FeedCategory

Create a category. domain is the optional classification scheme URI.

#
FeedCategory::value

fn FeedCategory::value(self : FeedCategory) -> String

Return the category value.

#
FeedConfig

pub struct FeedConfig {
title : String
description : String
feed_url : String?
site_url : String
author : Author?
categories : Array[FeedCategory]
pub_date : FeedDate?
last_build_date : FeedDate?
image : FeedImage?
hub : String?
docs : String?
copyright : String?
language : String?
managing_editor : String?
web_master : String?
ttl : Int?
generator : String
custom_namespaces : Array[(String, String)]
custom_elements : Array[CustomElement]
items : Array[FeedItem]
} derive(Compare, Eq,
Debug
)

#
FeedConfig::add_category

fn FeedConfig::add_category(self : FeedConfig, category : FeedCategory) -> FeedConfig

Append a category to the feed.

#
FeedConfig::add_custom_element

fn FeedConfig::add_custom_element(self : FeedConfig, element : CustomElement) -> FeedConfig

Append a custom XML element to the channel.

#
FeedConfig::add_custom_namespace

fn FeedConfig::add_custom_namespace(self : FeedConfig, prefix : String, uri : String) -> FeedConfig

Append a custom XML namespace declaration.

#
FeedConfig::add_item

fn FeedConfig::add_item(self : FeedConfig, item : FeedItem) -> FeedConfig

Append an item to this config's items list. Returns a new config with the item added, for chaining.

#
FeedConfig::new

fn FeedConfig::new(title : String, description : String, site_url : String, items? : Array[FeedItem]) -> FeedConfig

Create a feed config with the required title, description, and site_url. The default generator is "tkancf/feed_gen".

#
FeedConfig::set_author

fn FeedConfig::set_author(self : FeedConfig, author : Author?) -> FeedConfig

Set the feed author.
fn FeedConfig::set_copyright(self : FeedConfig, copyright : String?) -> FeedConfig

Set the feed copyright text.

#
FeedConfig::set_description

fn FeedConfig::set_description(self : FeedConfig, description : String) -> FeedConfig

Set the feed description.

#
FeedConfig::set_docs

fn FeedConfig::set_docs(self : FeedConfig, docs : String?) -> FeedConfig

Set the RSS documentation URL.

#
FeedConfig::set_feed_url

fn FeedConfig::set_feed_url(self : FeedConfig, feed_url : String?) -> FeedConfig

Set the feed URL used for RSS atom:link and Atom self link output.

#
FeedConfig::set_generator

fn FeedConfig::set_generator(self : FeedConfig, generator : String) -> FeedConfig

Set the generator string.

#
FeedConfig::set_hub

fn FeedConfig::set_hub(self : FeedConfig, hub : String?) -> FeedConfig

Set the PubSubHubbub hub URL.

#
FeedConfig::set_image

fn FeedConfig::set_image(self : FeedConfig, image : FeedImage?) -> FeedConfig

Set the feed image.

#
FeedConfig::set_language

fn FeedConfig::set_language(self : FeedConfig, language : String?) -> FeedConfig

Set the feed language.

#
FeedConfig::set_last_build_date

fn FeedConfig::set_last_build_date(self : FeedConfig, last_build_date : FeedDate?) -> FeedConfig

Set the last build date used for feed metadata and Atom updated.

#
FeedConfig::set_managing_editor

fn FeedConfig::set_managing_editor(self : FeedConfig, managing_editor : String?) -> FeedConfig

Set the RSS managing editor.

#
FeedConfig::set_pub_date

fn FeedConfig::set_pub_date(self : FeedConfig, pub_date : FeedDate?) -> FeedConfig

Set the feed publication date.

#
FeedConfig::set_site_url

fn FeedConfig::set_site_url(self : FeedConfig, site_url : String) -> FeedConfig

Set the site URL.

#
FeedConfig::set_title

fn FeedConfig::set_title(self : FeedConfig, title : String) -> FeedConfig

Set the feed title.

#
FeedConfig::set_ttl

fn FeedConfig::set_ttl(self : FeedConfig, ttl : Int?) -> FeedConfig

Set the RSS time-to-live value.

#
FeedConfig::set_web_master

fn FeedConfig::set_web_master(self : FeedConfig, web_master : String?) -> FeedConfig

Set the RSS webmaster.

#
FeedConfig::sort_by

fn FeedConfig::sort_by(self : FeedConfig, cmp : (FeedItem, FeedItem) -> Int) -> FeedConfig

Sort items using the given comparator function. Returns a new config with items sorted.

#
FeedConfig::to_atom

fn FeedConfig::to_atom(self : FeedConfig, indent? : Bool) -> Result[String, Array[FeedError]]

Render the feed as an Atom 1.0 XML string. Returns Err if title or description is empty, or if any custom element has an invalid tag name; all accumulated errors are reported without halting rendering.

When indent is true, produces indented output; otherwise minified.

#
FeedConfig::to_xml

fn FeedConfig::to_xml(self : FeedConfig, indent? : Bool) -> Result[String, Array[FeedError]]

Render the feed as an XML string. Returns Err if title or description is empty, or if any custom element has an invalid tag name; all accumulated errors are reported without halting rendering of the remaining elements.

When indent is true, produces indented output; otherwise minified.

#
FeedDate

pub struct FeedDate {
year : Int
month : Int
day : Int
hour : Int
minute : Int
second : Int
offset_minutes : Int
} derive(Compare, Eq,
Debug
)

A structured date/time value used for feed timestamps. Supports RFC 822 (for RSS <pubDate>) and RFC 3339 (for Atom <published>/<updated>).

#
FeedDate::new

fn FeedDate::new(year : Int, month : Int, day : Int, hour? : Int, minute? : Int, second? : Int, offset_minutes? : Int) -> FeedDate

Create a FeedDate with an explicit UTC offset in minutes.

#
FeedDate::utc

fn FeedDate::utc(year : Int, month : Int, day : Int, hour? : Int, minute? : Int, second? : Int) -> FeedDate

Create a FeedDate at UTC (offset = 0).

#
FeedEnclosure

pub struct FeedEnclosure {
url : String
size : Int64
mime_type : String?
} derive(Compare, Eq,
Debug
)

#
FeedEnclosure::effective_mime

fn FeedEnclosure::effective_mime(self : FeedEnclosure) -> String

Return the effective MIME type, resolved from explicit override or URL.

#
FeedEnclosure::mime_type

fn FeedEnclosure::mime_type(self : FeedEnclosure) -> String?

Return the explicit MIME type override.

#
FeedEnclosure::new

fn FeedEnclosure::new(url : String, size : Int64) -> FeedEnclosure

Create an enclosure. MIME type is detected lazily from the URL extension during rendering. Use set_mime_type to override detection.

#
FeedEnclosure::set_mime_type

fn FeedEnclosure::set_mime_type(self : FeedEnclosure, mime_type : String?) -> FeedEnclosure

Override the MIME type used during rendering.

#
FeedEnclosure::size

fn FeedEnclosure::size(self : FeedEnclosure) -> Int64

Return the enclosure size.

#
FeedEnclosure::url

fn FeedEnclosure::url(self : FeedEnclosure) -> String

Return the enclosure URL.

#
FeedImage

pub struct FeedImage {
url : String
title : String
link : String
width : Int?
height : Int?
description : String?
} derive(Compare, Eq,
Debug
)

Channel image. Optional width / height / description are omitted from the output when None.

#
FeedImage::description

fn FeedImage::description(self : FeedImage) -> String?

Return the image description.

#
FeedImage::height

fn FeedImage::height(self : FeedImage) -> Int?

Return the image height.
fn FeedImage::link(self : FeedImage) -> String

Return the image link.

#
FeedImage::new

fn FeedImage::new(url : String, title : String, link : String) -> FeedImage

Create a channel image. Optional dimensions default to None.

#
FeedImage::set_description

fn FeedImage::set_description(self : FeedImage, description : String?) -> FeedImage

Set the image description.

#
FeedImage::set_height

fn FeedImage::set_height(self : FeedImage, height : Int?) -> FeedImage

Set the image height.

#
FeedImage::set_width

fn FeedImage::set_width(self : FeedImage, width : Int?) -> FeedImage

Set the image width.

#
FeedImage::title

fn FeedImage::title(self : FeedImage) -> String

Return the image title.

#
FeedImage::url

fn FeedImage::url(self : FeedImage) -> String

Return the image URL.

#
FeedImage::width

fn FeedImage::width(self : FeedImage) -> Int?

Return the image width.

#
FeedItem

pub struct FeedItem {
title : String
description : String?
content : String?
url : String?
guid : String?
guid_is_perma_link : Bool?
categories : Array[FeedCategory]
author : Author?
pub_date : FeedDate?
lat : Double?
long : Double?
enclosure : FeedEnclosure?
custom_elements : Array[CustomElement]
} derive(Compare, Eq,
Debug
)

#
FeedItem::add_category

fn FeedItem::add_category(self : FeedItem, category : FeedCategory) -> FeedItem

Append a category to the item.

#
FeedItem::add_custom_element

fn FeedItem::add_custom_element(self : FeedItem, element : CustomElement) -> FeedItem

Append a custom XML element to the item.

#
FeedItem::author

fn FeedItem::author(self : FeedItem) -> Author?

Return the item author.

#
FeedItem::categories

fn FeedItem::categories(self : FeedItem) -> Array[FeedCategory]

Return the item categories.

#
FeedItem::content

fn FeedItem::content(self : FeedItem) -> String?

Return the item content.

#
FeedItem::custom_elements

fn FeedItem::custom_elements(self : FeedItem) -> Array[CustomElement]

Return the item custom XML elements.

#
FeedItem::description

fn FeedItem::description(self : FeedItem) -> String?

Return the item description.

#
FeedItem::enclosure

fn FeedItem::enclosure(self : FeedItem) -> FeedEnclosure?

Return the item enclosure.

#
FeedItem::guid

fn FeedItem::guid(self : FeedItem) -> String?

Return the item GUID.
fn FeedItem::guid_is_perma_link(self : FeedItem) -> Bool?

Return whether the item GUID renders as a permalink.

#
FeedItem::lat

fn FeedItem::lat(self : FeedItem) -> Double?

Return the item latitude.

#
FeedItem::long

fn FeedItem::long(self : FeedItem) -> Double?

Return the item longitude.

#
FeedItem::new

fn FeedItem::new(title : String, description? : String) -> FeedItem

Create a feed item with the required title. description is optional and defaults to None.

#
FeedItem::pub_date

fn FeedItem::pub_date(self : FeedItem) -> FeedDate?

Return the item publication date.

#
FeedItem::set_author

fn FeedItem::set_author(self : FeedItem, author : Author?) -> FeedItem

Set the item author.

#
FeedItem::set_content

fn FeedItem::set_content(self : FeedItem, content : String?) -> FeedItem

Set the item content.

#
FeedItem::set_description

fn FeedItem::set_description(self : FeedItem, description : String?) -> FeedItem

Set the item description.

#
FeedItem::set_enclosure

fn FeedItem::set_enclosure(self : FeedItem, enclosure : FeedEnclosure?) -> FeedItem

Set the item enclosure.

#
FeedItem::set_guid

fn FeedItem::set_guid(self : FeedItem, guid : String?) -> FeedItem

Set the item GUID.
fn FeedItem::set_guid_is_perma_link(self : FeedItem, guid_is_perma_link : Bool?) -> FeedItem

Set whether the GUID should render as a permalink.

#
FeedItem::set_lat

fn FeedItem::set_lat(self : FeedItem, lat : Double?) -> FeedItem

Set the item latitude.

#
FeedItem::set_long

fn FeedItem::set_long(self : FeedItem, long : Double?) -> FeedItem

Set the item longitude.

#
FeedItem::set_pub_date

fn FeedItem::set_pub_date(self : FeedItem, pub_date : FeedDate?) -> FeedItem

Set the item publication date.

#
FeedItem::set_title

fn FeedItem::set_title(self : FeedItem, title : String) -> FeedItem

Set the item title.

#
FeedItem::set_url

fn FeedItem::set_url(self : FeedItem, url : String?) -> FeedItem

Set the item URL.

#
FeedItem::title

fn FeedItem::title(self : FeedItem) -> String

Return the item title.

#
FeedItem::url

fn FeedItem::url(self : FeedItem) -> String?

Return the item URL.

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io