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()),