Skip to content

Commit

Permalink
Add --quiet, --timings, --colo[u]r, --bell
Browse files Browse the repository at this point in the history
Fixes #144
Fixes #237
Fixes #238
Fixes #278
  • Loading branch information
passcod committed Nov 27, 2023
1 parent 16e606e commit 4facb93
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 31 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 33,7 @@ is-terminal = "0.4.4"
notify-rust = "4.9.0"
serde_json = "1.0.107"
tempfile = "3.8.1"
termcolor = "1.4.0"
tracing = "0.1.40"
which = "5.0.0"

Expand Down
57 changes: 54 additions & 3 deletions crates/cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 7,7 @@ use watchexec_signals::Signal;
const OPTSET_FILTERING: &str = "Filtering";
const OPTSET_COMMAND: &str = "Command";
const OPTSET_DEBUGGING: &str = "Debugging";
const OPTSET_OUTPUT: &str = "Output";

include!(env!("BOSION_PATH"));

Expand Down Expand Up @@ -122,6 123,7 @@ pub struct Args {
#[arg(
short = 'c',
long = "clear",
help_heading = OPTSET_OUTPUT,
num_args = 0..=1,
default_missing_value = "clear",
value_name = "MODE",
Expand Down Expand Up @@ -603,7 605,7 @@ pub struct Args {
/// of the command.
#[arg(
long,
help_heading = OPTSET_COMMAND,
help_heading = OPTSET_OUTPUT,
conflicts_with_all = ["command", "completions", "manual"],
)]
pub only_emit_events: bool,
Expand Down Expand Up @@ -641,9 643,51 @@ pub struct Args {
///
/// With this, Watchexec will emit a desktop notification when a command starts and ends, on
/// supported platforms. On unsupported platforms, it may silently do nothing, or log a warning.
#[arg(long, short = 'N')]
#[arg(
short = 'N',
long,
help_heading = OPTSET_OUTPUT,
)]
pub notify: bool,

/// When to use terminal colours
#[arg(
long,
help_heading = OPTSET_OUTPUT,
default_value = "auto",
value_name = "MODE",
alias = "colour",
)]
pub color: ColourMode,

/// Print how long the command took to run
///
/// This may not be exactly accurate, as it includes some overhead from Watchexec itself. Use
/// the `time` utility, high-precision timers, or benchmarking tools for more accurate results.
#[arg(
long,
help_heading = OPTSET_OUTPUT,
)]
pub timings: bool,

/// Don't print starting and stopping messages
///
/// By default Watchexec will print a message when the command starts and stops. This option
/// disables this behaviour, so only the command's output, warnings, and errors will be printed.
#[arg(
short,
long,
help_heading = OPTSET_OUTPUT,
)]
pub quiet: bool,

/// Ring the terminal bell on command completion
#[arg(
long,
help_heading = OPTSET_OUTPUT,
)]
pub bell: bool,

/// Set the project origin
///
/// Watchexec will attempt to discover the project's "origin" (or "root") by searching for a
Expand Down Expand Up @@ -893,7 937,7 @@ pub enum FsEvent {
Metadata,
}

