OPML
OPML outlines read and written: the subscription lists that feed readers and podcast apps import and export.
Version 1.0.0 · 3 functions · 1 crate
Functions
What use opml.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use opml_read, opml_feeds from opml (Quillo 0.6).
opml_read(text: str) → anyor an errorAn OPML text as { title, outlines }: the head's title and the outline tree, each { text, title, type, xml_url, html_url, children }.
opml_feeds(text: str) → listor an errorThe feeds of an OPML subscription list, flat: { title, xml_url, html_url, category } each, category being the folder they are in (null at the top). For importing a reader's export.
opml_write(title: str, feeds: list) → stror an errorAn OPML subscription list titled title from feeds ({ title, xml_url, html_url, category }; xml_url needed), feeds with a category put in a folder of that name, in the order first met. Readers and podcast apps import it.
Get it
$ quillo install opml # opml.qwer into this folder$ quillo install opml --example # and its example, opml.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/opml.qwer$ curl -O https://quillolang.org/plugins/files/opml.qwe$ curl -O https://quillolang.org/plugins/files/opml_sample.opmlThen, in your program, all of it or only what you call:
use opml.qweruse opml_read, opml_feeds from opml // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
opml = "1"Example
// A feed reader's import and export of subscriptions as OPML.//// quillo run opml.qwe then open http://localhost:8080/importuse "opml.qwer"route GET /import = opml_feeds(read_file("opml_sample.opml")?)?route GET /tree { let o = opml_read(read_file("opml_sample.opml")?)? { title: o.title, folders: o.outlines | map((x) => { name: x.text, feeds: len(x.children) }) }}route GET /export.opml = respond(200, opml_write("Quillo Reader export", [ { title: "Quillo blog", xml_url: "https://quillolang.org/blog/feed.xml", html_url: "https://quillolang.org/blog/", category: "Programming" }, { title: "Example News", xml_url: "https://news.example.com/feed" }, { title: "This Week in Rust", xml_url: "https://this-week-in-rust.org/rss.xml", category: "Programming" },])?, { "content-type": "text/x-opml; charset=utf-8", "content-disposition": "attachment; filename=subscriptions.opml" })$ quillo run opml.qwe$ curl 'localhost:8080/import'$ curl 'localhost:8080/export.opml'Source
//! OPML outlines read and written: the feed lists that RSS readers (Feedly, NetNewsWire, Inoreader) and podcast apps import and export, and other outlines.//! version: 1.0.0//!//! [dependencies]//! opml = "1"use opml::{Head, Outline, OPML};use serde_json::{json, Value};/// Files up to 10 MB are read.const MAX: usize = 10 * 1024 * 1024;fn doc(text: &str) -> Result<OPML, String> { if text.len() > MAX { return Err("OPML files up to 10 MB".into()); } OPML::from_str(text).map_err(|e| format!("not OPML: {}", e))}/// An OPML text as `{ title, outlines }`: the head's title and the outline tree, each `{ text, title, type, xml_url,/// html_url, children }`.pub fn opml_read(text: &str) -> Result<Value, String> { let d = doc(text)?; Ok(json!({ "title": d.head.as_ref().and_then(|h| h.title.clone()), "outlines": d.body.outlines.iter().map(|o| tree(o, 0)).collect::<Vec<_>>(), }))}/// The feeds of an OPML subscription list, flat: `{ title, xml_url, html_url, category }` each, `category` being the folder/// they are in (null at the top). For importing a reader's export.pub fn opml_feeds(text: &str) -> Result<Vec<Value>, String> { let d = doc(text)?; let mut out = vec![]; for o in &d.body.outlines { flat(o, None, &mut out, 0); } Ok(out)}/// An OPML subscription list titled `title` from `feeds` (`{ title, xml_url, html_url, category }`; `xml_url` needed), feeds/// with a `category` put in a folder of that name, in the order first met. Readers and podcast apps import it.pub fn opml_write(title: &str, feeds: Vec<serde_json::Value>) -> Result<String, String> { let mut d = OPML::default(); d.head = Some(Head { title: Some(title.to_string()), ..Head::default() }); let mut folders: Vec<Outline> = vec![]; for (i, f) in feeds.iter().enumerate().take(100_000) { let url = f["xml_url"].as_str().ok_or_else(|| format!("feed {} has no xml_url", i + 1))?; let name = f["title"].as_str().unwrap_or(url).to_string(); let o = Outline { text: name.clone(), title: Some(name), r#type: Some("rss".into()), xml_url: Some(url.to_string()), html_url: f["html_url"].as_str().map(str::to_string), ..Outline::default() }; match f["category"].as_str().filter(|c| !c.is_empty()) { Some(cat) => match folders.iter_mut().find(|x| x.text == cat) { Some(folder) => folder.outlines.push(o), None => folders.push(Outline { text: cat.to_string(), title: Some(cat.to_string()), outlines: vec![o], ..Outline::default() }), }, None => folders.push(o), } } d.body.outlines = folders; d.to_string().map_err(|e| e.to_string())}// The helpers that name the `type` field come last: Quillo's reader of .qwer files takes `r#` for the start of a raw// string, and would miss any `pub fn` written after it.fn tree(o: &Outline, depth: usize) -> Value { json!({ "text": o.text, "title": o.title, "type": o.r#type, "xml_url": o.xml_url, "html_url": o.html_url, "children": if depth < 50 { o.outlines.iter().map(|c| tree(c, depth + 1)).collect::<Vec<_>>() } else { vec![] }, })}fn flat(o: &Outline, category: Option<&str>, out: &mut Vec<Value>, depth: usize) { if let Some(url) = &o.xml_url { out.push(json!({ "title": o.title.clone().unwrap_or_else(|| o.text.clone()), "xml_url": url, "html_url": o.html_url, "category": category })); } if depth < 50 { for c in &o.outlines { // A folder's name is the category of the feeds inside it. let cat = if o.xml_url.is_none() { Some(o.text.as_str()) } else { category }; flat(c, cat, out, depth + 1); } }}Related
avro.qwer · 1.0.0Apache Avro data files: rows written with a schema, as Kafka, Hadoop and BigQuery use, and read back with the schema they carry.9 views · 6 downloadsBibTeX bibtex.qwer · 1.0.0BibTeX and BibLaTeX bibliographies read, with LaTeX accents and macros resolved; reference lists in APA or IEEE style, CSL-JSON for Zotero and pandoc, and entries written back.7 views · 6 downloadsCSV csv.qwer · 1.0.0CSV in and out: rows as maps keyed by the header, with quotes, commas and line breaks in cells handled.8 views · 8 downloadsFixed width fixed_width.qwer · 1.0.0Fixed-width records read and written from a column spec: bank payment files, payroll exports and mainframe formats.8 views · 8 downloadsINI ini.qwer · 1.0.0INI files read and written, sections and keys: php.ini, setup.cfg, .gitconfig and the settings of older software.8 views · 7 downloadsJSON Lines ndjson.qwer · 1.0.0JSON Lines (NDJSON) read a page at a time from files of any size, searched by field, appended to and written: logs, exports and imports.7 views · 6 downloadsThread 0 posts
On OPML: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.