Skip to main content

osp_cli/app/
mod.rs

1//! Main host-facing entrypoints plus bootstrap/runtime state.
2
3use crate::native::NativeCommandRegistry;
4use crate::ui::messages::{MessageBuffer, MessageLevel, adjust_verbosity};
5use std::ffi::OsString;
6
7pub(crate) mod bootstrap;
8pub(crate) mod command_output;
9pub(crate) mod config_explain;
10pub(crate) mod dispatch;
11pub(crate) mod external;
12pub(crate) mod help;
13pub(crate) mod host;
14pub(crate) mod logging;
15pub(crate) mod repl_lifecycle;
16pub mod runtime;
17pub mod session;
18pub mod sink;
19#[cfg(test)]
20mod tests;
21pub(crate) mod timing;
22
23pub(crate) use bootstrap::*;
24pub(crate) use command_output::*;
25pub use host::run_from;
26pub(crate) use host::*;
27pub use runtime::{
28    AppClients, AppRuntime, AuthState, ConfigState, LaunchContext, RuntimeContext, TerminalKind,
29    UiState,
30};
31pub(crate) use session::AppStateInit;
32pub use session::{
33    AppSession, AppState, DebugTimingBadge, DebugTimingState, LastFailure, ReplScopeFrame,
34    ReplScopeStack,
35};
36pub use sink::{BufferedUiSink, StdIoUiSink, UiSink};
37
38#[derive(Clone, Default)]
39pub struct App {
40    native_commands: NativeCommandRegistry,
41}
42
43impl App {
44    pub fn new() -> Self {
45        Self {
46            native_commands: NativeCommandRegistry::default(),
47        }
48    }
49
50    pub fn with_native_commands(mut self, native_commands: NativeCommandRegistry) -> Self {
51        self.native_commands = native_commands;
52        self
53    }
54
55    pub fn run_from<I, T>(&self, args: I) -> miette::Result<i32>
56    where
57        I: IntoIterator<Item = T>,
58        T: Into<OsString> + Clone,
59    {
60        host::run_from_with_sink_and_native(args, &mut StdIoUiSink, &self.native_commands)
61    }
62
63    pub fn with_sink<'a>(self, sink: &'a mut dyn UiSink) -> AppRunner<'a> {
64        AppRunner { app: self, sink }
65    }
66
67    pub fn run_with_sink<I, T>(&self, args: I, sink: &mut dyn UiSink) -> miette::Result<i32>
68    where
69        I: IntoIterator<Item = T>,
70        T: Into<OsString> + Clone,
71    {
72        host::run_from_with_sink_and_native(args, sink, &self.native_commands)
73    }
74
75    pub fn run_process<I, T>(&self, args: I) -> i32
76    where
77        I: IntoIterator<Item = T>,
78        T: Into<OsString> + Clone,
79    {
80        let mut sink = StdIoUiSink;
81        self.run_process_with_sink(args, &mut sink)
82    }
83
84    pub fn run_process_with_sink<I, T>(&self, args: I, sink: &mut dyn UiSink) -> i32
85    where
86        I: IntoIterator<Item = T>,
87        T: Into<OsString> + Clone,
88    {
89        let args = args.into_iter().map(Into::into).collect::<Vec<OsString>>();
90        let message_verbosity = bootstrap_message_verbosity(&args);
91
92        match host::run_from_with_sink_and_native(args, sink, &self.native_commands) {
93            Ok(code) => code,
94            Err(err) => {
95                let mut messages = MessageBuffer::default();
96                messages.error(render_report_message(&err, message_verbosity));
97                sink.write_stderr(&messages.render_grouped(message_verbosity));
98                classify_exit_code(&err)
99            }
100        }
101    }
102}
103
104pub struct AppRunner<'a> {
105    app: App,
106    sink: &'a mut dyn UiSink,
107}
108
109impl<'a> AppRunner<'a> {
110    pub fn run_from<I, T>(&mut self, args: I) -> miette::Result<i32>
111    where
112        I: IntoIterator<Item = T>,
113        T: Into<OsString> + Clone,
114    {
115        self.app.run_with_sink(args, self.sink)
116    }
117
118    pub fn run_process<I, T>(&mut self, args: I) -> i32
119    where
120        I: IntoIterator<Item = T>,
121        T: Into<OsString> + Clone,
122    {
123        self.app.run_process_with_sink(args, self.sink)
124    }
125}
126
127#[derive(Clone, Default)]
128pub struct AppBuilder {
129    native_commands: NativeCommandRegistry,
130}
131
132impl AppBuilder {
133    pub fn new() -> Self {
134        Self {
135            native_commands: NativeCommandRegistry::default(),
136        }
137    }
138
139    pub fn with_native_commands(mut self, native_commands: NativeCommandRegistry) -> Self {
140        self.native_commands = native_commands;
141        self
142    }
143
144    pub fn build(self) -> App {
145        App::new().with_native_commands(self.native_commands)
146    }
147
148    pub fn build_with_sink<'a>(self, sink: &'a mut dyn UiSink) -> AppRunner<'a> {
149        self.build().with_sink(sink)
150    }
151}
152
153pub fn run_process<I, T>(args: I) -> i32
154where
155    I: IntoIterator<Item = T>,
156    T: Into<OsString> + Clone,
157{
158    let mut sink = StdIoUiSink;
159    run_process_with_sink(args, &mut sink)
160}
161
162pub fn run_process_with_sink<I, T>(args: I, sink: &mut dyn UiSink) -> i32
163where
164    I: IntoIterator<Item = T>,
165    T: Into<OsString> + Clone,
166{
167    let args = args.into_iter().map(Into::into).collect::<Vec<OsString>>();
168    let message_verbosity = bootstrap_message_verbosity(&args);
169
170    match host::run_from_with_sink(args, sink) {
171        Ok(code) => code,
172        Err(err) => {
173            let mut messages = MessageBuffer::default();
174            messages.error(render_report_message(&err, message_verbosity));
175            sink.write_stderr(&messages.render_grouped(message_verbosity));
176            classify_exit_code(&err)
177        }
178    }
179}
180
181fn bootstrap_message_verbosity(args: &[OsString]) -> MessageLevel {
182    let mut verbose = 0u8;
183    let mut quiet = 0u8;
184
185    for token in args.iter().skip(1) {
186        let Some(value) = token.to_str() else {
187            continue;
188        };
189
190        if value == "--" {
191            break;
192        }
193
194        match value {
195            "--verbose" => {
196                verbose = verbose.saturating_add(1);
197                continue;
198            }
199            "--quiet" => {
200                quiet = quiet.saturating_add(1);
201                continue;
202            }
203            _ => {}
204        }
205
206        if value.starts_with('-') && !value.starts_with("--") {
207            for ch in value.chars().skip(1) {
208                match ch {
209                    'v' => verbose = verbose.saturating_add(1),
210                    'q' => quiet = quiet.saturating_add(1),
211                    _ => {}
212                }
213            }
214        }
215    }
216
217    adjust_verbosity(MessageLevel::Success, verbose, quiet)
218}