#![doc = include_str!("../examples/basic_usage.rs")]
#![warn(missing_docs)]
pub(crate) mod colors;
pub mod logging;
pub(crate) mod proc;
pub(crate) mod prox;
pub use colors::{ALL, BRIGHT, NORMAL};
use colors::color_converter::ColorAsString;
use dashmap::DashMap;
use owo_colors::AnsiColors;
use serde::{Deserialize, Serialize};
use serde_inline_default::serde_inline_default;
use serde_with::{DisplayFromStr, DurationSeconds, serde_as};
use std::{
collections::HashMap,
ffi::OsString,
path::PathBuf,
process::{Child, ExitStatus},
sync::{Arc, Mutex, atomic::AtomicBool, mpsc},
time::{Duration, Instant},
};
use typed_builder::TypedBuilder;
const DEFAULT_READINESS_TIMEOUT: Duration = Duration::from_secs(5);
const DEFAULT_PREFIX_WIDTH: usize = 8;
const DEFAULT_COLORS: &[AnsiColors] = BRIGHT;
const DEFAULT_OUTPUT_IDLE_DEBOUNCE_MS: u64 = 3000;
#[serde_as]
#[serde_inline_default]
#[derive(Debug, Clone, TypedBuilder, serde_derive_default::Default, Serialize, Deserialize)]
pub struct Config {
#[serde_inline_default(false)]
#[builder(default = false)]
pub keep_going: bool,
#[serde(default)]
#[builder(default)]
pub watch: Vec<PathBuf>,
#[serde_as(as = "DurationSeconds")]
#[serde_inline_default(DEFAULT_READINESS_TIMEOUT)]
#[builder(default = DEFAULT_READINESS_TIMEOUT)]
pub readiness_fallback_timeout: Duration,
#[serde_inline_default(DEFAULT_PREFIX_WIDTH)]
#[builder(default = DEFAULT_PREFIX_WIDTH)]
pub prefix_width: usize,
#[serde_as(as = "Vec<ColorAsString>")]
#[serde_inline_default(DEFAULT_COLORS.to_vec())]
#[builder(default = DEFAULT_COLORS.to_vec())]
pub colors: Vec<AnsiColors>,
#[serde_inline_default(false)]
#[builder(default = false)]
pub show_timestamps: bool,
#[serde_as(as = "ColorAsString")]
#[serde_inline_default(AnsiColors::BrightWhite)]
#[builder(default = AnsiColors::BrightWhite)]
pub prox_color: AnsiColors,
#[serde_inline_default(true)]
#[builder(default = true)]
pub handle_control_c: bool,
#[serde_inline_default(DEFAULT_OUTPUT_IDLE_DEBOUNCE_MS)]
#[builder(default = DEFAULT_OUTPUT_IDLE_DEBOUNCE_MS)]
pub output_idle_debounce_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProcStatus {
Starting,
Running,
Exited(ExitStatus),
Error(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProxSignal {
Start,
Restart,
Shutdown,
}
impl TryFrom<char> for ProxSignal {
type Error = &'static str;
fn try_from(value: char) -> Result<Self, Self::Error> {
match value {
'r' => Ok(ProxSignal::Start),
'R' => Ok(ProxSignal::Restart),
'q' | 'Q' => Ok(ProxSignal::Shutdown),
_ => Err("Invalid signal character"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProxEvent {
Idle,
AllStarted,
SomeFailed {
exited_procs: Vec<String>,
},
Started {
proc_name: String,
},
Restarted {
proc_name: String,
},
StartFailed {
proc_name: String,
message: String,
},
Exited {
proc_name: String,
status: ExitStatus,
},
SigIntReceived,
}
#[serde_as]
#[derive(Debug, TypedBuilder, serde_derive_default::Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Prox {
#[builder(default)]
pub config: Config,
pub procs: Vec<Proc>,
#[serde(skip)]
#[builder(default, setter(strip_option))]
pub signal_rx: Option<mpsc::Receiver<ProxSignal>>,
#[serde(skip)]
#[builder(default, setter(skip))]
pub status_refs: Arc<DashMap<String, ProcStatus>>,
#[serde(skip)]
#[builder(default, setter(skip))]
child_refs: Arc<DashMap<String, Child>>,
#[serde(skip)]
#[builder(default, setter(skip))]
starting: Arc<DashMap<String, Instant>>,
#[serde(skip)]
#[builder(default, setter(skip))]
running: Arc<AtomicBool>,
#[serde(skip)]
#[builder(default, setter(skip))]
process_group_id: Arc<Mutex<i32>>,
#[serde(skip)]
#[builder(default, setter(skip))]
event_tx: Option<mpsc::Sender<ProxEvent>>,
}
#[serde_as]
#[serde_inline_default]
#[derive(Debug, Clone, Deserialize, TypedBuilder)]
#[serde(deny_unknown_fields)]
pub struct Proc {
pub name: String,
#[serde_as(as = "DisplayFromStr")]
pub command: OsString,
#[serde_as(as = "Vec<DisplayFromStr>")]
#[serde(default)]
#[builder(default)]
pub args: Vec<OsString>,
#[builder(default, setter(strip_option))]
pub working_dir: Option<PathBuf>,
#[builder(default, setter(strip_option))]
pub readiness_pattern: Option<String>,
#[serde(default)]
#[builder(default)]
pub watch: Vec<PathBuf>,
#[serde(default)]
#[builder(default)]
pub watch_rel: Vec<PathBuf>,
#[serde_as(as = "HashMap<DisplayFromStr, DisplayFromStr>")]
#[serde(default)]
#[builder(default)]
pub env: HashMap<OsString, OsString>,
#[serde(default)]
#[builder(default)]
pub env_clear: bool,
#[serde_as(as = "Option<ColorAsString>")]
#[builder(default, setter(strip_option))]
pub color: Option<AnsiColors>,
#[serde_as(as = "Option<Vec<DisplayFromStr>>")]
#[builder(default, setter(strip_option))]
pub cleanup_cmd: Option<Vec<OsString>>,
#[serde(default)]
#[builder(default)]
pub cleanup_before_start: bool,
}
#[cfg(test)]
mod tests {
use super::{Proc, Prox};
#[test]
fn test_builder() {
Prox::builder()
.procs(vec![
Proc::builder()
.name("name".to_string())
.command("cargo".into())
.build(),
])
.build();
}
}