diff --git a/source/lib.rs b/source/lib.rs index 436f181..b69de0e 100644 --- a/source/lib.rs +++ b/source/lib.rs @@ -2,6 +2,8 @@ //! //! ## Getting Started //! +//! ### Parsing +//! //! ```rust //! use opml::OPML; //! @@ -11,6 +13,36 @@ //! 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): +//! // +//! // +//! // Rust Feeds +//! // +//! // +//! // +//! // +//! // +//! // +//! ``` +//! //! ## License //! //! Open-sourced with either the @@ -28,7 +60,7 @@ use regex::Regex; use strong_xml::{XmlError, XmlRead, XmlWrite}; /// The top-level `` element. -#[derive(XmlWrite, XmlRead, PartialEq, Debug)] +#[derive(XmlWrite, XmlRead, PartialEq, Debug, Clone)] #[xml(tag = "opml")] pub struct OPML { /// The version attribute from the element. @@ -74,11 +106,40 @@ impl 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 { + let result: Result = 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 `` child element of ``. /// Contains the metadata of the OPML document. -#[derive(XmlWrite, XmlRead, PartialEq, Debug)] +#[derive(XmlWrite, XmlRead, PartialEq, Debug, Clone, Default)] #[xml(tag = "head")] pub struct Head { /// The title of the document. @@ -135,7 +196,7 @@ pub struct Head { } /// The `` child element of ``. Contains all the ``. -#[derive(XmlWrite, XmlRead, PartialEq, Debug)] +#[derive(XmlWrite, XmlRead, PartialEq, Debug, Clone, Default)] #[xml(tag = "body")] pub struct Body { /// `` elements. @@ -144,7 +205,7 @@ pub struct Body { } /// The `` element. -#[derive(XmlWrite, XmlRead, PartialEq, Debug)] +#[derive(XmlWrite, XmlRead, PartialEq, Debug, Clone, Default)] #[xml(tag = "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. @@ -156,13 +217,13 @@ pub struct Outline { #[xml(attr = "type")] pub r#type: Option, - /// 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`. - #[xml(default, attr = "isComment")] - pub is_comment: bool, + /// 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(attr = "isComment")] + pub is_comment: Option, - /// Indicating whether a breakpoint is set on this outline. This attribute is mainly necessary for outlines used to edit scripts. Defaults to `false`. - #[xml(default, attr = "isBreakpoint")] - pub is_breakpoint: bool, + /// Indicating whether a breakpoint is set on this outline. This attribute is mainly necessary for outlines used to edit scripts. + #[xml(attr = "isBreakpoint")] + pub is_breakpoint: Option, /// The date-time (RFC822) that this `` element was created. #[xml(attr = "created")] @@ -204,3 +265,15 @@ pub struct Outline { #[xml(attr = "url")] pub url: Option, } + +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 + } +} diff --git a/tests/construction.rs b/tests/construction.rs new file mode 100644 index 0000000..2aaca1a --- /dev/null +++ b/tests/construction.rs @@ -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> { + 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> { + 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(()) +} diff --git a/tests/samples/construction_1.opml b/tests/samples/construction_1.opml new file mode 100644 index 0000000..c8472f6 --- /dev/null +++ b/tests/samples/construction_1.opml @@ -0,0 +1 @@ +Rust Feeds diff --git a/tests/samples/construction_2.opml b/tests/samples/construction_2.opml new file mode 100644 index 0000000..df549ef --- /dev/null +++ b/tests/samples/construction_2.opml @@ -0,0 +1 @@ +Rust Feeds diff --git a/tests/valid.rs b/tests/valid.rs index d8f2497..ea9ba16 100644 --- a/tests/valid.rs +++ b/tests/valid.rs @@ -8,37 +8,11 @@ fn test_minimum_valid_opml() { OPML::new(&read("tests/samples/minimum_valid_opml.opml").unwrap()).unwrap(), OPML { version: "2.0".to_string(), - head: Head { - 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, - }, + head: Head::default(), body: Body { outlines: vec![Outline { text: "Outline Text".to_string(), - r#type: None, - 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![] + ..Outline::default() }] }, } @@ -71,8 +45,8 @@ fn test_valid_opml_with_everything() { outlines: vec![Outline { text: "Outline Text".to_string(), r#type: Some("Outline Type".to_string()), - is_breakpoint: true, - is_comment: true, + is_breakpoint: Some(true), + is_comment: Some(true), created: Some("Outline Date".to_string()), category: Some("Outline Category".to_string()), xml_url: Some("Outline XML URL".to_string()), @@ -85,8 +59,8 @@ fn test_valid_opml_with_everything() { outlines: vec![Outline { text: "Nested Outline Text".to_string(), r#type: Some("Nested Outline Type".to_string()), - is_breakpoint: true, - is_comment: false, + is_breakpoint: Some(true), + is_comment: Some(false), created: Some("Nested Outline Date".to_string()), category: Some("Nested Outline Category".to_string()), xml_url: Some("Nested Outline XML URL".to_string()),