Add option to export plain text room logs

This commit is contained in:
Joscha 2022-07-07 03:41:44 +02:00
parent 02d3b067b8
commit 0ccf788d7b
5 changed files with 258 additions and 14 deletions

136
Cargo.lock generated
View file

@ -48,6 +48,17 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -119,6 +130,45 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "clap"
version = "3.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"once_cell",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_derive"
version = "3.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.3" version = "0.9.3"
@ -142,6 +192,7 @@ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"chrono", "chrono",
"clap",
"crossterm", "crossterm",
"directories", "directories",
"edit", "edit",
@ -157,6 +208,7 @@ dependencies = [
"tokio", "tokio",
"tokio-tungstenite", "tokio-tungstenite",
"toss", "toss",
"unicode-width",
] ]
[[package]] [[package]]
@ -414,15 +466,27 @@ dependencies = [
"ahash", "ahash",
] ]
[[package]]
name = "hashbrown"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3"
[[package]] [[package]]
name = "hashlink" name = "hashlink"
version = "0.7.0" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
dependencies = [ dependencies = [
"hashbrown", "hashbrown 0.11.2",
] ]
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.1.19"
@ -460,6 +524,16 @@ dependencies = [
"unicode-normalization", "unicode-normalization",
] ]
[[package]]
name = "indexmap"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"autocfg",
"hashbrown 0.12.1",
]
[[package]] [[package]]
name = "instant" name = "instant"
version = "0.1.12" version = "0.1.12"
@ -590,6 +664,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "os_str_bytes"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa"
[[package]] [[package]]
name = "palette" name = "palette"
version = "0.6.0" version = "0.6.0"
@ -666,6 +746,30 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.40" version = "1.0.40"
@ -979,6 +1083,12 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.98" version = "1.0.98"
@ -1004,6 +1114,21 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.31" version = "1.0.31"
@ -1346,6 +1471,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"

View file

@ -7,6 +7,7 @@ edition = "2021"
anyhow = "1.0.58" anyhow = "1.0.58"
async-trait = "0.1.56" async-trait = "0.1.56"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
clap = { version = "3.2.8", features = ["derive"] }
crossterm = "0.24.0" crossterm = "0.24.0"
directories = "4.0.1" directories = "4.0.1"
edit = "0.1.4" edit = "0.1.4"
@ -20,6 +21,7 @@ serde = { version = "1.0.138", features = ["derive"] }
serde_json = "1.0.82" serde_json = "1.0.82"
thiserror = "1.0.31" thiserror = "1.0.31"
tokio = { version = "1.19.2", features = ["full"] } tokio = { version = "1.19.2", features = ["full"] }
unicode-width = "0.1.9"
[dependencies.tokio-tungstenite] [dependencies.tokio-tungstenite]
version = "0.17.1" version = "0.17.1"

72
src/export.rs Normal file
View file

@ -0,0 +1,72 @@
//! Export logs from the vault to plain text files.
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
use unicode_width::UnicodeWidthStr;
use crate::euph::api::Snowflake;
use crate::store::{MsgStore, Tree};
use crate::vault::{EuphMsg, Vault};
const TIME_FORMAT: &str = "%F %T";
const TIME_EMPTY: &str = " ";
pub async fn export(vault: &Vault, room: String, file: &Path) -> anyhow::Result<()> {
let mut file = BufWriter::new(File::create(file)?);
let vault = vault.euph(room);
let mut tree_id = vault.first_tree().await;
while let Some(some_tree_id) = tree_id {
let tree = vault.tree(&some_tree_id).await;
write_tree(&mut file, &tree, some_tree_id, 0)?;
tree_id = vault.next_tree(&some_tree_id).await;
}
Ok(())
}
fn write_tree(
file: &mut BufWriter<File>,
tree: &Tree<EuphMsg>,
id: Snowflake,
indent: usize,
) -> anyhow::Result<()> {
let indent_string = "| ".repeat(indent);
if let Some(msg) = tree.msg(&id) {
write_msg(file, &indent_string, msg)?;
} else {
write_placeholder(file, &indent_string)?;
}
if let Some(children) = tree.children(&id) {
for child in children {
write_tree(file, tree, *child, indent + 1)?;
}
}
Ok(())
}
fn write_msg(file: &mut BufWriter<File>, indent_string: &str, msg: &EuphMsg) -> anyhow::Result<()> {
let nick = &msg.nick;
let nick_empty = " ".repeat(nick.width());
for (i, line) in msg.content.lines().enumerate() {
if i == 0 {
let time = msg.time.0.format(TIME_FORMAT);
writeln!(file, "{time} {indent_string}[{nick}] {line}")?;
} else {
writeln!(file, "{TIME_EMPTY} {indent_string} {nick_empty} {line}")?;
}
}
Ok(())
}
fn write_placeholder(file: &mut BufWriter<File>, indent_string: &str) -> anyhow::Result<()> {
writeln!(file, "{TIME_EMPTY} {indent_string}...")?;
Ok(())
}

View file

@ -3,21 +3,65 @@
// TODO Clean up use and manipulation of toss Pos and Size // TODO Clean up use and manipulation of toss Pos and Size
mod euph; mod euph;
mod export;
mod logger; mod logger;
mod replies; mod replies;
mod store; mod store;
mod ui; mod ui;
mod vault; mod vault;
use std::path::PathBuf;
use clap::Parser;
use directories::ProjectDirs; use directories::ProjectDirs;
use log::info; use log::info;
use toss::terminal::Terminal; use toss::terminal::Terminal;
use ui::Ui; use ui::Ui;
use vault::Vault;
use crate::logger::Logger; use crate::logger::Logger;
#[derive(Debug, clap::Subcommand)]
enum Command {
/// Run the client interactively (default).
Run,
/// Export logs for a single room as a plain text file.
Export { room: String, file: PathBuf },
}
impl Default for Command {
fn default() -> Self {
Self::Run
}
}
#[derive(Debug, clap::Parser)]
struct Args {
#[clap(subcommand)]
command: Option<Command>,
}
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let args = Args::parse();
let dirs = ProjectDirs::from("de", "plugh", "cove").expect("unable to determine directories");
println!("Data dir: {}", dirs.data_dir().to_string_lossy());
let vault = vault::launch(&dirs.data_dir().join("vault.db"))?;
match args.command.unwrap_or_default() {
Command::Run => run(&vault).await?,
Command::Export { room, file } => export::export(&vault, room, &file).await?,
}
vault.close().await;
println!("Goodbye!");
Ok(())
}
async fn run(vault: &Vault) -> anyhow::Result<()> {
let (logger, logger_rx) = Logger::init(log::Level::Debug); let (logger, logger_rx) = Logger::init(log::Level::Debug);
info!( info!(
"Welcome to {} {}", "Welcome to {} {}",
@ -25,18 +69,10 @@ async fn main() -> anyhow::Result<()> {
env!("CARGO_PKG_VERSION") env!("CARGO_PKG_VERSION")
); );
let dirs = ProjectDirs::from("de", "plugh", "cove").expect("unable to determine directories");
println!("Data dir: {}", dirs.data_dir().to_string_lossy());
let vault = vault::launch(&dirs.data_dir().join("vault.db"))?;
let mut terminal = Terminal::new()?; let mut terminal = Terminal::new()?;
// terminal.set_measuring(true); // terminal.set_measuring(true);
Ui::run(&mut terminal, vault.clone(), logger, logger_rx).await?; Ui::run(&mut terminal, vault.clone(), logger, logger_rx).await?;
drop(terminal); // So the vault can print again drop(terminal); // So the vault can print again
vault.close().await;
println!("Goodbye!");
Ok(()) Ok(())
} }

View file

@ -41,11 +41,11 @@ impl FromSql for Time {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EuphMsg { pub struct EuphMsg {
id: Snowflake, pub id: Snowflake,
parent: Option<Snowflake>, pub parent: Option<Snowflake>,
time: Time, pub time: Time,
nick: String, pub nick: String,
content: String, pub content: String,
} }
impl Msg for EuphMsg { impl Msg for EuphMsg {