use crate::{
application,
arguments::{ColorMode, OutputOptions, QuerySource},
delivery,
error::{Failure, query_execution_failure},
host::CliHost,
presentation::{self, render_query_result},
request_input::{NativeRequest, read_native_request, read_query_request, read_utf8_input},
};
#[cfg(feature = "roff")]
use crate::{error::query_failure, request_input::read_input_bytes};
use mant_loader::LoadPolicy;
use mant_protocol::{InputFormat, ScopeQueryRequest, ScopeRequestSchema};
use std::io::Read;
pub(super) struct QueryExecution {
pub(super) source: QuerySource,
pub(super) policy: LoadPolicy,
pub(super) output: QueryOutput,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct QueryOutput {
pub(super) presentation: OutputOptions,
pub(super) pretty: bool,
pub(super) preserve_anchors: bool,
pub(super) target: presentation::OutputTarget,
}
impl QueryOutput {
fn render_options(self) -> presentation::RenderOptions {
presentation::RenderOptions {
format: self.presentation.format(),
pretty: self.pretty,
preserve_anchors: self.preserve_anchors,
color: self.presentation.color == ColorMode::Always,
target: self.target,
}
}
}
pub(super) fn execute_query(
command: QueryExecution,
input: &mut dyn Read,
host: &dyn CliHost,
) -> Result<String, Failure> {
let QueryExecution {
source,
policy,
output,
} = command;
let source = match source {
QuerySource::ScopeArguments { scope, view } => {
return execute_scope_arguments(scope, view, output, policy, host);
}
QuerySource::StdinJson => match read_native_request(input)? {
NativeRequest::Query(request) => QuerySource::Arguments(request),
NativeRequest::Scope(request) => {
return execute_scope_request(&request, output, host);
}
},
source => source,
};
let result = match source {
QuerySource::InputStdin { format, view } => {
validate_markdown_policy(policy)?;
let query = match format {
InputFormat::Markdown => {
let source =
read_utf8_input(input, mant_loader::MAX_MARKDOWN_BYTES, "Markdown input")?;
host.query_markdown(&source)?
}
InputFormat::Roff => {
#[cfg(feature = "roff")]
{
let source =
read_input_bytes(input, mant_loader::MAX_MANUAL_BYTES, "roff input")?;
mant_loader::load_roff_bytes(&source)
.map_err(|error| query_failure(mant_engine::QueryError::Load(error)))?
}
#[cfg(not(feature = "roff"))]
return Err(Failure::usage(
"native roff input is unavailable in this build",
));
}
InputFormat::Auto => unreachable!("stdin input format is validated by clap"),
};
mant_engine::project_query_view(query, &view).map_err(query_execution_failure)?
}
source => {
let request = read_query_request(source, input)?;
application::execute_query(&request, policy, host)?
}
};
if policy == LoadPolicy::TldrOnly && output.presentation.format.is_none() {
let color = output.presentation.color;
let mant_engine::QueryViewResult::Excerpt(mant_protocol::QueryExcerpt {
selections, ..
}) = &result
else {
return Err(Failure::operational(
"the tldr terminal presentation requires a tldr excerpt",
));
};
let document = selections.iter().find_map(|selection| match selection {
mant_protocol::ExcerptSelection::Tldr { document, .. } => Some(document),
_ => None,
});
return document.map_or_else(
|| Err(Failure::operational("no tldr quick reference is available")),
|document| {
Ok(delivery::tldr::render_tldr_terminal(
document,
color == ColorMode::Always,
))
},
);
}
render_query_result(&result, output.render_options())
}
fn execute_scope_arguments(
scope: mant_protocol::DocumentScope,
view: Option<mant_protocol::ScopeQueryView>,
output: QueryOutput,
policy: LoadPolicy,
host: &dyn CliHost,
) -> Result<String, Failure> {
let Some(view) = view else {
return Err(Failure::usage(
"multi-document output requires --search or --explain; use --display tui for interactive reading",
));
};
if policy != LoadPolicy::Combined {
return Err(Failure::usage(
"--manual and --tldr do not apply to multi-document scopes",
));
}
let request = ScopeQueryRequest {
schema: ScopeRequestSchema::V0Dot11,
scope,
view,
};
execute_scope_request(&request, output, host)
}
fn execute_scope_request(
request: &ScopeQueryRequest,
output: QueryOutput,
host: &dyn CliHost,
) -> Result<String, Failure> {
let response = application::execute_scope_query(request, host)?;
presentation::render_scope_query_result(&response, output.render_options())
}
fn validate_markdown_policy(policy: LoadPolicy) -> Result<(), Failure> {
if policy != LoadPolicy::Combined {
return Err(Failure::usage(
"content-only policies do not apply to Markdown input",
));
}
Ok(())
}