#[derive(Clone, Copy, Debug, ValueEnum)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
pub enum ShellCompletion {
Bash,
Elvish,
Expand All @@ -903,6 947,13 @@ pub enum ShellCompletion {
Zsh,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
pub enum ColourMode {
Auto,
Always,
Never,
}

#[derive(Clone, Copy, Debug)]
pub struct TimeSpan<const UNITLESS_NANOS_MULTIPLIER: u64 = { 1_000_000_000 }>(pub Duration);

Expand Down
113 changes: 85 additions & 28 deletions crates/cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 4,7 @@ use std::{
env::current_dir,
ffi::{OsStr, OsString},
fs::File,
io::{stderr, IsTerminal, Write},
path::Path,
process::Stdio,
sync::Arc,
Expand All @@ -12,6 13,7 @@ use std::{
use clearscreen::ClearScreen;
use miette::{miette, IntoDiagnostic, Report, Result};
use notify_rust::Notification;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use tokio::{process::Command as TokioCommand, time::sleep};
use tracing::{debug, debug_span, error};
use watchexec::{
Expand All @@ -24,11 26,20 @@ use watchexec::{
use watchexec_events::{Event, Keyboard, ProcessEnd, Tag};
use watchexec_signals::Signal;

use crate::{state::State, emits::events_to_simple_format};
use crate::{
args::{Args, ClearMode, EmitEvents, OnBusyUpdate},
args::{Args, ClearMode, ColourMode, EmitEvents, OnBusyUpdate},
state::RotatingTempFile,
};
use crate::{emits::events_to_simple_format, state::State};

#[derive(Clone, Copy)]
struct OutputFlags {
quiet: bool,
colour: ColorChoice,
timings: bool,
bell: bool,
toast: bool,
}

pub fn make_config(args: &Args, state: &State) -> Result<Config> {
let _span = debug_span!("args-runtime").entered();
Expand Down Expand Up @@ -82,7 93,10 @@ pub fn make_config(args: &Args, state: &State) -> Result<Config> {
if args.only_emit_events {
config.on_action(move |mut action| {
// if we got a terminate or interrupt signal, quit
if action.signals().any(|sig| sig == Signal::Terminate || sig == Signal::Interrupt) {
if action
.signals()
.any(|sig| sig == Signal::Terminate || sig == Signal::Interrupt)
{
action.quit();
return action;
}
Expand All @@ -109,14 123,20 @@ pub fn make_config(args: &Args, state: &State) -> Result<Config> {

match emit_events_to {
EmitEvents::Stdin => {
println!("{}", events_to_simple_format(action.events.as_ref()).unwrap_or_default());
println!(
"{}",
events_to_simple_format(action.events.as_ref()).unwrap_or_default()
);
}
EmitEvents::JsonStdin => {
for event in action.events.iter().filter(|e| !e.is_empty()) {
println!("{}", serde_json::to_string(event).unwrap_or_default());
}
}
other => unreachable!("emit_events_to should have been validated earlier: {:?}", other),
other => unreachable!(
"emit_events_to should have been validated earlier: {:?}",
other
),
}

action
Expand All @@ -132,8 152,19 @@ pub fn make_config(args: &Args, state: &State) -> Result<Config> {
let stop_signal = args.stop_signal;
let stop_timeout = args.stop_timeout.0;

let notif = args.notify;
let print_events = args.print_events;
let outflags = OutputFlags {
quiet: args.quiet,
colour: match args.color {
ColourMode::Auto if !std::io::stdin().is_terminal() => ColorChoice::Never,
ColourMode::Auto => ColorChoice::Auto,
ColourMode::Always => ColorChoice::Always,
ColourMode::Never => ColorChoice::Never,
},
timings: args.timings,
bell: args.bell,
toast: args.notify,
};

let workdir = Arc::new(args.workdir.clone());

Expand Down Expand Up @@ -282,7 313,7 @@ pub fn make_config(args: &Args, state: &State) -> Result<Config> {
setup_process(
innerjob.clone(),
context.command.clone(),
notif,
outflags,
)
});
}
Expand All @@ -295,7 326,7 @@ pub fn make_config(args: &Args, state: &State) -> Result<Config> {
setup_process(
innerjob.clone(),
context.command.clone(),
notif,
outflags,
)
});
}
Expand All @@ -308,7 339,7 @@ pub fn make_config(args: &Args, state: &State) -> Result<Config> {
setup_process(
innerjob.clone(),
context.command.clone(),
notif,
outflags,
)
});
});
Expand All @@ -317,7 348,7 @@ pub fn make_config(args: &Args, state: &State) -> Result<Config> {
} else {
job.start();
job.run(move |context| {
setup_process(innerjob.clone(), context.command.clone(), notif)
setup_process(innerjob.clone(), context.command.clone(), outflags)
});
}
})
Expand Down Expand Up @@ -396,11 427,12 @@ fn interpret_command_args(args: &Args) -> Result<Arc<Command>> {

#[cfg(windows)]
fn available_powershell() -> String {
// TODO
todo!("figure out if powershell.exe is available, and use that, otherwise use pwsh.exe")
}

fn setup_process(job: Job, command: Arc<Command>, notif: bool) {
if notif {
fn setup_process(job: Job, command: Arc<Command>, outflags: OutputFlags) {
if outflags.toast {
Notification::new()
.summary("Watchexec: change detected")
.body(&format!("Running {command}"))
Expand All @@ -413,13 445,23 @@ fn setup_process(job: Job, command: Arc<Command>, notif: bool) {
);
}

if !outflags.quiet {
let mut stderr = StandardStream::stderr(outflags.colour);
stderr.reset().ok();
stderr
.set_color(ColorSpec::new().set_fg(Some(Color::Green)))
.ok();
writeln!(&mut stderr, "[Running: {command}]").ok();
stderr.reset().ok();
}

tokio::spawn(async move {
job.to_wait().await;
job.run(move |context| end_of_process(context.current, notif));
job.run(move |context| end_of_process(context.current, outflags));
});
}

fn end_of_process(state: &CommandState, notif: bool) {
fn end_of_process(state: &CommandState, outflags: OutputFlags) {
let CommandState::Finished {
status,
started,
Expand All @@ -430,24 472,26 @@ fn end_of_process(state: &CommandState, notif: bool) {
};

let duration = *finished - *started;
let msg = match status {
ProcessEnd::ExitError(code) => {
format!("Command exited with {code}, lasted {duration:?}")
}
let timing = if outflags.timings {
format!(", lasted {duration:?}")
} else {
String::new()
};
let (msg, fg) = match status {
ProcessEnd::ExitError(code) => (format!("Command exited with {code}{timing}"), Color::Red),
ProcessEnd::ExitSignal(sig) => {
format!("Command killed by {sig:?}, lasted {duration:?}")
}
ProcessEnd::ExitStop(sig) => {
format!("Command stopped by {sig:?}, lasted {duration:?}")
(format!("Command killed by {sig:?}{timing}"), Color::Magenta)
}
ProcessEnd::Continued => format!("Command continued, lasted {duration:?}"),
ProcessEnd::Exception(ex) => {
format!("Command ended by exception {ex:#x}, lasted {duration:?}")
}
ProcessEnd::Success => format!("Command was successful, lasted {duration:?}"),
ProcessEnd::ExitStop(sig) => (format!("Command stopped by {sig:?}{timing}"), Color::Blue),
ProcessEnd::Continued => (format!("Command continued{timing}"), Color::Cyan),
ProcessEnd::Exception(ex) => (
format!("Command ended by exception {ex:#x}{timing}"),
Color::Yellow,
),
ProcessEnd::Success => (format!("Command was successful{timing}"), Color::Green),
};

if notif {
if outflags.toast {
Notification::new()
.summary("Watchexec: command ended")
.body(&msg)
Expand All @@ -459,6 503,19 @@ fn end_of_process(state: &CommandState, notif: bool) {
drop,
);
}

if !outflags.quiet {
let mut stderr = StandardStream::stderr(outflags.colour);
stderr.reset().ok();
stderr.set_color(ColorSpec::new().set_fg(Some(fg))).ok();
writeln!(&mut stderr, "[{msg}]").ok();
stderr.reset().ok();
}

if outflags.bell {
eprint!("\x07");
stderr().flush().ok();
}
}

fn emit_events_to_command(
Expand Down

0 comments on commit 4facb93

Please sign in to comment.