feed_gen

    RSS 2.0 and Atom 1.0 feed generation library

    rss
    atom
    xml
    feed
    syndication
    Download zip
    Author
    Version
    0.3.1
    License
    Apache-2.0
    Last updated
    3 months ago
    Downloads
    71

    No dependencies

    #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.