use clap::ArgMatches;
use std::cell::RefCell;
use std::rc::Rc;
use crate::cli::handler::Output as HandlerOutput;
use crate::cli::handler::{CommandContext, ExternalFailure, RunError, RunErrorKind};
use crate::cli::hooks::{HookError, Hooks};
use crate::context::{ContextRegistry, RenderContext};
use crate::StructuredOutputProjection;
use crate::Theme;
use serde::Serialize;
pub use standout_dispatch::{
extract_command_path, get_deepest_matches, has_subcommand, insert_default_command,
};
pub enum DispatchOutput {
Text {
formatted: String,
raw: String,
},
Binary(Vec<u8>, String),
Silent,
}
#[derive(Debug)]
struct HandlerErrorSource(Box<dyn std::error::Error + Send + Sync + 'static>);
impl std::fmt::Display for HandlerErrorSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
impl std::error::Error for HandlerErrorSource {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(self.0.as_ref())
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn render_handler_output<T: Serialize>(
result: crate::cli::HandlerResult<T>,
matches: &ArgMatches,
ctx: &CommandContext,
hooks: Option<&Hooks>,
template: &str,
theme: &Theme,
context_registry: &ContextRegistry,
template_engine: &dyn standout_render::template::TemplateEngine,
output_mode: crate::OutputMode,
structured_output_projection: Option<&StructuredOutputProjection>,
ambiguous_width: crate::AmbiguousWidth,
) -> Result<DispatchOutput, RunError> {
match result {
Ok(output) => match output {
HandlerOutput::Render(data) => {
let mut json_data = serde_json::to_value(&data).map_err(|e| {
RunError::new(
format!("Failed to serialize handler result: {}", e),
RunErrorKind::Render,
)
})?;
if let Some(hooks) = hooks {
json_data =
hooks
.run_post_dispatch(matches, ctx, json_data)
.map_err(|error| {
hook_run_error(error, crate::cli::HookPhase::PostDispatch)
})?;
}
let ambiguous_width =
standout_render::detect_ambiguous_width_override().unwrap_or(ambiguous_width);
let render_ctx = RenderContext::with_ambiguous_width(
output_mode,
standout_render::detect_terminal_width(),
ambiguous_width,
theme,
&json_data,
);
let render_result = match (output_mode, structured_output_projection) {
(crate::OutputMode::Csv, Some(projection)) => {
standout_render::template::RenderResult::plain(
projection
.csv_projection()
.render(&json_data)
.map_err(|e| RunError::new(e.to_string(), RunErrorKind::Render))?,
)
}
_ => standout_render::template::render_auto_with_engine_split(
template_engine,
template,
&json_data,
theme,
output_mode,
context_registry,
&render_ctx,
)
.map_err(|e| RunError::new(e.to_string(), RunErrorKind::Render))?,
};
Ok(DispatchOutput::Text {
formatted: render_result.formatted,
raw: render_result.raw,
})
}
HandlerOutput::Silent => Ok(DispatchOutput::Silent),
HandlerOutput::Binary { data, filename } => Ok(DispatchOutput::Binary(data, filename)),
},
Err(error) => Err(handler_run_error(error)),
}
}
pub(crate) fn handler_run_error(error: anyhow::Error) -> RunError {
let error = match error.downcast::<ExternalFailure>() {
Ok(external) => return RunError::from(external),
Err(error) => error,
};
RunError::new(format!("Error: {}", error), RunErrorKind::Handler)
.with_source(HandlerErrorSource(error.into_boxed_dyn_error()))
}
pub(crate) fn hook_run_error(mut error: HookError, phase: crate::cli::HookPhase) -> RunError {
if phase == crate::cli::HookPhase::PreDispatch {
if let Some(source) = error.source.take() {
match source.downcast::<ExternalFailure>() {
Ok(external) => return RunError::from(*external),
Err(source) => error.source = Some(source),
}
}
}
error.phase = phase;
RunError::new(format!("Hook error: {}", error), RunErrorKind::Hook(phase)).with_source(error)
}
pub type DispatchFn = Rc<
RefCell<
dyn FnMut(
&ArgMatches,
&CommandContext,
Option<&Hooks>,
crate::OutputMode,
&crate::Theme,
crate::AmbiguousWidth,
) -> Result<DispatchOutput, RunError>,
>,
>;
pub fn dispatch(
dispatch_fn: &DispatchFn,
matches: &ArgMatches,
ctx: &CommandContext,
hooks: Option<&Hooks>,
output_mode: crate::OutputMode,
theme: &crate::Theme,
ambiguous_width: crate::AmbiguousWidth,
) -> Result<DispatchOutput, RunError> {
(dispatch_fn.borrow_mut())(matches, ctx, hooks, output_mode, theme, ambiguous_width)
}