From b861469319ac962394d63b40f8ac97488c157e1a Mon Sep 17 00:00:00 2001
From: Thomas Gideon
Date: Sun, 16 Jul 2023 08:22:28 -0400
Subject: [PATCH] Apply block quote to multiline content
---
Cargo.lock | 1 +
Cargo.toml | 1 +
src/format.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++++---
src/main.rs | 18 +++++++-----
4 files changed, 87 insertions(+), 11 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 7d44745..7f1d35a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -932,6 +932,7 @@ dependencies = [
"html2md",
"log",
"megalodon",
+ "regex",
"rss",
"rustyline",
"tokio",
diff --git a/Cargo.toml b/Cargo.toml
index 945b07c..0d41947 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,6 +13,7 @@ env_logger = "0.10.0"
html2md = "0.2.14"
log = "0.4.19"
megalodon = "0.8.3"
+regex = "1.9.1"
rss = "2.0.4"
rustyline = "12.0.0"
tokio = { version = "1.28.2", features = ["default", "full"] }
diff --git a/src/format.rs b/src/format.rs
index 405561b..a6b50c3 100644
--- a/src/format.rs
+++ b/src/format.rs
@@ -6,6 +6,7 @@ use megalodon::{
response::Response,
Megalodon,
};
+use regex::{Captures, Regex};
use rustyline::DefaultEditor;
use url::Url;
use url_open::UrlOpen;
@@ -19,7 +20,7 @@ pub(super) async fn format_status(
"{}
> {}{}",
status.created_at.with_timezone(&Local).format("%H:%M"),
- parse_html(&status.content).trim(),
+ apply_block_quote(1, (&status.content).trim()),
format_attachments(1, &status.media_attachments)
);
let Response { json, .. } = client.get_status_context(status.id.clone(), None).await?;
@@ -35,7 +36,7 @@ pub(super) async fn format_status(
> {}
>> {}{}",
status.created_at.with_timezone(&Local).format("%H:%M"),
- parse_html(&status.content).trim(),
+ apply_block_quote(2, parse_html(&status.content).trim()),
format_attachments(2, &status.media_attachments)
)
})
@@ -52,8 +53,11 @@ pub(super) async fn format_status(
))
}
+fn quote_prefix(depth: usize) -> String {
+ vec![">"; depth].join("")
+}
+
fn format_attachments(depth: usize, attachments: &[Attachment]) -> String {
- let prefix = vec![">"; depth].join("");
if attachments.is_empty() {
String::default()
} else {
@@ -69,10 +73,76 @@ fn format_attachments(depth: usize, attachments: &[Attachment]) -> String {
} else {
a.url.clone()
};
- format!("{} ", prefix, src)
+ format!("{} ", quote_prefix(depth), src)
})
.collect::>()
.join("\n")
)
}
}
+
+// without processing the content per line, the block quote markdown is only added to the first
+// line, breaking the desired formatting
+fn apply_block_quote>(depth: usize, content: S) -> String {
+ let prefix = quote_prefix(depth);
+ let empty = Regex::new("\n\\s").expect("Failed to compiled regex for nested empty lines!");
+ let non_empty =
+ Regex::new("\n(?P[^>\\s])").expect("Failed to compile regex for nested lines!");
+ // parsing some multiline content adds paragraph tags, convert those back to new lines to apply
+ // the block quote level to each line
+ let content = content.as_ref().replace("
", "\n\n");
+ let content = content.replace("
", "");
+ let content = content.replace("
", "");
+ // replace separately to avoid trailing spaces when replacing empty lines with the prefix
+ let content = empty.replace(content.as_ref(), format!("\n{}\n", prefix));
+ let content = non_empty.replace_all(&content, |c: &Captures| {
+ format!("\n{} {}", prefix, c["initial"].to_string())
+ });
+ content.to_string()
+}
+
+#[cfg(test)]
+mod test {
+ use crate::format::apply_block_quote;
+
+ #[test]
+ fn test_apply_bq() {
+ assert_eq!(
+ "Foo
+>
+> Bar"
+ .to_owned(),
+ apply_block_quote(
+ 1, "Foo
+
+Bar"
+ )
+ );
+ }
+
+ #[test]
+ fn test_apply_bq_deeper() {
+ assert_eq!(
+ "Foo
+>>
+>> Bar"
+ .to_owned(),
+ apply_block_quote(
+ 2, "Foo
+
+Bar"
+ )
+ );
+ }
+
+ #[test]
+ fn test_apply_bq_strip_ps() {
+ assert_eq!(
+ "Foo
+>>
+>> Bar"
+ .to_owned(),
+ apply_block_quote(2, "Foo
Bar
")
+ );
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index f8adbee..be22d03 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -27,12 +27,12 @@ mod range;
#[derive(Debug, Parser)]
#[command()]
struct Config {
- #[arg(short, long, env = "MASTODON_URL")]
+ #[arg(short, long, env = "MASTODON_URL", required = true)]
url: String,
- #[arg(short, long, env = "MASTODON_ACCESS_TOKEN")]
+ #[arg(short, long, env = "MASTODON_ACCESS_TOKEN", required = true)]
access_token: String,
#[arg(short, long)]
- output_dir: String,
+ output_dir: Option,
#[arg(required = true)]
date: String,
#[arg(short, long)]
@@ -108,10 +108,14 @@ async fn main() -> Result<()> {
}
}
reversed.reverse();
- let output = format!("{}{}.md", output_dir, date);
- let mut f = File::options().append(true).open(&output)?;
- f.write_all(&reversed.join("\n\n").as_bytes())?;
- println!("Appended matching posts to {}.", output);
+ if let Some(output_dir) = output_dir {
+ let output = format!("{}{}.md", output_dir, date);
+ let mut f = File::options().append(true).open(&output)?;
+ f.write_all(&reversed.join("\n\n").as_bytes())?;
+ println!("Appended matching posts to {}.", output);
+ } else {
+ println!("{}", reversed.join("\n\n"));
+ }
Ok(())
}