Compare commits

..

3 Commits

Author SHA1 Message Date
projectmoon 06344a67ea Update version
continuous-integration/drone/push Build is passing Details
2024-03-21 17:28:14 +01:00
projectmoon 4f0bb705fb Rename parse functions to better indicate difference between gemfeed and atom 2024-03-21 17:28:02 +01:00
projectmoon dbddde049e Gemfeed parsing now requires a title. 2024-03-21 17:27:14 +01:00
3 changed files with 58 additions and 17 deletions

2
Cargo.lock generated
View File

@ -428,7 +428,7 @@ dependencies = [
[[package]] [[package]]
name = "gemfreely" name = "gemfreely"
version = "0.1.2" version = "0.1.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"atom_syndication", "atom_syndication",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "gemfreely" name = "gemfreely"
version = "0.1.2" version = "0.1.3"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
description = "Synchronize Gemini protocol blogs to the Fediverse" description = "Synchronize Gemini protocol blogs to the Fediverse"

View File

@ -29,7 +29,7 @@ fn is_gemfeed_post_link(node: &GemtextNode) -> bool {
} }
} }
fn parse_gemfeed_gemtext(base_url: &Url, gemfeed: &GemtextAst) -> Result<Vec<GemfeedEntry>> { fn parse_gemfeed(base_url: &Url, gemfeed: &GemtextAst) -> Result<Vec<GemfeedEntry>> {
gemfeed gemfeed
.inner() .inner()
.into_iter() .into_iter()
@ -38,9 +38,10 @@ fn parse_gemfeed_gemtext(base_url: &Url, gemfeed: &GemtextAst) -> Result<Vec<Gem
.collect() .collect()
} }
fn parse_gemfeed_atom(feed: &str, settings: &GemfeedParserSettings) -> Result<Vec<GemfeedEntry>> { fn parse_atom(
let feed = feed.parse::<AtomFeed>()?; feed: &AtomFeed,
settings: &GemfeedParserSettings,
) -> Result<Vec<GemfeedEntry>> {
feed.entries() feed.entries()
.into_iter() .into_iter()
.map(|entry| GemfeedEntry::from_atom(entry, &settings.atom_date_format)) .map(|entry| GemfeedEntry::from_atom(entry, &settings.atom_date_format))
@ -75,9 +76,11 @@ impl From<Cow<'_, str>> for GemfeedType {
} }
} }
#[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub struct Gemfeed { pub struct Gemfeed {
url: Url, url: Url,
title: String,
entries: Vec<GemfeedEntry>, entries: Vec<GemfeedEntry>,
} }
@ -111,9 +114,10 @@ impl Default for GemfeedParserSettings<'_> {
#[allow(dead_code)] #[allow(dead_code)]
impl Gemfeed { impl Gemfeed {
pub fn new(url: &Url, entries: Vec<GemfeedEntry>) -> Gemfeed { pub fn new(url: &Url, title: &str, entries: Vec<GemfeedEntry>) -> Gemfeed {
Gemfeed { Gemfeed {
url: url.clone(), url: url.clone(),
title: title.to_owned(),
entries, entries,
} }
} }
@ -140,8 +144,10 @@ impl Gemfeed {
settings: &GemfeedParserSettings, settings: &GemfeedParserSettings,
) -> Result<Gemfeed> { ) -> Result<Gemfeed> {
if let Some(content) = resp.content() { if let Some(content) = resp.content() {
let entries = parse_gemfeed_atom(content, settings)?; let feed = content.parse::<AtomFeed>()?;
Ok(Self::new(url, entries)) let entries = parse_atom(&feed, settings)?;
let title = feed.title();
Ok(Self::new(url, title, entries))
} else { } else {
Err(anyhow!("Not a valid Atom Gemfeed")) Err(anyhow!("Not a valid Atom Gemfeed"))
} }
@ -153,12 +159,24 @@ impl Gemfeed {
.to_owned() .to_owned()
.map(|text| GemtextAst::from_value(&text)); .map(|text| GemtextAst::from_value(&text));
// TODO should be some actual validation of the feed here.
if let Some(ref feed) = maybe_feed { if let Some(ref feed) = maybe_feed {
let entries = parse_gemfeed_gemtext(url, feed)?; Self::load_from_ast(url, feed)
Ok(Self::new(url, entries))
} else { } else {
Err(anyhow!("Not a valid Gemtext Gemfeed")) Err(anyhow!("Not a valid Gemfeed - could not parse gemtext"))
}
}
fn load_from_ast(url: &Url, feed: &GemtextAst) -> Result<Gemfeed> {
let feed_title = feed.inner().iter().find_map(|node| match node {
GemtextNode::Heading { level, text } if *level == (1 as usize) => Some(text),
_ => None,
});
if let Some(title) = feed_title {
let entries = parse_gemfeed(url, feed)?;
Ok(Self::new(url, title, entries))
} else {
Err(anyhow!("Not a valid Gemfeed: missing title"))
} }
} }
@ -209,7 +227,7 @@ pub struct GemfeedEntry {
impl GemfeedEntry { impl GemfeedEntry {
pub fn from_ast(base_url: &Url, node: &GemtextNode) -> Result<GemfeedEntry> { pub fn from_ast(base_url: &Url, node: &GemtextNode) -> Result<GemfeedEntry> {
let link = GemfeedLink::try_from(node)?; let link = GemfeedLink::try_from(node)?;
// Gemfeeds have only the date--lock to 12pm UTC as a guess. // Gemfeeds have only the date--according to spec, it should be 12pm UTC.
println!("{:?}", link.published); println!("{:?}", link.published);
let publish_date = link let publish_date = link
.published .published
@ -391,6 +409,29 @@ impl TryFrom<&AtomEntry> for GemfeedLink {
mod gemfeed_tests { mod gemfeed_tests {
use super::*; use super::*;
#[test]
fn parse_gemfeed_invalid_if_no_title() -> Result<()> {
let gemfeed: String = r#"
This is a gemfeed without a title.
=> atom.xml Atom Feed
## Posts
=> post2.gmi 2023-03-05 Post 2
=> post1.gmi 2023-02-01 Post 1
"#
.lines()
.map(|line| line.trim_start())
.map(|line| format!("{}\n", line))
.collect();
let base_url = Url::parse("gemini://example.com/posts")?;
let ast = GemtextAst::from_string(gemfeed);
let result = Gemfeed::load_from_ast(&base_url, &ast);
assert!(matches!(result, Err(_)));
Ok(())
}
#[test] #[test]
fn parse_gemfeed_ignores_non_post_links() -> Result<()> { fn parse_gemfeed_ignores_non_post_links() -> Result<()> {
let gemfeed: String = r#" let gemfeed: String = r#"
@ -411,7 +452,7 @@ mod gemfeed_tests {
let base_url = Url::parse("gemini://example.com/posts")?; let base_url = Url::parse("gemini://example.com/posts")?;
let ast = GemtextAst::from_string(gemfeed); let ast = GemtextAst::from_string(gemfeed);
let results = parse_gemfeed_gemtext(&base_url, &ast)?; let results = parse_gemfeed(&base_url, &ast)?;
assert_eq!(results.len(), 2); assert_eq!(results.len(), 2);
Ok(()) Ok(())
} }
@ -435,13 +476,13 @@ mod gemfeed_tests {
let base_url = Url::parse("gemini://example.com/posts")?; let base_url = Url::parse("gemini://example.com/posts")?;
let ast = GemtextAst::from_string(gemfeed); let ast = GemtextAst::from_string(gemfeed);
let results = parse_gemfeed_gemtext(&base_url, &ast)?; let results = parse_gemfeed(&base_url, &ast)?;
assert_eq!(results.len(), 2); assert_eq!(results.len(), 2);
Ok(()) Ok(())
} }
#[test] #[test]
fn convert_gemfeed_success() -> Result<()> { fn convert_gemfeed_links_success() -> Result<()> {
let gemfeed_links: String = r#" let gemfeed_links: String = r#"
=> post2.gmi 2023-03-05 Post 2 => post2.gmi 2023-03-05 Post 2
=> post1.gmi 2023-02-01 Post 1 => post1.gmi 2023-02-01 Post 1