Create debug command, pretty print formatted tables

This commit is contained in:
timvisee 2018-05-17 16:29:51 +02:00
parent db1087c9e9
commit 7425c6c405
No known key found for this signature in database
GPG key ID: 109CBA0BF74036C2
13 changed files with 192 additions and 24 deletions

View file

@ -3,7 +3,6 @@ The first release used for gathering feedback on the application by selected
people.
Features:
- A `defaults` command to list defaults such as the host URL and history file
- Make use of stdout and stderr consistent
- Allow file/directory archiving on upload
- Allow unarchiving on download
@ -17,6 +16,7 @@ Features:
- Gentoo portage package
- Arch AUR package
- Windows, macOS and Redox support
- Allow empty owner token for info command
- Check and validate all errors, are some too verbose?
# Beta release 0.1 (public)

68
cli/src/action/debug.rs Normal file
View file

@ -0,0 +1,68 @@
use chrono::Duration;
use clap::ArgMatches;
use ffsend_api::config::{SEND_DEFAULT_EXPIRE_TIME, SEND_DEFAULT_HOST};
use prettytable::{
cell::Cell,
format::FormatBuilder,
row::Row,
Table,
};
use cmd::matcher::{
debug::DebugMatcher,
main::MainMatcher,
Matcher,
};
use error::ActionError;
#[cfg(feature = "history")]
use history_tool;
use util::{ensure_owner_token, format_duration, print_success};
/// A file debug action.
pub struct Debug<'a> {
cmd_matches: &'a ArgMatches<'a>,
}
impl<'a> Debug<'a> {
/// Construct a new debug action.
pub fn new(cmd_matches: &'a ArgMatches<'a>) -> Self {
Self {
cmd_matches,
}
}
/// Invoke the debug action.
// TODO: create a trait for this method
pub fn invoke(&self) -> Result<(), ActionError> {
// Create the command matchers
let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
let matcher_debug = DebugMatcher::with(self.cmd_matches).unwrap();
// Create a table for all debug information
let mut table = Table::new();
table.set_format(FormatBuilder::new().padding(0, 2).build());
// The default host
table.add_row(Row::new(vec![
Cell::new("host:"),
Cell::new(SEND_DEFAULT_HOST),
]));
// The history file
table.add_row(Row::new(vec![
Cell::new("history file:"),
Cell::new(matcher_main.history().to_str().unwrap_or("?")),
]));
// The default host
table.add_row(Row::new(vec![
Cell::new("default expiry:"),
Cell::new(&format_duration(Duration::seconds(SEND_DEFAULT_EXPIRE_TIME))),
]));
// Print the table
table.printstd();
Ok(())
}
}

View file

