Add option to export to stdout

This commit is contained in:
Joscha 2023-02-12 00:54:09 +01:00
parent 0ceaffc608
commit ca10ca277b
4 changed files with 47 additions and 40 deletions

View file

@ -17,6 +17,7 @@ Procedure when bumping the version number:
### Added ### Added
- `--verbose` flag - `--verbose` flag
- `json-stream` room export format - `json-stream` room export format
- Option to export to stdout
### Changed ### Changed
- Respect colon-delimited emoji when calculating nick hue - Respect colon-delimited emoji when calculating nick hue

View file

@ -4,9 +4,9 @@ mod json;
mod text; mod text;
use std::fs::File; use std::fs::File;
use std::io::{BufWriter, Write}; use std::io::{self, BufWriter, Write};
use crate::vault::EuphVault; use crate::vault::{EuphRoomVault, EuphVault};
#[derive(Debug, Clone, Copy, clap::ValueEnum)] #[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum Format { pub enum Format {
@ -57,12 +57,28 @@ pub struct Args {
/// If the value ends with a `/`, it is assumed to point to a directory and /// If the value ends with a `/`, it is assumed to point to a directory and
/// `%r.%e` will be appended. /// `%r.%e` will be appended.
/// ///
/// If the value is a literal `-`, the export will be written to stdout. To
/// write to a file named `-`, you can use `./-`.
///
/// Must be a valid utf-8 encoded string. /// Must be a valid utf-8 encoded string.
#[arg(long, short, default_value_t = Into::into("%r.%e"))] #[arg(long, short, default_value_t = Into::into("%r.%e"))]
#[arg(verbatim_doc_comment)] #[arg(verbatim_doc_comment)]
out: String, out: String,
} }
async fn export_room<W: Write>(
vault: &EuphRoomVault,
out: &mut W,
format: Format,
) -> anyhow::Result<()> {
match format {
Format::Text => text::export(vault, out).await?,
Format::Json => json::export(vault, out).await?,
Format::JsonStream => json::export_stream(vault, out).await?,
}
Ok(())
}
pub async fn export(vault: &EuphVault, mut args: Args) -> anyhow::Result<()> { pub async fn export(vault: &EuphVault, mut args: Args) -> anyhow::Result<()> {
if args.out.ends_with('/') { if args.out.ends_with('/') {
args.out.push_str("%r.%e"); args.out.push_str("%r.%e");
@ -79,21 +95,24 @@ pub async fn export(vault: &EuphVault, mut args: Args) -> anyhow::Result<()> {
}; };
if rooms.is_empty() { if rooms.is_empty() {
println!("No rooms to export"); eprintln!("No rooms to export");
} }
for room in rooms { for room in rooms {
let out = format_out(&args.out, &room, args.format); if args.out == "-" {
println!("Exporting &{room} as {} to {out}", args.format.name()); eprintln!("Exporting &{room} as {} to stdout", args.format.name());
let vault = vault.room(room);
let vault = vault.room(room); let mut stdout = BufWriter::new(io::stdout());
let mut file = BufWriter::new(File::create(out)?); export_room(&vault, &mut stdout, args.format).await?;
match args.format { stdout.flush()?;
Format::Text => text::export_to_file(&vault, &mut file).await?, } else {
Format::Json => json::export_to_file(&vault, &mut file).await?, let out = format_out(&args.out, &room, args.format);
Format::JsonStream => json::export_stream_to_file(&vault, &mut file).await?, eprintln!("Exporting &{room} as {} to {out}", args.format.name());
let vault = vault.room(room);
let mut file = BufWriter::new(File::create(out)?);
export_room(&vault, &mut file, args.format).await?;
file.flush()?;
} }
file.flush()?;
} }
Ok(()) Ok(())

View file

@ -1,14 +1,10 @@
use std::fs::File; use std::io::Write;
use std::io::{BufWriter, Write};
use crate::vault::EuphRoomVault; use crate::vault::EuphRoomVault;
const CHUNK_SIZE: usize = 10000; const CHUNK_SIZE: usize = 10000;
pub async fn export_to_file( pub async fn export<W: Write>(vault: &EuphRoomVault, file: &mut W) -> anyhow::Result<()> {
vault: &EuphRoomVault,
file: &mut BufWriter<File>,
) -> anyhow::Result<()> {
write!(file, "[")?; write!(file, "[")?;
let mut total = 0; let mut total = 0;
@ -39,14 +35,10 @@ pub async fn export_to_file(
write!(file, "\n]")?; write!(file, "\n]")?;
println!(" {total} messages in total"); println!(" {total} messages in total");
Ok(()) Ok(())
} }
pub async fn export_stream_to_file( pub async fn export_stream<W: Write>(vault: &EuphRoomVault, file: &mut W) -> anyhow::Result<()> {
vault: &EuphRoomVault,
file: &mut BufWriter<File>,
) -> anyhow::Result<()> {
let mut total = 0; let mut total = 0;
let mut offset = 0; let mut offset = 0;
loop { loop {
@ -69,6 +61,5 @@ pub async fn export_stream_to_file(
} }
println!(" {total} messages in total"); println!(" {total} messages in total");
Ok(()) Ok(())
} }

View file

@ -1,5 +1,4 @@
use std::fs::File; use std::io::Write;
use std::io::{BufWriter, Write};
use euphoxide::api::MessageId; use euphoxide::api::MessageId;
use time::format_description::FormatItem; use time::format_description::FormatItem;
@ -14,16 +13,13 @@ const TIME_FORMAT: &[FormatItem<'_>] =
format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
const TIME_EMPTY: &str = " "; const TIME_EMPTY: &str = " ";
pub async fn export_to_file( pub async fn export<W: Write>(vault: &EuphRoomVault, out: &mut W) -> anyhow::Result<()> {
vault: &EuphRoomVault,
file: &mut BufWriter<File>,
) -> anyhow::Result<()> {
let mut exported_trees = 0; let mut exported_trees = 0;
let mut exported_msgs = 0; let mut exported_msgs = 0;
let mut root_id = vault.first_root_id().await; let mut root_id = vault.first_root_id().await;
while let Some(some_root_id) = root_id { while let Some(some_root_id) = root_id {
let tree = vault.tree(some_root_id).await; let tree = vault.tree(some_root_id).await;
write_tree(file, &tree, some_root_id, 0)?; write_tree(out, &tree, some_root_id, 0)?;
root_id = vault.next_root_id(some_root_id).await; root_id = vault.next_root_id(some_root_id).await;
exported_trees += 1; exported_trees += 1;
@ -38,8 +34,8 @@ pub async fn export_to_file(
Ok(()) Ok(())
} }
fn write_tree( fn write_tree<W: Write>(
file: &mut BufWriter<File>, out: &mut W,
tree: &Tree<SmallMessage>, tree: &Tree<SmallMessage>,
id: MessageId, id: MessageId,
indent: usize, indent: usize,
@ -47,22 +43,22 @@ fn write_tree(
let indent_string = "| ".repeat(indent); let indent_string = "| ".repeat(indent);
if let Some(msg) = tree.msg(&id) { if let Some(msg) = tree.msg(&id) {
write_msg(file, &indent_string, msg)?; write_msg(out, &indent_string, msg)?;
} else { } else {
write_placeholder(file, &indent_string)?; write_placeholder(out, &indent_string)?;
} }
if let Some(children) = tree.children(&id) { if let Some(children) = tree.children(&id) {
for child in children { for child in children {
write_tree(file, tree, *child, indent + 1)?; write_tree(out, tree, *child, indent + 1)?;
} }
} }
Ok(()) Ok(())
} }
fn write_msg( fn write_msg<W: Write>(
file: &mut BufWriter<File>, file: &mut W,
indent_string: &str, indent_string: &str,
msg: &SmallMessage, msg: &SmallMessage,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
@ -85,7 +81,7 @@ fn write_msg(
Ok(()) Ok(())
} }
fn write_placeholder(file: &mut BufWriter<File>, indent_string: &str) -> anyhow::Result<()> { fn write_placeholder<W: Write>(file: &mut W, indent_string: &str) -> anyhow::Result<()> {
writeln!(file, "{TIME_EMPTY} {indent_string}[...]")?; writeln!(file, "{TIME_EMPTY} {indent_string}[...]")?;
Ok(()) Ok(())
} }