Skip to main content

osp_cli/app/
mod.rs

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