Apply block quote to multiline content
This commit is contained in:
parent
4dae4360cc
commit
b861469319
4 changed files with 87 additions and 11 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -932,6 +932,7 @@ dependencies = [
|
||||||
"html2md",
|
"html2md",
|
||||||
"log",
|
"log",
|
||||||
"megalodon",
|
"megalodon",
|
||||||
|
"regex",
|
||||||
"rss",
|
"rss",
|
||||||
"rustyline",
|
"rustyline",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
|
@ -13,6 +13,7 @@ env_logger = "0.10.0"
|
||||||
html2md = "0.2.14"
|
html2md = "0.2.14"
|
||||||
log = "0.4.19"
|
log = "0.4.19"
|
||||||
megalodon = "0.8.3"
|
megalodon = "0.8.3"
|
||||||
|
regex = "1.9.1"
|
||||||
rss = "2.0.4"
|
rss = "2.0.4"
|
||||||
rustyline = "12.0.0"
|
rustyline = "12.0.0"
|
||||||
tokio = { version = "1.28.2", features = ["default", "full"] }
|
tokio = { version = "1.28.2", features = ["default", "full"] }
|
||||||
|
|
|
@ -6,6 +6,7 @@ use megalodon::{
|
||||||
response::Response,
|
response::Response,
|
||||||
Megalodon,
|
Megalodon,
|
||||||
};
|
};
|
||||||
|
use regex::{Captures, Regex};
|
||||||
use rustyline::DefaultEditor;
|
use rustyline::DefaultEditor;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use url_open::UrlOpen;
|
use url_open::UrlOpen;
|
||||||
|
@ -19,7 +20,7 @@ pub(super) async fn format_status(
|
||||||
"{}
|
"{}
|
||||||
> {}{}",
|
> {}{}",
|
||||||
status.created_at.with_timezone(&Local).format("%H:%M"),
|
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)
|
format_attachments(1, &status.media_attachments)
|
||||||
);
|
);
|
||||||
let Response { json, .. } = client.get_status_context(status.id.clone(), None).await?;
|
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"),
|
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)
|
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 {
|
fn format_attachments(depth: usize, attachments: &[Attachment]) -> String {
|
||||||
let prefix = vec![">"; depth].join("");
|
|
||||||
if attachments.is_empty() {
|
if attachments.is_empty() {
|
||||||
String::default()
|
String::default()
|
||||||
} else {
|
} else {
|
||||||
|
@ -69,10 +73,76 @@ fn format_attachments(depth: usize, attachments: &[Attachment]) -> String {
|
||||||
} else {
|
} else {
|
||||||
a.url.clone()
|
a.url.clone()
|
||||||
};
|
};
|
||||||
format!("{} <img src=\"{}\" />", prefix, src)
|
format!("{} <img src=\"{}\" />", quote_prefix(depth), src)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("\n")
|
.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<S: AsRef<str>>(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<initial>[^>\\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("</p><p>", "\n\n");
|
||||||
|
let content = content.replace("<p>", "");
|
||||||
|
let content = content.replace("</p>", "");
|
||||||
|
// 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, "<p>Foo</p><p>Bar</p>")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -27,12 +27,12 @@ mod range;
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command()]
|
#[command()]
|
||||||
struct Config {
|
struct Config {
|
||||||
#[arg(short, long, env = "MASTODON_URL")]
|
#[arg(short, long, env = "MASTODON_URL", required = true)]
|
||||||
url: String,
|
url: String,
|
||||||
#[arg(short, long, env = "MASTODON_ACCESS_TOKEN")]
|
#[arg(short, long, env = "MASTODON_ACCESS_TOKEN", required = true)]
|
||||||
access_token: String,
|
access_token: String,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
output_dir: String,
|
output_dir: Option<String>,
|
||||||
#[arg(required = true)]
|
#[arg(required = true)]
|
||||||
date: String,
|
date: String,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
|
@ -108,10 +108,14 @@ async fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reversed.reverse();
|
reversed.reverse();
|
||||||
|
if let Some(output_dir) = output_dir {
|
||||||
let output = format!("{}{}.md", output_dir, date);
|
let output = format!("{}{}.md", output_dir, date);
|
||||||
let mut f = File::options().append(true).open(&output)?;
|
let mut f = File::options().append(true).open(&output)?;
|
||||||
f.write_all(&reversed.join("\n\n").as_bytes())?;
|
f.write_all(&reversed.join("\n\n").as_bytes())?;
|
||||||
println!("Appended matching posts to {}.", output);
|
println!("Appended matching posts to {}.", output);
|
||||||
|
} else {
|
||||||
|
println!("{}", reversed.join("\n\n"));
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue