replace prettytable by comfy-table

This commit is contained in:
jmarkin 2022-11-12 00:23:19 +03:00 committed by Alexander Weiss
parent cfb7a23aad
commit f59a6fef84
7 changed files with 272 additions and 127 deletions

180
Cargo.lock generated
View File

@ -300,13 +300,25 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "comfy-table"
version = "6.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1090f39f45786ec6dc6286f8ea9c75d0a7ef0a0d3cda674cef0c3af7b307fbc2"
dependencies = [
"crossterm",
"strum",
"strum_macros",
"unicode-width",
]
[[package]]
name = "console"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c"
dependencies = [
"encode_unicode 0.3.6",
"encode_unicode",
"lazy_static",
"libc",
"terminal_size",
@ -411,6 +423,31 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossterm"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
dependencies = [
"bitflags",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
dependencies = [
"winapi",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -573,16 +610,6 @@ dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
@ -594,17 +621,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "either"
version = "1.8.0"
@ -617,12 +633,6 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "encoding_rs"
version = "0.8.31"
@ -1150,6 +1160,16 @@ dependencies = [
"cc",
]
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
@ -1316,6 +1336,29 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys 0.42.0",
]
[[package]]
name = "path-dedot"
version = "3.0.18"
@ -1375,19 +1418,6 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "prettytable-rs"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f375cb74c23b51d23937ffdeb48b1fbf5b6409d4b9979c1418c1de58bc8f801"
dependencies = [
"atty",
"encode_unicode 1.0.0",
"lazy_static",
"term",
"unicode-width",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@ -1676,6 +1706,7 @@ dependencies = [
"chrono",
"clap",
"clap_complete",
"comfy-table",
"crossbeam-channel",
"derivative",
"derive-getters",
@ -1698,7 +1729,6 @@ dependencies = [
"nix",
"pariter",
"path-dedot",
"prettytable-rs",
"quickcheck",
"quickcheck_macros",
"rand",
@ -1926,6 +1956,36 @@ dependencies = [
"digest",
]
[[package]]
name = "signal-hook"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "simplelog"
version = "0.12.0"
@ -1946,6 +2006,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "socket2"
version = "0.4.7"
@ -1968,6 +2034,25 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
[[package]]
name = "strum_macros"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "subtle"
version = "2.4.1"
@ -2010,17 +2095,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.3"

View File

@ -77,7 +77,6 @@ toml = "0.5"
merge = "0.1"
serde_with = "2.1"
rpassword = "7"
prettytable-rs = {version = "0.9", default-features = false }
bytesize = "1"
indicatif = "0.17"
path-dedot = "3"
@ -86,6 +85,7 @@ humantime = "2"
users = "0.11"
itertools = "0.10"
simplelog = "0.12"
comfy-table = "6.1.2"
[dev-dependencies]
rstest = "0.15"

View File

@ -5,11 +5,10 @@ use chrono::{DateTime, Datelike, Duration, Local, Timelike};
use clap::{AppSettings, Parser};
use derivative::Derivative;
use merge::Merge;
use prettytable::{format, row, Table};
use serde::Deserialize;
use serde_with::{serde_as, DisplayFromStr};
use super::{progress_counter, prune, RusticConfig};
use super::{progress_counter, prune, table_with_titles, RusticConfig};
use crate::backend::{Cache, DecryptFullBackend, FileType};
use crate::repo::{
ConfigFile, SnapshotFile, SnapshotFilter, SnapshotGroup, SnapshotGroupCriterion, StringList,
@ -89,7 +88,8 @@ pub(super) fn execute(
snapshots.sort_unstable_by(|sn1, sn2| sn1.cmp(sn2).reverse());
let latest_time = snapshots[0].time;
let mut group_keep = opts.config.keep.clone();
let mut table = Table::new();
let mut table =
table_with_titles(["ID", "Time", "Host", "Tags", "Paths", "Action", "Reason"]);
let mut iter = snapshots.iter().peekable();
let mut last = None;
@ -123,18 +123,22 @@ pub(super) fn execute(
let tags = sn.tags.formatln();
let paths = sn.paths.formatln();
let time = sn.time.format("%Y-%m-%d %H:%M:%S");
table.add_row(row![sn.id, time, sn.hostname, tags, paths, action, reason]);
let time = sn.time.format("%Y-%m-%d %H:%M:%S").to_string();
table.add_row([
sn.id.to_string(),
time,
sn.hostname.to_string(),
tags,
paths,
action.to_string(),
reason,
]);
last = Some(sn);
}
table.set_titles(
row![b->"ID", b->"Time", b->"Host", b->"Tags", b->"Paths", b->"Action", br->"Reason"],
);
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
println!();
table.printstd();
println!("{table}");
println!();
}
@ -290,7 +294,7 @@ impl KeepOptions {
latest_time: DateTime<Local>,
) -> Option<String> {
let mut keep = false;
let mut reason = String::new();
let mut reason = Vec::new();
if self
.keep_ids
@ -298,12 +302,12 @@ impl KeepOptions {
.any(|id| sn.id.to_hex().starts_with(id))
{
keep = true;
reason.push_str("id\n");
reason.push("id");
}
if !self.keep_tags.is_empty() && sn.tags.matches(&self.keep_tags) {
keep = true;
reason.push_str("tags\n");
reason.push("tags");
}
let keep_checks = [
@ -356,17 +360,15 @@ impl KeepOptions {
if *counter > 0 {
*counter -= 1;
keep = true;
reason.push_str(reason1);
reason.push('\n');
reason.push(reason1);
}
if sn.time + Duration::from_std(*within).unwrap() > latest_time {
keep = true;
reason.push_str(reason2);
reason.push('\n');
reason.push(reason2);
}
}
}
keep.then_some(reason)
keep.then_some(reason.join("\n"))
}
}

View File

@ -6,6 +6,9 @@ use std::time::Duration;
use anyhow::{bail, Result};
use bytesize::ByteSize;
use comfy_table::{
presets::ASCII_MARKDOWN, Attribute, Cell, CellAlignment, ContentArrangement, Table,
};
use indicatif::HumanDuration;
use indicatif::{ProgressBar, ProgressState, ProgressStyle};
use log::*;
@ -149,3 +152,34 @@ pub fn wait(d: Option<humantime::Duration>) {
p.finish();
}
}
// Helpers for table output
pub fn bold_cell<T: ToString>(s: T) -> Cell {
Cell::new(s).add_attribute(Attribute::Bold)
}
pub fn table() -> Table {
let mut table = Table::new();
table
.load_preset(ASCII_MARKDOWN)
.set_content_arrangement(ContentArrangement::Dynamic);
table
}
pub fn table_with_titles<I: IntoIterator<Item = T>, T: ToString>(titles: I) -> Table {
let mut table = table();
table.set_header(titles.into_iter().map(bold_cell));
table
}
pub fn table_right_from<I: IntoIterator<Item = T>, T: ToString>(start: usize, titles: I) -> Table {
let mut table = table_with_titles(titles);
// set alignment of all rows except first start row
table
.column_iter_mut()
.skip(start)
.for_each(|c| c.set_cell_alignment(CellAlignment::Right));
table
}

View File

@ -2,9 +2,8 @@ use anyhow::Result;
use clap::Parser;
use derive_more::Add;
use log::*;
use prettytable::{format, row, Table};
use super::{bytes, progress_counter};
use super::{bytes, progress_counter, table_right_from};
use crate::backend::{DecryptReadBackend, ReadBackend, ALL_FILE_TYPES};
use crate::blob::{BlobType, BlobTypeMap, Sum};
use crate::index::IndexEntry;
@ -75,33 +74,56 @@ pub(super) fn execute(
}
p.finish_with_message("done");
let mut table = Table::new();
let mut table = table_right_from(
1,
["Blob type", "Count", "Total Size", "Total Size in Packs"],
);
for (blob_type, info) in &info {
table.add_row(row![format!("{blob_type:?}"),r->info.count,r->bytes(info.data_size), r->bytes(info.size) ]);
table.add_row([
format!("{blob_type:?}"),
info.count.to_string(),
bytes(info.data_size),
bytes(info.size),
]);
}
for (blob_type, info_delete) in &info_delete {
if info_delete.count > 0 {
table.add_row(row![format!("{blob_type:?} to delete"),r->info_delete.count,r->bytes(info_delete.data_size),r->bytes(info_delete.size)]);
table.add_row([
format!("{blob_type:?} to delete"),
info_delete.count.to_string(),
bytes(info_delete.data_size),
bytes(info_delete.size),
]);
}
}
let total = info.sum() + info_delete.sum();
table.add_row(row!["Total",r->total.count,r->bytes(total.data_size),r->bytes(total.size)]);
table.add_row([
"Total".to_string(),
total.count.to_string(),
bytes(total.data_size),
bytes(total.size),
]);
table.set_titles(row![b->"Blob type", br->"Count", br->"Total Size",br->"Total Size in Packs"]);
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
println!();
table.printstd();
println!("{table}");
let mut table = table_right_from(
1,
["Blob type", "Pack Count", "Minimum Size", "Maximum Size"],
);
let mut table = Table::new();
for (blob_type, info) in info {
table.add_row(row![format!("{blob_type:?} packs"), r->info.pack_count, r->bytes(info.min_pack_size), r->bytes(info.max_pack_size)]);
table.add_row([
format!("{blob_type:?} packs"),
info.pack_count.to_string(),
bytes(info.min_pack_size),
bytes(info.max_pack_size),
]);
}
table.set_titles(row![b->"Blob type", br->"Pack Count", br->"Minimum Size",br->"Maximum Size"]);
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
println!();
table.printstd();
println!("{table}");
Ok(())
}
@ -109,24 +131,26 @@ pub(super) fn execute(
fn fileinfo(text: &str, be: &impl ReadBackend) -> Result<()> {
info!("scanning files...");
let mut table = Table::new();
let mut table = table_right_from(1, ["File type", "Count", "Total Size"]);
let mut total_count = 0;
let mut total_size = 0;
for tpe in ALL_FILE_TYPES {
let list = be.list_with_size(tpe)?;
let count = list.len();
let size = list.iter().map(|f| f.1 as u64).sum();
table.add_row(row![format!("{:?}", tpe), r->count, r->bytes(size)]);
table.add_row([format!("{:?}", tpe), count.to_string(), bytes(size)]);
total_count += count;
total_size += size;
}
println!("{}", text);
table.add_row(row!["Total",r->total_count,r->bytes(total_size)]);
table.add_row([
"Total".to_string(),
total_count.to_string(),
bytes(total_size),
]);
table.set_titles(row![b->"File type", br->"Count", br->"Total Size"]);
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
println!();
table.printstd();
println!("{table}");
println!();
Ok(())
}

View File

@ -2,11 +2,11 @@ use std::time::Duration;
use anyhow::Result;
use clap::Parser;
use comfy_table::Cell;
use humantime::format_duration;
use itertools::Itertools;
use prettytable::{format, row, Table};
use super::{bytes, RusticConfig};
use super::{bold_cell, bytes, table, table_right_from, RusticConfig};
use crate::backend::DecryptReadBackend;
use crate::repo::{
DeleteOption, SnapshotFile, SnapshotFilter, SnapshotGroup, SnapshotGroupCriterion,
@ -104,21 +104,34 @@ pub(super) fn execute(
0 => format!("{}", sn.id),
count => format!("{} (+{})", sn.id, count),
};
row![id, time, sn.hostname, tags, paths, r->files, r->dirs, r->size]
[
id,
time.to_string(),
sn.hostname,
tags,
paths,
files,
dirs,
size,
]
};
let mut table: Table = snapshots
let mut table = table_right_from(
5,
[
"ID", "Time", "Host", "Tags", "Paths", "Files", "Dirs", "Size",
],
);
let snapshots: Vec<_> = snapshots
.into_iter()
.group_by(|sn| if opts.all { sn.id } else { sn.tree })
.into_iter()
.map(|(_, mut g)| (g.next().unwrap(), g.count()))
.map(snap_to_table)
.collect();
table.set_titles(
row![b->"ID", b->"Time", b->"Host", b->"Tags", b->"Paths", br->"Files",br->"Dirs", br->"Size"],
);
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
table.printstd();
table.add_rows(snapshots);
println!("{table}");
}
println!("{} snapshot(s)", count);
}
@ -127,31 +140,35 @@ pub(super) fn execute(
}
fn display_snap(sn: SnapshotFile) {
let mut table = Table::new();
let mut table = table();
table.add_row(row![b->"Snapshot", b->sn.id.to_hex()]);
let mut add_entry = |title: &str, value: String| {
table.add_row([bold_cell(title), Cell::new(value)]);
};
add_entry("Snapshot", sn.id.to_hex());
// note that if original was not set, it is set to sn.id by the load process
if sn.original != Some(sn.id) {
table.add_row(row![b->"Original ID", sn.original.unwrap().to_hex()]);
add_entry("Original ID", sn.original.unwrap().to_hex());
}
table.add_row(row![b->"Time", sn.time.format("%Y-%m-%d %H:%M:%S")]);
table.add_row(row![b->"Host", sn.hostname]);
table.add_row(row![b->"Tags", sn.tags.formatln()]);
add_entry("Time", sn.time.format("%Y-%m-%d %H:%M:%S").to_string());
add_entry("Host", sn.hostname);
add_entry("Tags", sn.tags.formatln());
let delete = match sn.delete {
DeleteOption::NotSet => "not set".to_string(),
DeleteOption::Never => "never".to_string(),
DeleteOption::After(t) => format!("after {}", t.format("%Y-%m-%d %H:%M:%S")),
};
table.add_row(row![b->"Delete", delete]);
table.add_row(row![b->"Paths", sn.paths.formatln()]);
add_entry("Delete", delete);
add_entry("Paths", sn.paths.formatln());
let parent = match sn.parent {
None => "no parent snapshot".to_string(),
Some(p) => p.to_hex(),
};
table.add_row(row![b->"Parent", parent]);
add_entry("Parent", parent);
if let Some(summary) = sn.summary {
table.add_row(row![]);
table.add_row(row![b->"Command", summary.command]);
add_entry("", "".to_string());
add_entry("Command", summary.command);
let source = format!(
"files: {} / dirs: {} / size: {}",
@ -159,23 +176,21 @@ fn display_snap(sn: SnapshotFile) {
summary.total_dirs_processed,
bytes(summary.total_bytes_processed)
);
table.add_row(row![b->"Source", source]);
table.add_row(row![]);
add_entry("Source", source);
add_entry("", "".to_string());
let files = format!(
"new: {:>10} / changed: {:>10} / unchanged: {:>10}",
summary.files_new, summary.files_changed, summary.files_unmodified,
);
table.add_row(row![b->"Files", files]);
add_entry("Files", files);
let trees = format!(
"new: {:>10} / changed: {:>10} / unchanged: {:>10}",
summary.dirs_new, summary.dirs_changed, summary.dirs_unmodified,
);
table.add_row(row![b->"Dirs", trees]);
table.add_row(row![]);
add_entry("Dirs", trees);
add_entry("", "".to_string());
let written = format!(
"data: {:>10} blobs / raw: {:>10} / packed: {:>10}\n\
@ -191,7 +206,7 @@ fn display_snap(sn: SnapshotFile) {
bytes(summary.data_added),
bytes(summary.data_added_packed),
);
table.add_row(row![b->"Added to repo", written]);
add_entry("Added to repo", written);
let duration = format!(
"backup start: {} / backup end: {} / backup duration: {}\n\
@ -201,9 +216,8 @@ fn display_snap(sn: SnapshotFile) {
format_duration(Duration::from_secs_f64(summary.backup_duration)),
format_duration(Duration::from_secs_f64(summary.total_duration))
);
table.add_row(row![b->"Duration", duration]);
add_entry("Duration", duration);
}
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
table.printstd();
println!("{table}");
println!();
}

View File

@ -451,9 +451,6 @@ impl StringList {
}
pub fn formatln(&self) -> String {
self.0
.iter()
.map(|p| p.to_string() + "\n")
.collect::<String>()
self.0.join("\n")
}
}