improve CLI and refactors
This commit is contained in:
parent
b14a7a50fd
commit
2acbb468ad
|
@ -2,7 +2,7 @@ use std::{fs, rc::Rc, thread};
|
|||
|
||||
use crossbeam_channel::{bounded, Sender};
|
||||
use libupgr::config::{Config, Step};
|
||||
use miette::{miette, WrapErr, IntoDiagnostic};
|
||||
use miette::{miette, IntoDiagnostic, WrapErr};
|
||||
use mlua::Lua;
|
||||
|
||||
pub fn start() -> miette::Result<LuaActorHandle> {
|
||||
|
|
|
@ -48,7 +48,7 @@ pub enum IntoCommandError {
|
|||
}
|
||||
|
||||
impl CfgCommand {
|
||||
pub fn into_command(self, shell: &[String]) -> Result<Command, IntoCommandError> {
|
||||
pub fn as_command(&self, shell: &[String]) -> Result<Command, IntoCommandError> {
|
||||
match self {
|
||||
Self::Args(args) => {
|
||||
let mut cmd = Command::new(args.first().ok_or(IntoCommandError::EmptyArgs)?);
|
||||
|
|
|
@ -8,10 +8,12 @@ edition = "2018"
|
|||
[dependencies]
|
||||
clap = { version = "3.1.6", features = ["derive"] }
|
||||
dirs = "4.0.0"
|
||||
humantime = "2.1.0"
|
||||
libupgr = { path = "../libupgr" }
|
||||
miette = { version = "4.2.1", features = ["fancy"] }
|
||||
mlua = { version = "0.7.4", features = ["luajit", "serialize"] }
|
||||
owo-colors = "3.3.0"
|
||||
sled = "0.34.7"
|
||||
terminal_size = "0.1.17"
|
||||
|
||||
[features]
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
pub struct InfoTable {
|
||||
rows: Vec<(String, String, usize)>,
|
||||
title: String,
|
||||
width: usize,
|
||||
}
|
||||
|
||||
impl InfoTable {
|
||||
pub fn new_with_title<T>(title: T) -> Self
|
||||
where
|
||||
T: ToString,
|
||||
{
|
||||
let title = title.to_string();
|
||||
|
||||
Self {
|
||||
rows: Vec::new(),
|
||||
width: title.len(),
|
||||
title,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row<K, V>(&mut self, key: K, value: &V) -> &mut Self
|
||||
where
|
||||
K: ToString,
|
||||
V: ToInfoData,
|
||||
{
|
||||
let key = key.to_string();
|
||||
let info_data = value.to_info_data();
|
||||
|
||||
let width = value
|
||||
.info_width()
|
||||
.or_else(|| info_data.lines().map(|s| s.len()).max())
|
||||
.unwrap_or(0) +
|
||||
key.len();
|
||||
|
||||
self.rows.push((key, info_data, width));
|
||||
|
||||
if self.width < width {
|
||||
self.width = width;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for InfoTable {
|
||||
fn to_string(&self) -> String {
|
||||
let mut buf = String::new();
|
||||
buf += &format!("┏{:━<width$}┓\n", &self.title, width = self.width + 4);
|
||||
|
||||
for &(ref key, ref val, width) in &self.rows {
|
||||
buf += &format!(
|
||||
"┃ {}: {}{: <width$} ┃\n",
|
||||
key,
|
||||
val,
|
||||
"",
|
||||
width = self.width - width,
|
||||
);
|
||||
}
|
||||
|
||||
buf += &format!("┗{:━<width$}┛", "", width = self.width + 4);
|
||||
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait semilar to ToString, but exclusively used by InfoTable. This is an
|
||||
/// additional trait so coloring can be supported easily
|
||||
pub trait ToInfoData {
|
||||
/// The String that should be displayed in the data column of the InfoTable.
|
||||
fn to_info_data(&self) -> String;
|
||||
|
||||
/// The width that the data returned by to_info_data has. Useful when using
|
||||
/// colors. If this returns None, the len of to_info_data is used.
|
||||
fn info_width(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl ToInfoData for &str {
|
||||
fn to_info_data(&self) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToInfoData for bool {
|
||||
fn to_info_data(&self) -> String {
|
||||
if *self {
|
||||
"yes".green().to_string()
|
||||
} else {
|
||||
"no".red().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn info_width(&self) -> Option<usize> {
|
||||
Some(if *self { 3 } else { 2 })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToInfoData for Duration {
|
||||
fn to_info_data(&self) -> String {
|
||||
format!("{}ms", self.as_millis().purple())
|
||||
}
|
||||
|
||||
fn info_width(&self) -> Option<usize> {
|
||||
Some(self.as_millis().to_string().len() + 2)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CustomTableData<'a>(pub &'a str, pub usize);
|
||||
|
||||
impl<'a> ToInfoData for CustomTableData<'a> {
|
||||
fn to_info_data(&self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
|
||||
fn info_width(&self) -> Option<usize> {
|
||||
Some(self.1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn info_table_simple() {
|
||||
let s = InfoTable::new_with_title("Test Title")
|
||||
.row("Test Key", &"Test Value")
|
||||
.to_string();
|
||||
|
||||
assert_eq!(
|
||||
s,
|
||||
"\
|
||||
┏Test Title━━━━━━━━━━━━┓
|
||||
┃ Test Key: Test Value ┃
|
||||
┗━━━━━━━━━━━━━━━━━━━━━━┛",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn info_table_multi() {
|
||||
let s = InfoTable::new_with_title("Test Title")
|
||||
.row("Test Key", &"Test Value")
|
||||
.row("Test Key 2", &"Test Value 2")
|
||||
.to_string();
|
||||
|
||||
assert_eq!(
|
||||
s,
|
||||
"\
|
||||
┏Test Title━━━━━━━━━━━━━━━━┓
|
||||
┃ Test Key: Test Value ┃
|
||||
┃ Test Key 2: Test Value 2 ┃
|
||||
┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛",
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::info_table::{CustomTableData, InfoTable, ToInfoData};
|
||||
use crate::output::{info_table::InfoTable, FormattedCfgCommand};
|
||||
use clap::Parser;
|
||||
use libupgr::{
|
||||
config::{CfgCommand, Config},
|
||||
|
@ -7,9 +7,16 @@ use libupgr::{
|
|||
use miette::{miette, Context, IntoDiagnostic};
|
||||
use mlua::{prelude::LuaFunction, Lua};
|
||||
use owo_colors::OwoColorize;
|
||||
use std::{fs::File, io::Read, path::PathBuf, rc::Rc, time::Instant};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, Read},
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use terminal_size::terminal_size;
|
||||
|
||||
mod info_table;
|
||||
mod output;
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Opt {
|
||||
|
@ -86,7 +93,7 @@ pub fn exec_steps(conf: Config, uninteractive: bool) -> miette::Result<()> {
|
|||
}
|
||||
|
||||
pub fn exec_cmd(
|
||||
cmd: CfgCommand,
|
||||
cfg_cmd: CfgCommand,
|
||||
shell: &[String],
|
||||
when: Option<LuaFunction>,
|
||||
workdir: Option<PathBuf>,
|
||||
|
@ -98,63 +105,59 @@ pub fn exec_cmd(
|
|||
.wrap_err("Failed to call when function")?;
|
||||
|
||||
if !val {
|
||||
println!("Skipping {}", cmd.to_info_data());
|
||||
println!(
|
||||
"Skipping {}",
|
||||
FormattedCfgCommand::new(&cfg_cmd, Some(shell))
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let mut table = InfoTable::new_with_title("Running");
|
||||
table.row("Command", &cmd);
|
||||
let mut table = InfoTable::with_title("Running");
|
||||
table.row("Command", FormattedCfgCommand::new(&cfg_cmd, Some(shell)));
|
||||
|
||||
let mut cmd = cmd.into_command(shell)?;
|
||||
let mut cmd = cfg_cmd.as_command(shell)?;
|
||||
if let Some(workdir) = workdir {
|
||||
table.row("Workdir", &workdir.to_string_lossy().as_ref());
|
||||
cmd.current_dir(workdir);
|
||||
}
|
||||
|
||||
println!("{}", table.to_string());
|
||||
let (terminal_size::Width(width), _) =
|
||||
terminal_size().ok_or_else(|| miette!("Couldn't get terminal size"))?;
|
||||
|
||||
table
|
||||
.write_to(io::stdout(), width as usize)
|
||||
.into_diagnostic()?;
|
||||
|
||||
let start_time = Instant::now();
|
||||
let exit = cmd.spawn().into_diagnostic()?.wait().into_diagnostic()?;
|
||||
let total_time = Instant::now() - start_time;
|
||||
|
||||
let mut data = String::new();
|
||||
println!(
|
||||
"{}",
|
||||
InfoTable::new_with_title("Summary")
|
||||
.row("Time", &total_time)
|
||||
.row(
|
||||
"Exit Code",
|
||||
&exit
|
||||
.code()
|
||||
.map(|s| if s == 0 {
|
||||
data = 0.green().to_string();
|
||||
CustomTableData(&data, 1)
|
||||
} else {
|
||||
let s = s.to_string();
|
||||
data = s.red().to_string();
|
||||
CustomTableData(&data, s.len())
|
||||
})
|
||||
.unwrap_or(CustomTableData("Unknown", 7))
|
||||
InfoTable::with_title("Summary")
|
||||
.row("Command", FormattedCfgCommand::new(&cfg_cmd, Some(shell)))
|
||||
.row(
|
||||
"Time",
|
||||
humantime::format_duration(
|
||||
// only be precise to milliseconds
|
||||
Duration::from_millis(total_time.as_millis() as u64),
|
||||
)
|
||||
.to_string()
|
||||
);
|
||||
.blue(),
|
||||
)
|
||||
.row(
|
||||
"Exit Code",
|
||||
&exit
|
||||
.code()
|
||||
.map(|s| {
|
||||
if s == 0 {
|
||||
0.green().to_string()
|
||||
} else {
|
||||
s.red().to_string()
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| "Unknown".blue().to_string()),
|
||||
)
|
||||
.write_to(io::stdout(), width as usize)
|
||||
.into_diagnostic()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl ToInfoData for CfgCommand {
|
||||
fn to_info_data(&self) -> String {
|
||||
match self {
|
||||
Self::Shell(s) => s.green().to_string(),
|
||||
Self::Args(args) => args.join(" ").green().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn info_width(&self) -> Option<usize> {
|
||||
Some(match self {
|
||||
Self::Shell(s) => s.len(),
|
||||
Self::Args(a) => a.iter().map(String::len).sum::<usize>() + a.len() - 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
use std::io::{self, Write};
|
||||
|
||||
pub struct InfoTable {
|
||||
rows: Vec<(String, String)>,
|
||||
title: String,
|
||||
}
|
||||
|
||||
impl InfoTable {
|
||||
pub fn with_title<T>(title: T) -> Self
|
||||
where
|
||||
T: ToString,
|
||||
{
|
||||
let title = title.to_string();
|
||||
|
||||
Self {
|
||||
rows: Vec::new(),
|
||||
title,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row(&mut self, key: impl ToString, value: impl ToString) -> &mut Self {
|
||||
self.rows.push((key.to_string(), value.to_string()));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn write_to(&self, mut writer: impl Write, width: usize) -> io::Result<()> {
|
||||
writeln!(writer, "━{:━<width$}", &self.title, width = width - 1)?;
|
||||
|
||||
for &(ref key, ref val) in &self.rows {
|
||||
writeln!(writer, "{}: {}", key, val)?;
|
||||
}
|
||||
|
||||
writer.write_all("━".repeat(width).as_bytes())?;
|
||||
writer.write_all(b"\n")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
use std::{
|
||||
cell::Cell,
|
||||
fmt::{Display, Write},
|
||||
};
|
||||
|
||||
use libupgr::config::CfgCommand;
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
pub mod info_table;
|
||||
|
||||
pub struct FormattedCfgCommand<'a, Shell> {
|
||||
command: &'a CfgCommand,
|
||||
shell: Cell<Option<Shell>>,
|
||||
}
|
||||
|
||||
impl<'a, Shell, S> FormattedCfgCommand<'a, Shell>
|
||||
where
|
||||
Shell: IntoIterator<Item = S>,
|
||||
S: Display,
|
||||
{
|
||||
pub fn new(command: &'a CfgCommand, shell: Option<Shell>) -> Self {
|
||||
Self {
|
||||
command,
|
||||
shell: Cell::new(shell),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Shell, S> Display for FormattedCfgCommand<'a, Shell>
|
||||
where
|
||||
Shell: IntoIterator<Item = S>,
|
||||
S: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.command {
|
||||
CfgCommand::Args(args) => {
|
||||
FormattedEscapedCommand::new(args).cyan().fmt(f)?;
|
||||
},
|
||||
CfgCommand::Shell(cmd) => {
|
||||
if let Some(shell) = self.shell.take() {
|
||||
write!(f, "[{}] ", FormattedEscapedCommand::new(shell).red())?;
|
||||
}
|
||||
|
||||
cmd.cyan().fmt(f)?;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct FormattedEscapedCommand<Shell>(Cell<Option<Shell>>);
|
||||
|
||||
impl<Shell> FormattedEscapedCommand<Shell> {
|
||||
fn new(s: Shell) -> Self {
|
||||
Self(Cell::new(Some(s)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Shell, S> Display for FormattedEscapedCommand<Shell>
|
||||
where
|
||||
Shell: IntoIterator<Item = S>,
|
||||
S: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut first = true;
|
||||
for a in self
|
||||
.0
|
||||
.take()
|
||||
.expect("Attempt to format FormattedEscapedCommand twice")
|
||||
{
|
||||
let a = a.to_string();
|
||||
if !first {
|
||||
f.write_char(' ')?;
|
||||
}
|
||||
first = false;
|
||||
|
||||
if a.contains(' ') {
|
||||
write!(f, r#""{}""#, a)?;
|
||||
} else {
|
||||
f.write_str(&a)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue