1use 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}