Files
RedBear-OS/local/recipes/shells/brush/source/brush-shell/src/events.rs
T
vasilito 25fb843c40 brush: vendor the source tree (un-ignore) to complete the local fork
The prior commit switched the recipe to `[source] path = "source"`, but
.gitignore:77 still listed `local/recipes/shells/brush/source` (a leftover from
when brush was a transient upstream git fetch), so the vendored tree was not
tracked — a fresh clone would have no brush source and the build would fail.
Drop that ignore line and commit the vendored working tree (reubeno/brush @
897b373e, with the Redox port patches pre-applied). brush is now a durable
local fork like the other path=source recipes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 23:34:31 +09:00

185 lines
6.0 KiB
Rust

//! Facilities for configuring event tracing in the brush shell.
use std::{collections::HashSet, fmt::Display};
use brush_core::Error;
use tracing_subscriber::{
Layer, Registry, filter::Targets, layer::SubscriberExt, reload::Handle, util::SubscriberInitExt,
};
/// Type of event to trace.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)]
pub enum TraceEvent {
/// Traces parsing and evaluation of arithmetic expressions.
#[clap(name = "arithmetic")]
Arithmetic,
/// Traces command execution.
#[clap(name = "commands")]
Commands,
/// Traces command completion generation.
#[clap(name = "complete")]
Complete,
/// Traces word expansion.
#[clap(name = "expand")]
Expand,
/// Traces functions.
#[clap(name = "functions")]
Functions,
/// Traces input controls.
#[clap(name = "input")]
Input,
/// Traces job management.
#[clap(name = "jobs")]
Jobs,
/// Traces the process of parsing tokens into an abstract syntax tree.
#[clap(name = "parse")]
Parse,
/// Traces pattern matching.
#[clap(name = "pattern")]
Pattern,
/// Traces the process of tokenizing input text.
#[clap(name = "tokenize")]
Tokenize,
/// Traces usage of unimplemented functionality.
#[clap(name = "unimplemented", alias = "unimp")]
Unimplemented,
}
impl Display for TraceEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Arithmetic => write!(f, "arithmetic"),
Self::Commands => write!(f, "commands"),
Self::Complete => write!(f, "complete"),
Self::Expand => write!(f, "expand"),
Self::Functions => write!(f, "functions"),
Self::Input => write!(f, "input"),
Self::Jobs => write!(f, "jobs"),
Self::Parse => write!(f, "parse"),
Self::Pattern => write!(f, "pattern"),
Self::Tokenize => write!(f, "tokenize"),
Self::Unimplemented => write!(f, "unimplemented"),
}
}
}
#[derive(Default)]
pub(crate) struct TraceEventConfig {
enabled_debug_events: HashSet<TraceEvent>,
disabled_events: HashSet<TraceEvent>,
handle: Option<Handle<Targets, Registry>>,
}
impl TraceEventConfig {
pub fn init(enabled_debug_events: &[TraceEvent], disabled_events: &[TraceEvent]) -> Self {
let enabled_debug_events: HashSet<TraceEvent> =
enabled_debug_events.iter().copied().collect();
let disabled_events: HashSet<TraceEvent> = disabled_events.iter().copied().collect();
let mut config = Self {
enabled_debug_events,
disabled_events,
..Default::default()
};
let filter = config.compose_filter();
// Make the filter reloadable so that we can change the log level at runtime.
let (reload_filter, handle) = tracing_subscriber::reload::Layer::new(filter);
let layer = tracing_subscriber::fmt::layer()
.with_writer(std::io::stderr)
.without_time()
.with_target(false)
.with_filter(reload_filter);
if tracing_subscriber::registry()
.with(layer)
.try_init()
.is_ok()
{
config.handle = Some(handle);
} else {
// Something went wrong; proceed on anyway but complain audibly.
eprintln!("warning: failed to initialize tracing.");
}
config
}
fn compose_filter(&self) -> tracing_subscriber::filter::Targets {
let mut filter = tracing_subscriber::filter::Targets::new()
.with_default(tracing_subscriber::filter::LevelFilter::INFO);
for event in &self.enabled_debug_events {
let targets = Self::event_to_tracing_targets(event);
filter = filter.with_targets(
targets
.into_iter()
.map(|target| (target, tracing::Level::DEBUG)),
);
}
for event in &self.disabled_events {
let targets = Self::event_to_tracing_targets(event);
filter = filter.with_targets(
targets
.into_iter()
.map(|target| (target, tracing::level_filters::LevelFilter::OFF)),
);
}
filter
}
const fn event_to_tracing_targets(event: &TraceEvent) -> [&str; 1] {
match event {
TraceEvent::Arithmetic => ["arithmetic"],
TraceEvent::Commands => ["commands"],
TraceEvent::Complete => ["completion"],
TraceEvent::Expand => ["expansion"],
TraceEvent::Functions => ["functions"],
TraceEvent::Input => ["input"],
TraceEvent::Jobs => ["jobs"],
TraceEvent::Parse => ["parse"],
TraceEvent::Pattern => ["pattern"],
TraceEvent::Tokenize => ["tokenize"],
TraceEvent::Unimplemented => ["unimplemented"],
}
}
pub const fn get_enabled_events(&self) -> &HashSet<TraceEvent> {
&self.enabled_debug_events
}
pub fn enable(&mut self, event: TraceEvent) -> Result<(), Error> {
// Don't bother to reload config if nothing has changed.
if !self.enabled_debug_events.insert(event) {
return Ok(());
}
self.reload_filter()
}
pub fn disable(&mut self, event: TraceEvent) -> Result<(), Error> {
// Don't bother to reload config if nothing has changed.
if !self.enabled_debug_events.remove(&event) {
return Ok(());
}
self.reload_filter()
}
fn reload_filter(&self) -> Result<(), Error> {
if let Some(handle) = &self.handle {
if handle.reload(self.compose_filter()).is_ok() {
Ok(())
} else {
Err(brush_core::ErrorKind::Unimplemented("failed to enable tracing events").into())
}
} else {
Err(brush_core::ErrorKind::Unimplemented("tracing not initialized").into())
}
}
}