Compare commits

...

6 Commits

Author SHA1 Message Date
projectmoon 1fe590925b More tests
continuous-integration/drone/push Build is passing Details
2024-03-27 12:36:42 +01:00
projectmoon 3a4f7ba3a4 Verison 0.1.7: integrate Gemini library bugfixes
continuous-integration/drone/push Build is passing Details
2024-03-24 16:10:01 +01:00
projectmoon e9e04b1a0b Integrate germ bug fixes 2024-03-24 16:08:18 +01:00
projectmoon 8aa7369a12 upgrade to germ 0.4 2024-03-24 16:05:14 +01:00
projectmoon aab2e0e358 Version 0.1.6: strip release binaries
continuous-integration/drone/push Build is passing Details
2024-03-22 18:19:53 +01:00
projectmoon d219e28280 Strip binaries when building static release 2024-03-22 18:15:56 +01:00
6 changed files with 158 additions and 20 deletions

View File

@ -1,3 +1,6 @@
[build]
rustflags = ["-C", "target-feature=+crt-static"]
target = "x86_64-unknown-linux-gnu"
[profile.release]
strip = true

8
Cargo.lock generated
View File

@ -428,7 +428,7 @@ dependencies = [
[[package]]
name = "gemfreely"
version = "0.1.4"
version = "0.1.7"
dependencies = [
"anyhow",
"atom_syndication",
@ -478,12 +478,14 @@ dependencies = [
[[package]]
name = "germ"
version = "0.3.10"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1a3b6e49850abb8a060002e2a3ec9a7e49626fc10192a93703f54e2589b9361"
checksum = "6a44d21599b2d90136a258162d244267909f00073c4dce7ebda5a91b10f93f59"
dependencies = [
"anyhow",
"rustls 0.21.10",
"tokio 1.36.0",
"tokio-rustls 0.24.1",
"url",
]

View File

@ -1,6 +1,6 @@
[package]
name = "gemfreely"
version = "0.1.5"
version = "0.1.7"
edition = "2021"
license = "AGPL-3.0-or-later"
description = "Synchronize Gemini protocol blogs to the Fediverse"
@ -12,7 +12,7 @@ atom_syndication = "0.12.2"
chrono = "0.4.35"
clap = { version = "4.5.3", features = ["derive"] }
gemini-feed = "0.1.0"
germ = "0.3"
germ = {version = "0.4", features = ["blocking"] }
once_cell = "1.19.0"
regex = "1.10.3"
tokio = {version = "1.36", features = [ "full" ] }

View File

@ -94,13 +94,18 @@ async fn sync_gemlog(
let mut count = 0;
for entry in gemlogs_to_post {
let post = wf.create_post(entry).await?;
let result = wf.create_post(entry).await;
count += 1;
println!(
"Created post: {} [title={}]",
post.id,
post.title.unwrap_or_default()
);
if let Ok(post) = result {
println!(
"Created post: {} [title={}]",
post.id,
post.title.unwrap_or_default()
);
} else {
println!("Error creating post: {} ", result.unwrap_err());
}
}
println!("Post synchronization complete [posts synced={}]", count);

View File

@ -1,7 +1,6 @@
use chrono::{DateTime, NaiveDate, Utc};
use once_cell::sync::{Lazy, OnceCell};
use regex::Regex;
use std::borrow::Cow;
use std::path::PathBuf;
use std::result::Result as StdResult;
use std::slice::IterMut;
@ -10,7 +9,9 @@ use anyhow::{anyhow, Error, Result};
use atom_syndication::{Entry as AtomEntry, Feed as AtomFeed};
use germ::ast::{Ast as GemtextAst, Node as GemtextNode};
use germ::convert::{self as germ_convert, Target};
use germ::request::{request as gemini_request, Response as GeminiResponse};
use germ::meta::Meta as GeminiMeta;
use germ::request::blocking::request as gemini_request;
use germ::request::Response as GeminiResponse;
use url::Url;
use crate::Cli;
@ -19,9 +20,9 @@ static GEMFEED_POST_REGEX: Lazy<regex::Regex> =
Lazy::new(|| Regex::new(r#"(\d\d\d\d-\d\d-\d\d)"#).unwrap());
fn is_header(level: usize) -> bool {
// For some reason, Germ reports headers with an emoji as header level 0.
level == 0 || level == 1
level == 1
}
fn is_gemfeed_post_link(node: &GemtextNode) -> bool {
match node {
GemtextNode::Link {
@ -57,17 +58,17 @@ impl GemfeedType {
const ATOM_MIME_TYPES: &'static [&'static str] = &["text/xml", "application/atom+xml"];
}
impl From<Cow<'_, str>> for GemfeedType {
impl From<GeminiMeta> for GemfeedType {
// See https://github.com/gemrest/germ/issues/2. Will be converted
// to use germ Meta struct after this is fixed.
fn from(mime: Cow<'_, str>) -> Self {
fn from(meta: GeminiMeta) -> Self {
let is_atom = Self::ATOM_MIME_TYPES
.into_iter()
.any(|atom_mime| mime.contains(atom_mime));
.any(|atom_mime| meta.mime().contains(atom_mime));
if is_atom {
GemfeedType::Atom
} else if mime.contains("text/gemini") {
} else if meta.mime().contains("text/gemini") {
GemfeedType::Gemtext
} else {
GemfeedType::Unknown
@ -127,7 +128,9 @@ impl Gemfeed {
pub fn load_with_settings(url: &Url, settings: &GemfeedParserSettings) -> Result<Gemfeed> {
let resp = gemini_request(url)?;
match GemfeedType::from(resp.meta()) {
let meta = GeminiMeta::from_string(resp.meta());
match GemfeedType::from(meta) {
GemfeedType::Gemtext => Self::load_from_gemfeed(url, resp),
GemfeedType::Atom => Self::load_from_atom(url, resp, &settings),
_ => Err(anyhow!(
@ -221,8 +224,31 @@ pub struct GemfeedEntry {
body: OnceCell<String>,
}
impl Default for GemfeedEntry {
fn default() -> Self {
GemfeedEntry {
body: OnceCell::default(),
title: String::default(),
slug: String::default(),
url: Url::parse("gemini://example.com").unwrap(),
published: Option::default(),
}
}
}
#[allow(dead_code)]
impl GemfeedEntry {
/// Consumes self to forcibly set body to the given string.
pub fn with_body(self, body: String) -> GemfeedEntry {
GemfeedEntry {
title: self.title,
slug: self.slug,
published: self.published,
url: self.url,
body: OnceCell::from(body)
}
}
pub fn from_gemtext(base_url: &Url, node: &GemtextNode) -> Result<GemfeedEntry> {
let link = GemfeedLink::try_from(node)?;
// Gemfeeds have only the date--according to spec, it should
@ -402,6 +428,63 @@ impl TryFrom<&AtomEntry> for GemfeedLink {
}
}
#[cfg(test)]
mod gemfeed_entry_tests {
use super::*;
#[test]
fn parse_markdown_with_gt_lt_title() -> Result<()> {
let gemtext: String = r#"
# This is gemtext <dyn>
With a > in it.
"#
.lines()
.map(|line| line.trim_start())
.map(|line| format!("{}\n", line))
.collect();
let entry = GemfeedEntry {
published: None,
slug: "".to_string(),
title: "".to_string(),
url: Url::parse("gemini://example.com")?,
body: OnceCell::from(gemtext),
};
let result = entry.body_as_markdown();
assert!(result.is_ok());
Ok(())
}
#[test]
fn parse_markdown_with_gt_lt() -> Result<()> {
let gemtext: String = r#"
# This is gemtext
With a < in > it.
"#
.lines()
.map(|line| line.trim_start())
.map(|line| format!("{}\n", line))
.collect();
let entry = GemfeedEntry {
published: None,
slug: "".to_string(),
title: "".to_string(),
url: Url::parse("gemini://example.com")?,
body: OnceCell::from(gemtext),
};
let result = entry.body_as_markdown();
assert!(result.is_ok());
Ok(())
}
}
#[cfg(test)]
mod gemfeed_tests {
use super::*;
@ -582,6 +665,30 @@ mod gemfeed_tests {
Ok(())
}
#[test]
fn parse_gemfeed_handles_gt_lt() -> Result<()> {
let gemfeed: String = r#"
# My Gemfeed
This is a gemfeed.
## Posts
=> post2.gmi 2023-03-05 Post 2 <dyn>
=> 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 results = parse_gemfeed(&base_url, &ast)?;
assert_eq!(results.len(), 2);
Ok(())
}
#[test]
fn convert_gemfeed_links_success() -> Result<()> {
let gemfeed_links: String = r#"

View File

@ -99,3 +99,24 @@ impl TryFrom<&GemfeedEntry> for PostCreateRequest {
Ok(req)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tryfrom_to_request_handles_gt_lt() {
let gemtext: String = r#"
# This is gemtext <dyn>
With a > in it.
"#
.lines()
.map(|line| line.trim_start())
.map(|line| format!("{}\n", line))
.collect();
let entry = GemfeedEntry::default().with_body(gemtext);
let result = PostCreateRequest::try_from(entry);
assert!(result.is_ok());
}
}