Add a bunch of things:
* A way of easily constructing OPMLs * An example of how to do construct in the getting started documentation * Make outline booleans optional so there's no unnecessary XML output * Defaults for all structs * Various helper methods for adding feeds and getting the XML output * Tests for construction
This commit is contained in:
parent
fa480a82a7
commit
53e76e8aa6
|
@ -2,6 +2,8 @@
|
||||||
//!
|
//!
|
||||||
//! ## Getting Started
|
//! ## Getting Started
|
||||||
//!
|
//!
|
||||||
|
//! ### Parsing
|
||||||
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use opml::OPML;
|
//! use opml::OPML;
|
||||||
//!
|
//!
|
||||||
|
@ -11,6 +13,36 @@
|
||||||
//! println!("{:#?}", parsed);
|
//! println!("{:#?}", parsed);
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! ### Constructing
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use opml::OPML;
|
||||||
|
//!
|
||||||
|
//! let mut opml = OPML::default();
|
||||||
|
//! opml
|
||||||
|
//! .add_feed("Rust Blog", "https://blog.rust-lang.org/feed.xml")
|
||||||
|
//! .add_feed(
|
||||||
|
//! "Inside Rust",
|
||||||
|
//! "https://blog.rust-lang.org/inside-rust/feed.xml",
|
||||||
|
//! );
|
||||||
|
//!
|
||||||
|
//! opml.head.title = Some("Rust Feeds".to_string());
|
||||||
|
//!
|
||||||
|
//! let xml = opml.to_xml().unwrap();
|
||||||
|
//! println!("{}", xml);
|
||||||
|
//!
|
||||||
|
//! // Outputs (without whitespace):
|
||||||
|
//! // <opml version="2.0">
|
||||||
|
//! // <head>
|
||||||
|
//! // <title>Rust Feeds</title>
|
||||||
|
//! // </head>
|
||||||
|
//! // <body>
|
||||||
|
//! // <outline text="Rust Blog" xmlUrl="https://blog.rust-lang.org/feed.xml"/>
|
||||||
|
//! // <outline text="Inside Rust" xmlUrl="https://blog.rust-lang.org/inside-rust/feed.xml"/>
|
||||||
|
//! // </body>
|
||||||
|
//! // </opml>
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
//! ## License
|
//! ## License
|
||||||
//!
|
//!
|
||||||
//! Open-sourced with either the
|
//! Open-sourced with either the
|
||||||
|
@ -28,7 +60,7 @@ use regex::Regex;
|
||||||
use strong_xml::{XmlError, XmlRead, XmlWrite};
|
use strong_xml::{XmlError, XmlRead, XmlWrite};
|
||||||
|
|
||||||
/// The top-level `<opml>` element.
|
/// The top-level `<opml>` element.
|
||||||
#[derive(XmlWrite, XmlRead, PartialEq, Debug)]
|
#[derive(XmlWrite, XmlRead, PartialEq, Debug, Clone)]
|
||||||
#[xml(tag = "opml")]
|
#[xml(tag = "opml")]
|
||||||
pub struct OPML {
|
pub struct OPML {
|
||||||
/// The version attribute from the element.
|
/// The version attribute from the element.
|
||||||
|
@ -74,11 +106,40 @@ impl OPML {
|
||||||
|
|
||||||
Ok(opml)
|
Ok(opml)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_feed(&mut self, name: &str, url: &str) -> &mut Self {
|
||||||
|
self.body.outlines.push(Outline {
|
||||||
|
text: name.to_string(),
|
||||||
|
xml_url: Some(url.to_string()),
|
||||||
|
..Outline::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_xml(&self) -> Result<String, String> {
|
||||||
|
let result: Result<String, XmlError> = self.to_string();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(value) => Ok(value),
|
||||||
|
Err(err) => Err(format!("XML writing error: {:#?}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OPML {
|
||||||
|
fn default() -> Self {
|
||||||
|
OPML {
|
||||||
|
version: "2.0".to_string(),
|
||||||
|
head: Head::default(),
|
||||||
|
body: Body::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `<head>` child element of `<opml>`.
|
/// The `<head>` child element of `<opml>`.
|
||||||
/// Contains the metadata of the OPML document.
|
/// Contains the metadata of the OPML document.
|
||||||
#[derive(XmlWrite, XmlRead, PartialEq, Debug)]
|
#[derive(XmlWrite, XmlRead, PartialEq, Debug, Clone, Default)]
|
||||||
#[xml(tag = "head")]
|
#[xml(tag = "head")]
|
||||||
pub struct Head {
|
pub struct Head {
|
||||||
/// The title of the document.
|
/// The title of the document.
|
||||||
|
@ -135,7 +196,7 @@ pub struct Head {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `<body>` child element of `<opml>`. Contains all the `<outlines>`.
|
/// The `<body>` child element of `<opml>`. Contains all the `<outlines>`.
|
||||||
#[derive(XmlWrite, XmlRead, PartialEq, Debug)]
|
#[derive(XmlWrite, XmlRead, PartialEq, Debug, Clone, Default)]
|
||||||
#[xml(tag = "body")]
|
#[xml(tag = "body")]
|
||||||
pub struct Body {
|
pub struct Body {
|
||||||
/// `<outline>` elements.
|
/// `<outline>` elements.
|
||||||
|
@ -144,7 +205,7 @@ pub struct Body {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `<outline>` element.
|
/// The `<outline>` element.
|
||||||
#[derive(XmlWrite, XmlRead, PartialEq, Debug)]
|
#[derive(XmlWrite, XmlRead, PartialEq, Debug, Clone, Default)]
|
||||||
#[xml(tag = "outline")]
|
#[xml(tag = "outline")]
|
||||||
pub struct Outline {
|
pub struct Outline {
|
||||||
/// Every outline element must have at least a text attribute, which is what is displayed when an outliner opens the OPML file.
|
/// Every outline element must have at least a text attribute, which is what is displayed when an outliner opens the OPML file.
|
||||||
|
@ -156,13 +217,13 @@ pub struct Outline {
|
||||||
#[xml(attr = "type")]
|
#[xml(attr = "type")]
|
||||||
pub r#type: Option<String>,
|
pub r#type: Option<String>,
|
||||||
|
|
||||||
/// Indicating whether the outline is commented or not. By convention if an outline is commented, all subordinate outlines are considered to also be commented. Defaults to `false`.
|
/// Indicating whether the outline is commented or not. By convention if an outline is commented, all subordinate outlines are considered to also be commented.
|
||||||
#[xml(default, attr = "isComment")]
|
#[xml(attr = "isComment")]
|
||||||
pub is_comment: bool,
|
pub is_comment: Option<bool>,
|
||||||
|
|
||||||
/// Indicating whether a breakpoint is set on this outline. This attribute is mainly necessary for outlines used to edit scripts. Defaults to `false`.
|
/// Indicating whether a breakpoint is set on this outline. This attribute is mainly necessary for outlines used to edit scripts.
|
||||||
#[xml(default, attr = "isBreakpoint")]
|
#[xml(attr = "isBreakpoint")]
|
||||||
pub is_breakpoint: bool,
|
pub is_breakpoint: Option<bool>,
|
||||||
|
|
||||||
/// The date-time (RFC822) that this `<outline>` element was created.
|
/// The date-time (RFC822) that this `<outline>` element was created.
|
||||||
#[xml(attr = "created")]
|
#[xml(attr = "created")]
|
||||||
|
@ -204,3 +265,15 @@ pub struct Outline {
|
||||||
#[xml(attr = "url")]
|
#[xml(attr = "url")]
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Outline {
|
||||||
|
pub fn add_feed(&mut self, name: &str, url: &str) -> &mut Self {
|
||||||
|
self.outlines.push(Outline {
|
||||||
|
text: name.to_string(),
|
||||||
|
xml_url: Some(url.to_string()),
|
||||||
|
..Outline::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fs::read_to_string as read;
|
||||||
|
|
||||||
|
use opml::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_opml_construction_1() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut opml = OPML::default();
|
||||||
|
opml
|
||||||
|
.add_feed("Rust Blog", "https://blog.rust-lang.org/feed.xml")
|
||||||
|
.add_feed(
|
||||||
|
"Inside Rust",
|
||||||
|
"https://blog.rust-lang.org/inside-rust/feed.xml",
|
||||||
|
);
|
||||||
|
|
||||||
|
opml.head.title = Some("Rust Feeds".to_string());
|
||||||
|
|
||||||
|
let actual = opml.to_xml().unwrap();
|
||||||
|
let expected = read("tests/samples/construction_1.opml")?;
|
||||||
|
|
||||||
|
assert_eq!(actual.trim(), expected.trim());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_opml_construction_2() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut opml = OPML::default();
|
||||||
|
|
||||||
|
let mut rust_group = Outline::default();
|
||||||
|
rust_group.text = "Rust Feeds".to_string();
|
||||||
|
rust_group
|
||||||
|
.add_feed("Rust Blog", "https://blog.rust-lang.org/feed.xml")
|
||||||
|
.add_feed(
|
||||||
|
"Inside Rust",
|
||||||
|
"https://blog.rust-lang.org/inside-rust/feed.xml",
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut mozilla_group = Outline::default();
|
||||||
|
mozilla_group.text = "Mozilla Feeds".to_string();
|
||||||
|
mozilla_group
|
||||||
|
.add_feed("Mozilla Blog", "https://blog.mozilla.org/feed")
|
||||||
|
.add_feed("Mozilla Hacks", "https://hacks.mozilla.org/feed");
|
||||||
|
|
||||||
|
opml.body.outlines.push(rust_group);
|
||||||
|
opml.body.outlines.push(mozilla_group);
|
||||||
|
opml.head.title = Some("Rust Feeds".to_string());
|
||||||
|
|
||||||
|
let actual = opml.to_xml().unwrap();
|
||||||
|
let expected = read("tests/samples/construction_2.opml")?;
|
||||||
|
|
||||||
|
assert_eq!(actual.trim(), expected.trim());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<opml version="2.0"><head><title>Rust Feeds</title></head><body><outline text="Rust Blog" xmlUrl="https://blog.rust-lang.org/feed.xml"/><outline text="Inside Rust" xmlUrl="https://blog.rust-lang.org/inside-rust/feed.xml"/></body></opml>
|
|
@ -0,0 +1 @@
|
||||||
|
<opml version="2.0"><head><title>Rust Feeds</title></head><body><outline text="Rust Feeds"><outline text="Rust Blog" xmlUrl="https://blog.rust-lang.org/feed.xml"/><outline text="Inside Rust" xmlUrl="https://blog.rust-lang.org/inside-rust/feed.xml"/></outline><outline text="Mozilla Feeds"><outline text="Mozilla Blog" xmlUrl="https://blog.mozilla.org/feed"/><outline text="Mozilla Hacks" xmlUrl="https://hacks.mozilla.org/feed"/></outline></body></opml>
|
|
@ -8,37 +8,11 @@ fn test_minimum_valid_opml() {
|
||||||
OPML::new(&read("tests/samples/minimum_valid_opml.opml").unwrap()).unwrap(),
|
OPML::new(&read("tests/samples/minimum_valid_opml.opml").unwrap()).unwrap(),
|
||||||
OPML {
|
OPML {
|
||||||
version: "2.0".to_string(),
|
version: "2.0".to_string(),
|
||||||
head: Head {
|
head: Head::default(),
|
||||||
title: None,
|
|
||||||
date_created: None,
|
|
||||||
date_modified: None,
|
|
||||||
owner_name: None,
|
|
||||||
owner_email: None,
|
|
||||||
owner_id: None,
|
|
||||||
docs: None,
|
|
||||||
expansion_state: None,
|
|
||||||
vert_scroll_state: None,
|
|
||||||
window_top: None,
|
|
||||||
window_left: None,
|
|
||||||
window_bottom: None,
|
|
||||||
window_right: None,
|
|
||||||
},
|
|
||||||
body: Body {
|
body: Body {
|
||||||
outlines: vec![Outline {
|
outlines: vec![Outline {
|
||||||
text: "Outline Text".to_string(),
|
text: "Outline Text".to_string(),
|
||||||
r#type: None,
|
..Outline::default()
|
||||||
is_breakpoint: false,
|
|
||||||
is_comment: false,
|
|
||||||
created: None,
|
|
||||||
category: None,
|
|
||||||
xml_url: None,
|
|
||||||
description: None,
|
|
||||||
html_url: None,
|
|
||||||
language: None,
|
|
||||||
title: None,
|
|
||||||
version: None,
|
|
||||||
url: None,
|
|
||||||
outlines: vec![]
|
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -71,8 +45,8 @@ fn test_valid_opml_with_everything() {
|
||||||
outlines: vec![Outline {
|
outlines: vec![Outline {
|
||||||
text: "Outline Text".to_string(),
|
text: "Outline Text".to_string(),
|
||||||
r#type: Some("Outline Type".to_string()),
|
r#type: Some("Outline Type".to_string()),
|
||||||
is_breakpoint: true,
|
is_breakpoint: Some(true),
|
||||||
is_comment: true,
|
is_comment: Some(true),
|
||||||
created: Some("Outline Date".to_string()),
|
created: Some("Outline Date".to_string()),
|
||||||
category: Some("Outline Category".to_string()),
|
category: Some("Outline Category".to_string()),
|
||||||
xml_url: Some("Outline XML URL".to_string()),
|
xml_url: Some("Outline XML URL".to_string()),
|
||||||
|
@ -85,8 +59,8 @@ fn test_valid_opml_with_everything() {
|
||||||
outlines: vec![Outline {
|
outlines: vec![Outline {
|
||||||
text: "Nested Outline Text".to_string(),
|
text: "Nested Outline Text".to_string(),
|
||||||
r#type: Some("Nested Outline Type".to_string()),
|
r#type: Some("Nested Outline Type".to_string()),
|
||||||
is_breakpoint: true,
|
is_breakpoint: Some(true),
|
||||||
is_comment: false,
|
is_comment: Some(false),
|
||||||
created: Some("Nested Outline Date".to_string()),
|
created: Some("Nested Outline Date".to_string()),
|
||||||
category: Some("Nested Outline Category".to_string()),
|
category: Some("Nested Outline Category".to_string()),
|
||||||
xml_url: Some("Nested Outline XML URL".to_string()),
|
xml_url: Some("Nested Outline XML URL".to_string()),
|
||||||
|
|
Loading…
Reference in New Issue