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]]
name = "gemfreely"
version = "0.1.2"
version = "0.1.3"
dependencies = [
"anyhow",
"atom_syndication",

View File

@ -1,6 +1,6 @@
[package]
name = "gemfreely"
version = "0.1.2"
version = "0.1.3"
edition = "2021"
license = "AGPL-3.0-or-later"
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
.inner()
.into_iter()
@ -38,9 +38,10 @@ fn parse_gemfeed_gemtext(base_url: &Url, gemfeed: &GemtextAst) -> Result<Vec<Gem
.collect()
}
fn parse_gemfeed_atom(feed: &str, settings: &GemfeedParserSettings) -> Result<Vec<GemfeedEntry>> {
let feed = feed.parse::<AtomFeed>()?;
fn parse_atom(
feed: &AtomFeed,
settings: &GemfeedParserSettings,
) -> Result<Vec<GemfeedEntry>> {
feed.entries()
.into_iter()
.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)]
pub struct Gemfeed {
url: Url,
title: String,
entries: Vec<GemfeedEntry>,
}
@ -111,9 +114,10 @@ impl Default for GemfeedParserSettings<'_> {
#[allow(dead_code)]
impl Gemfeed {
pub fn new(url: &Url, entries: Vec<GemfeedEntry>) -> Gemfeed {
pub fn new(url: &Url, title: &str, entries: Vec<GemfeedEntry>) -> Gemfeed {
Gemfeed {
url: url.clone(),
title: title.to_owned(),
entries,
}
}
@ -140,8 +144,10 @@ impl Gemfeed {
settings: &GemfeedParserSettings,
) -> Result<Gemfeed> {
if let Some(content) = resp.content() {
let entries = parse_gemfeed_atom(content, settings)?;
Ok(Self::new(url, entries))
let feed = content.parse::<AtomFeed>()?;
let entries = parse_atom(&feed, settings)?;
let title = feed.title();
Ok(Self::new(url, title, entries))
} else {
Err(anyhow!("Not a valid Atom Gemfeed"))
}
@ -153,12 +159,24 @@ impl Gemfeed {
.to_owned()
.map(|text| GemtextAst::from_value(&text));
// TODO should be some actual validation of the feed here.
if let Some(ref feed) = maybe_feed {
let entries = parse_gemfeed_gemtext(url, feed)?;
Ok(Self::new(url, entries))
Self::load_from_ast(url, feed)
} 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 {
pub fn from_ast(base_url: &Url, node: &GemtextNode) -> Result<GemfeedEntry> {
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);
let publish_date = link
.published
@ -391,6 +409,29 @@ impl TryFrom<&AtomEntry> for GemfeedLink {
mod gemfeed_tests {
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]
fn parse_gemfeed_ignores_non_post_links() -> Result<()> {
let gemfeed: String = r#"
@ -411,7 +452,7 @@ mod gemfeed_tests {
let base_url = Url::parse("gemini://example.com/posts")?;
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);
Ok(())
}
@ -435,13 +476,13 @@ mod gemfeed_tests {
let base_url = Url::parse("gemini://example.com/posts")?;
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);
Ok(())
}
#[test]
fn convert_gemfeed_success() -> Result<()> {
fn convert_gemfeed_links_success() -> Result<()> {
let gemfeed_links: String = r#"
=> post2.gmi 2023-03-05 Post 2
=> post1.gmi 2023-02-01 Post 1