@ -1,7 +1,5 @@
extern crate prettytable;
use clap::ArgMatches;
use self::prettytable::{
use prettytable::{
cell::Cell,
format::FormatBuilder,
row::Row,

View file

@ -15,6 +15,13 @@ use ffsend_api::file::remote_file::{
RemoteFile,
};
use ffsend_api::reqwest::Client;
use prettytable::{
cell::Cell,
format::FormatBuilder,
row::Row,
Table,
};
use cmd::matcher::{
Matcher,
@ -101,25 +108,62 @@ impl<'a> Info<'a> {
#[cfg(feature = "history")]
history_tool::add(&matcher_main, file.clone(), true);
// Print all file details
println!("ID: {}", file.id());
// Create a new table for the information
let mut table = Table::new();
table.set_format(FormatBuilder::new().padding(0, 2).build());
// Add the ID
table.add_row(Row::new(vec![
Cell::new("ID:"),
Cell::new(file.id()),
]));
// Metadata related details
if let Some(metadata) = metadata {
// The file name
table.add_row(Row::new(vec![
Cell::new("name:"),
Cell::new(metadata.metadata().name()),
]));
// The file size
let size = metadata.size();
println!("Name: {}", metadata.metadata().name());
if size >= 1024 {
println!("Size: {} ({} B)", format_bytes(size), size);
} else {
println!("Size: {}", format_bytes(size));
}
println!("MIME: {}", metadata.metadata().mime());
}
println!("Downloads: {} of {}", info.download_count(), info.download_limit());
if ttl_millis >= 60 * 1000 {
println!("Expiry: {} ({}s)", format_duration(&ttl), ttl.num_seconds());
} else {
println!("Expiry: {}", format_duration(&ttl));
table.add_row(Row::new(vec![
Cell::new("MIME:"),
Cell::new(
&if size >= 1024 {
format!("{} ({} B)", format_bytes(size), size)
} else {
format_bytes(size)
}
),
]));
// The file MIME
table.add_row(Row::new(vec![
Cell::new("MIME:"),
Cell::new(metadata.metadata().mime()),
]));
}
// The download count
table.add_row(Row::new(vec![
Cell::new("downloads:"),
Cell::new(&format!("{} of {}", info.download_count(), info.download_limit())),
]));
// The time to live
table.add_row(Row::new(vec![
Cell::new("expiry:"),
Cell::new(
&if ttl_millis >= 60 * 1000 {
format!("{} ({}s)", format_duration(&ttl), ttl.num_seconds())
} else {
format_duration(&ttl)
}
),
]));
Ok(())
}
}

View file

@ -1,3 +1,4 @@
pub mod debug;
pub mod delete;
pub mod download;
pub mod exists;

View file

@ -7,8 +7,8 @@ use ffsend_api::file::remote_file::RemoteFile;
use ffsend_api::reqwest::Client;
use cmd::matcher::{
Matcher,
main::MainMatcher,
Matcher,
password::PasswordMatcher,
};
use error::ActionError;

View file

@ -3,6 +3,7 @@ extern crate directories;
use clap::{App, AppSettings, Arg, ArgMatches};
use super::matcher::{
DebugMatcher,
DeleteMatcher,
DownloadMatcher,
ExistsMatcher,
@ -15,6 +16,7 @@ use super::matcher::{
#[cfg(feature = "history")]
use super::matcher::HistoryMatcher;
use super::subcmd::{
CmdDebug,
CmdDelete,
CmdDownload,
CmdExists,
@ -73,6 +75,7 @@ impl<'a: 'b, 'b> Handler<'a> {
.alias("assume-yes")
.global(true)
.help("Assume yes for prompts"))
.subcommand(CmdDebug::build())
.subcommand(CmdDelete::build())
.subcommand(CmdDownload::build().display_order(2))
.subcommand(CmdExists::build())
@ -88,8 +91,9 @@ impl<'a: 'b, 'b> Handler<'a> {
.short("H")
.value_name("FILE")
.global(true)
.help("History file to use")
.default_value(&DEFAULT_HISTORY_FILE))
.help("Use the specified history file")
.default_value(&DEFAULT_HISTORY_FILE)
.hide_default_value(true))
.arg(Arg::with_name("incognito")
.long("incognito")
.short("i")
@ -120,6 +124,11 @@ impl<'a: 'b, 'b> Handler<'a> {
&self.matches
}
/// Get the debug sub command, if matched.
pub fn debug(&'a self) -> Option<DebugMatcher> {
DebugMatcher::with(&self.matches)
}
/// Get the delete sub command, if matched.
pub fn delete(&'a self) -> Option<DeleteMatcher> {
DeleteMatcher::with(&self.matches)

View file

@ -0,0 +1,22 @@
use ffsend_api::url::Url;
use clap::ArgMatches;
use cmd::arg::{ArgOwner, ArgPassword, ArgUrl, CmdArgOption};
use super::Matcher;
/// The debug command matcher.
pub struct DebugMatcher<'a> {
matches: &'a ArgMatches<'a>,
}
impl<'a> Matcher<'a> for DebugMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
matches.subcommand_matches("debug")
.map(|matches|
DebugMatcher {
matches,
}
)
}
}

View file

@ -1,3 +1,4 @@
pub mod debug;
pub mod delete;
pub mod download;
pub mod exists;
@ -10,6 +11,7 @@ pub mod password;
pub mod upload;
// Reexport to matcher module
pub use self::debug::DebugMatcher;
pub use self::delete::DeleteMatcher;
pub use self::download::DownloadMatcher;
pub use self::exists::ExistsMatcher;

View file

@ -0,0 +1,14 @@
use clap::{App, SubCommand};
use cmd::arg::{ArgOwner, ArgPassword, ArgUrl, CmdArg};
/// The debug command definition.
pub struct CmdDebug;
impl CmdDebug {
pub fn build<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("debug")
.about("View debug information")
.visible_alias("dbg")
}
}

View file

@ -1,3 +1,4 @@
pub mod debug;
pub mod delete;
pub mod download;
pub mod exists;
@ -9,6 +10,7 @@ pub mod password;
pub mod upload;
// Reexport to cmd module
pub use self::debug::CmdDebug;
pub use self::delete::CmdDelete;
pub use self::download::CmdDownload;
pub use self::exists::CmdExists;

View file

@ -10,6 +10,7 @@ extern crate ffsend_api;
#[cfg(feature = "history")]
#[macro_use]
extern crate lazy_static;
extern crate prettytable;
extern crate rpassword;
extern crate serde;
#[cfg(feature = "history")]
@ -29,6 +30,7 @@ mod host;
mod progress;
mod util;
use action::debug::Debug;
use action::delete::Delete;
use action::download::Download;
use action::exists::Exists;
@ -58,6 +60,12 @@ fn main() {
/// If no proper action is selected, the program will quit with an error
/// message.
fn invoke_action(handler: &Handler) -> Result<(), Error> {
// Match the debug command
if handler.debug().is_some() {
return Debug::new(handler.matches()).invoke()
.map_err(|err| err.into());
}
// Match the delete command
if handler.delete().is_some() {
return Delete::new(handler.matches()).invoke()

View file

@ -530,9 +530,9 @@ pub fn format_bytes(bytes: u64) -> String {
/// - `9m55s`
/// - `1s`
/// - `now`
pub fn format_duration(duration: &Duration) -> String {
pub fn format_duration(duration: impl Borrow<Duration>) -> String {
// Get the total number of seconds, return immediately if zero or less
let mut secs = duration.num_seconds();
let mut secs = duration.borrow().num_seconds();
if secs <= 0 {
return "now".into();
}