Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
use clap::{ArgAction, Args, Parser, Subcommand};

fn parse_verbosity(value: &str) -> Result<u8, String> {
match value {
"0" => Ok(0),
"1" => Ok(1),
"2" | "v" => Ok(2),
_ => Err("verbosity must be 0, 1, or 2/v".into()),
}
}

/// GitHub stacked-PR builder for those who miss Gerrit
///
/// Main features:
Expand Down Expand Up @@ -77,9 +86,17 @@ pub struct Globals {
/// which have the potential to mutate remote state are skipped and printed.
#[arg(short = '#', long, global = true)]
pub dry_run: bool,
/// Output all commands executed, and their stdout/stderr.
#[arg(short, long, global = true)]
pub verbose: bool,
/// Output commands executed. Repeat for command output as well.
#[arg(
short,
long,
global = true,
value_parser = parse_verbosity,
num_args = 0..=1,
default_missing_value = "1",
default_value = "0"
)]
pub verbose: u8,
}

#[derive(Subcommand)]
Expand Down
4 changes: 2 additions & 2 deletions src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,12 @@ impl Env {
self.cli.globals.dry_run
}

pub fn verbose(&self) -> bool {
pub fn verbosity(&self) -> u8 {
self.cli.globals.verbose
}

pub fn always_echo(&self) -> bool {
self.dry_run() || self.verbose()
self.dry_run() || self.verbosity() > 0
}

pub fn next_exec_id(&self) -> usize {
Expand Down
50 changes: 43 additions & 7 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,37 @@ use anyhow::{Context, Result, bail};
use std::fmt::Debug;
use std::process::{Command, Output};

const MAX_VERBOSE_LINE_BYTES: usize = 100;

fn truncate_line(line: &str) -> String {
if line.len() <= MAX_VERBOSE_LINE_BYTES {
return line.to_owned();
}
let marker = "[...]";
let prefix_bytes = (MAX_VERBOSE_LINE_BYTES - marker.len()) / 2;
let suffix_bytes = MAX_VERBOSE_LINE_BYTES - marker.len() - prefix_bytes;
let mut prefix_end = prefix_bytes;
while !line.is_char_boundary(prefix_end) {
prefix_end -= 1;
}
let mut suffix_start = line.len() - suffix_bytes;
while !line.is_char_boundary(suffix_start) {
suffix_start += 1;
}
format!("{}{}{}", &line[..prefix_end], marker, &line[suffix_start..])
}

fn print_output(prefix: &str, output: &[u8], truncate: bool) {
for line in String::from_utf8_lossy(output).lines() {
let line = if truncate {
truncate_line(line)
} else {
line.to_owned()
};
eprintln!("{prefix}{line}");
}
}

pub fn exec_impl(env: &crate::env::Env, cmd: &mut Command) -> Result<Output> {
let id = env.next_exec_id();
if env.always_echo() {
Expand All @@ -13,13 +44,18 @@ pub fn exec_impl(env: &crate::env::Env, cmd: &mut Command) -> Result<Output> {
let output = cmd
.output()
.with_context(|| format!("exec-failed: {:?}", cmd))?;
if env.always_echo() || !output.status.success() {
for line in String::from_utf8_lossy(output.stdout.as_ref()).lines() {
eprintln!("exec-{}-stdout: {}", id, line);
}
for line in String::from_utf8_lossy(output.stderr.as_ref()).lines() {
eprintln!("exec-{}-stderr: {}", id, line);
}
if env.dry_run() || env.verbosity() > 0 || !output.status.success() {
let truncate = env.verbosity() == 1;
print_output(
&format!("exec-{id}-stdout: "),
output.stdout.as_ref(),
truncate,
);
print_output(
&format!("exec-{id}-stderr: "),
output.stderr.as_ref(),
truncate,
);
}
if !output.status.success() {
bail!("exec-{}-status-non-zero: {:?}", id, output.status);
Expand Down
Loading