use crate::config::ConfigLayer;
use crate::native::NativeCommandRegistry;
use crate::ui::messages::{MessageBuffer, MessageLevel, adjust_verbosity};
use crate::ui::{RenderSettings, render_messages_without_config};
use std::ffi::OsString;
pub(crate) mod assembly;
pub(crate) mod bootstrap;
pub(crate) mod builtin;
pub(crate) mod command_output;
pub(crate) mod config_explain;
pub(crate) mod dispatch;
pub(crate) mod external;
pub(crate) mod facts;
pub(crate) mod help;
pub(crate) mod host;
pub(crate) mod logging;
pub(crate) mod rebuild;
pub(crate) mod repl_lifecycle;
pub(crate) mod runtime;
pub(crate) mod session;
pub(crate) mod sink;
#[cfg(test)]
mod tests;
pub(crate) mod timing;
pub(crate) use bootstrap::*;
pub(crate) use builtin::*;
pub(crate) use command_output::*;
pub(crate) use config_explain::{
ConfigExplainContext, config_explain_json, config_explain_result, config_value_to_json,
explain_runtime_config, format_scope, is_sensitive_key, push_missing_config_key_messages,
render_config_explain_text,
};
pub(crate) use facts::*;
pub use host::run_from;
pub(crate) use host::*;
pub(crate) use repl_lifecycle::rebuild_repl_in_place;
pub use runtime::{
AppClients, AppRuntime, AuthState, ConfigState, LaunchContext, RuntimeContext, TerminalKind,
UiState,
};
#[cfg(test)]
pub(crate) use session::AppStateInit;
pub use session::{
AppSession, AppSessionBuilder, AppState, AppStateBuilder, DebugTimingBadge, DebugTimingState,
LastFailure, ReplScopeFrame, ReplScopeStack,
};
pub use sink::{BufferedUiSink, StdIoUiSink, UiSink};
#[derive(Clone, Default)]
pub(crate) struct AppDefinition {
native_commands: NativeCommandRegistry,
product_defaults: ConfigLayer,
}
impl AppDefinition {
fn with_native_commands(mut self, native_commands: NativeCommandRegistry) -> Self {
self.native_commands = native_commands;
self
}
fn with_product_defaults(mut self, product_defaults: ConfigLayer) -> Self {
self.product_defaults = product_defaults;
self
}
}
#[derive(Clone, Default)]
#[must_use]
pub struct App {
definition: AppDefinition,
}
impl App {
pub fn builder() -> AppBuilder {
AppBuilder::default()
}
pub fn new() -> Self {
Self::builder().build()
}
pub fn with_native_commands(mut self, native_commands: NativeCommandRegistry) -> Self {
self.definition = self.definition.with_native_commands(native_commands);
self
}
pub fn with_product_defaults(mut self, product_defaults: ConfigLayer) -> Self {
self.definition = self.definition.with_product_defaults(product_defaults);
self
}
pub fn run_from<I, T>(&self, args: I) -> miette::Result<i32>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
host::run_from_with_sink_and_app(args, &mut StdIoUiSink, &self.definition)
}
pub fn with_sink<'a>(self, sink: &'a mut dyn UiSink) -> AppRunner<'a> {
AppRunner { app: self, sink }
}
pub fn run_with_sink<I, T>(&self, args: I, sink: &mut dyn UiSink) -> miette::Result<i32>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
host::run_from_with_sink_and_app(args, sink, &self.definition)
}
pub fn run_process<I, T>(&self, args: I) -> i32
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let mut sink = StdIoUiSink;
self.run_process_with_sink(args, &mut sink)
}
pub fn run_process_with_sink<I, T>(&self, args: I, sink: &mut dyn UiSink) -> i32
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let args = args.into_iter().map(Into::into).collect::<Vec<OsString>>();
let message_verbosity = bootstrap_message_verbosity(&args);
match host::run_from_with_sink_and_app(args, sink, &self.definition) {
Ok(code) => code,
Err(err) => {
let mut messages = MessageBuffer::default();
messages.error(render_report_message(&err, message_verbosity));
sink.write_stderr(&render_messages_without_config(
&RenderSettings::test_plain(crate::core::output::OutputFormat::Auto),
&messages,
message_verbosity,
));
classify_exit_code(&err)
}
}
}
}
#[must_use = "AppRunner only has an effect when you call run_from or run_process on it"]
pub struct AppRunner<'a> {
app: App,
sink: &'a mut dyn UiSink,
}
impl<'a> AppRunner<'a> {
pub fn run_from<I, T>(&mut self, args: I) -> miette::Result<i32>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
self.app.run_with_sink(args, self.sink)
}
pub fn run_process<I, T>(&mut self, args: I) -> i32
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
self.app.run_process_with_sink(args, self.sink)
}
}
#[derive(Clone, Default)]
#[must_use]
pub struct AppBuilder {
definition: AppDefinition,
}
impl AppBuilder {
pub fn with_native_commands(mut self, native_commands: NativeCommandRegistry) -> Self {
self.definition = self.definition.with_native_commands(native_commands);
self
}
pub fn with_product_defaults(mut self, product_defaults: ConfigLayer) -> Self {
self.definition = self.definition.with_product_defaults(product_defaults);
self
}
pub fn build(self) -> App {
App {
definition: self.definition,
}
}
pub fn build_with_sink<'a>(self, sink: &'a mut dyn UiSink) -> AppRunner<'a> {
self.build().with_sink(sink)
}
}
pub fn run_process<I, T>(args: I) -> i32
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let mut sink = StdIoUiSink;
run_process_with_sink(args, &mut sink)
}
pub fn run_process_with_sink<I, T>(args: I, sink: &mut dyn UiSink) -> i32
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let args = args.into_iter().map(Into::into).collect::<Vec<OsString>>();
let message_verbosity = bootstrap_message_verbosity(&args);
match host::run_from_with_sink(args, sink) {
Ok(code) => code,
Err(err) => {
let mut messages = MessageBuffer::default();
messages.error(render_report_message(&err, message_verbosity));
sink.write_stderr(&render_messages_without_config(
&RenderSettings::test_plain(crate::core::output::OutputFormat::Auto),
&messages,
message_verbosity,
));
classify_exit_code(&err)
}
}
}
fn bootstrap_message_verbosity(args: &[OsString]) -> MessageLevel {
let mut verbose = 0u8;
let mut quiet = 0u8;
for token in args.iter().skip(1) {
let Some(value) = token.to_str() else {
continue;
};
if value == "--" {
break;
}
match value {
"--verbose" => {
verbose = verbose.saturating_add(1);
continue;
}
"--quiet" => {
quiet = quiet.saturating_add(1);
continue;
}
_ => {}
}
if value.starts_with('-') && !value.starts_with("--") {
for ch in value.chars().skip(1) {
match ch {
'v' => verbose = verbose.saturating_add(1),
'q' => quiet = quiet.saturating_add(1),
_ => {}
}
}
}
}
adjust_verbosity(MessageLevel::Success, verbose, quiet)
}