1pub mod command;
4pub mod completion;
5pub mod host_text;
6pub mod parser;
7pub mod sandbox;
8pub mod script;
9pub mod serialization;
10pub mod session_store;
11pub mod todo_io;
12pub mod vfs;
13pub mod vm;
14pub mod workspace;
15
16mod repl;
17
18use std::cell::RefCell;
19use std::io::{self, BufReader, IsTerminal};
20use std::path::Path;
21use std::rc::Rc;
22
23use vfs::Vfs;
24
25#[derive(Debug)]
27pub enum RunWithError {
28 Usage,
29 ReplFailed,
30}
31
32impl std::fmt::Display for RunWithError {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self {
35 Self::Usage => f.write_str("usage error"),
36 Self::ReplFailed => f.write_str("repl failed"),
37 }
38 }
39}
40
41impl std::error::Error for RunWithError {}
42
43pub fn run_main() -> Result<(), Box<dyn std::error::Error>> {
48 let args: Vec<String> = std::env::args().collect();
49 let is_tty = io::stdin().is_terminal();
50 let mut stdin = BufReader::new(io::stdin());
51 let mut stdout = io::stdout();
52 let mut stderr = io::stderr();
53 run_main_from_args(&args, is_tty, &mut stdin, &mut stdout, &mut stderr)
54}
55
56#[allow(clippy::too_many_lines)] pub fn run_main_from_args<R, W1, W2>(
62 args: &[String],
63 is_tty: bool,
64 stdin: &mut R,
65 stdout: &mut W1,
66 stderr: &mut W2,
67) -> Result<(), Box<dyn std::error::Error>>
68where
69 R: std::io::BufRead + std::io::Read,
70 W1: std::io::Write,
71 W2: std::io::Write,
72{
73 let positionals: Vec<&str> = args
74 .iter()
75 .skip(1)
76 .filter(|a| *a != "-e" && *a != "-f")
77 .map(String::as_str)
78 .collect();
79 let set_e = args.iter().skip(1).any(|a| a == "-e");
80 let run_script = args.iter().skip(1).any(|a| a == "-f");
81
82 if run_script {
83 if positionals.len() != 1 {
84 writeln!(stderr, "usage: dev_shell [-e] -f script.dsh")?;
85 return Err(Box::new(std::io::Error::other("usage")));
86 }
87 let script_path = positionals[0];
88 let script_src = match host_text::read_host_text(Path::new(script_path)) {
89 Ok(s) => s,
90 Err(e) => {
91 writeln!(stderr, "dev_shell: {script_path}: {e}")?;
92 return Err(e.into());
93 }
94 };
95 let bin_path = Path::new(".dev_shell.bin");
96 #[cfg(unix)]
97 vm::export_devshell_workspace_root_env();
98 let vm_session = if cfg!(unix) {
99 vm::try_session_rc_or_host(stderr)
100 } else {
101 match vm::try_session_rc(stderr) {
102 Ok(s) => s,
103 Err(()) => return Err(Box::new(std::io::Error::other("vm session"))),
104 }
105 };
106 let vfs: Rc<RefCell<Vfs>> =
107 if cfg!(unix) && vm_session.borrow().is_host_only() && !cfg!(test) {
108 let root = vm::vm_workspace_host_root();
109 std::fs::create_dir_all(&root)?;
110 Rc::new(RefCell::new(Vfs::new_host_root(root)?))
111 } else {
112 let v = match serialization::load_from_file(bin_path) {
113 Ok(v) => v,
114 Err(e) => {
115 if e.kind() != io::ErrorKind::NotFound {
116 let _ =
117 writeln!(stderr, "Failed to load {}: {}", bin_path.display(), e);
118 }
119 Vfs::new()
120 }
121 };
122 Rc::new(RefCell::new(v))
123 };
124 if vm_session.borrow().is_guest_primary() {
125 if let Err(e) =
126 session_store::apply_guest_primary_startup(&mut vfs.borrow_mut(), bin_path)
127 {
128 let _ = writeln!(stderr, "dev_shell: guest-primary session: {e}");
129 }
130 }
131 script::run_script(
132 &vfs,
133 &vm_session,
134 &script_src,
135 bin_path,
136 set_e,
137 stdin,
138 stdout,
139 stderr,
140 )
141 .map_err(|e| Box::new(std::io::Error::other(e.to_string())) as Box<dyn std::error::Error>)
142 } else {
143 let path = match positionals.as_slice() {
144 [] => Path::new(".dev_shell.bin"),
145 [p] => Path::new(p),
146 _ => {
147 writeln!(stderr, "usage: dev_shell [options] [path]")?;
148 return Err(Box::new(std::io::Error::other("usage")));
149 }
150 };
151
152 #[cfg(unix)]
153 vm::export_devshell_workspace_root_env();
154
155 let vm_session = if cfg!(unix) {
156 vm::try_session_rc_or_host(stderr)
157 } else {
158 match vm::try_session_rc(stderr) {
159 Ok(s) => s,
160 Err(()) => return Err(Box::new(std::io::Error::other("vm session"))),
161 }
162 };
163
164 #[cfg(all(unix, not(test)))]
165 if vm::should_delegate_lima_shell(&vm_session, is_tty, run_script) {
166 let vfs = Rc::new(RefCell::new(Vfs::new()));
167 vm_session
168 .borrow_mut()
169 .ensure_ready(&vfs.borrow(), vfs.borrow().cwd())
170 .map_err(|e| {
171 Box::new(std::io::Error::other(e.to_string())) as Box<dyn std::error::Error>
172 })?;
173 let err = vm_session.borrow().exec_lima_interactive_shell();
174 return Err(Box::new(err));
175 }
176
177 let vfs: Rc<RefCell<Vfs>> =
178 if cfg!(unix) && vm_session.borrow().is_host_only() && !cfg!(test) {
179 let root = vm::vm_workspace_host_root();
180 std::fs::create_dir_all(&root)?;
181 Rc::new(RefCell::new(Vfs::new_host_root(root)?))
182 } else {
183 let v = match serialization::load_from_file(path) {
184 Ok(v) => v,
185 Err(e) => {
186 if e.kind() == io::ErrorKind::NotFound {
187 if positionals.len() > 1 {
188 let _ = writeln!(stderr, "File not found, starting with empty VFS");
189 }
190 } else {
191 let _ = writeln!(stderr, "Failed to load {}: {}", path.display(), e);
192 }
193 Vfs::new()
194 }
195 };
196 Rc::new(RefCell::new(v))
197 };
198 if vm_session.borrow().is_guest_primary() {
199 if let Err(e) = session_store::apply_guest_primary_startup(&mut vfs.borrow_mut(), path)
200 {
201 let _ = writeln!(stderr, "dev_shell: guest-primary session: {e}");
202 }
203 }
204 repl::run(&vfs, &vm_session, is_tty, path, stdin, stdout, stderr).map_err(|()| {
205 Box::new(std::io::Error::other("repl error")) as Box<dyn std::error::Error>
206 })?;
207 Ok(())
208 }
209}
210
211pub fn run_with<R, W1, W2>(
216 args: &[String],
217 stdin: &mut R,
218 stdout: &mut W1,
219 stderr: &mut W2,
220) -> Result<(), RunWithError>
221where
222 R: std::io::BufRead + std::io::Read,
223 W1: std::io::Write,
224 W2: std::io::Write,
225{
226 let path = match args {
227 [] | [_] => Path::new(".dev_shell.bin"),
228 [_, path] => Path::new(path),
229 _ => {
230 let _ = writeln!(stderr, "usage: dev_shell [path]");
231 return Err(RunWithError::Usage);
232 }
233 };
234 #[cfg(unix)]
235 vm::export_devshell_workspace_root_env();
236 let vm_session = if cfg!(unix) {
237 vm::try_session_rc_or_host(stderr)
238 } else {
239 vm::try_session_rc(stderr).map_err(|()| RunWithError::ReplFailed)?
240 };
241 let vfs: Rc<RefCell<Vfs>> = if cfg!(unix) && vm_session.borrow().is_host_only() && !cfg!(test) {
242 let root = vm::vm_workspace_host_root();
243 std::fs::create_dir_all(&root).map_err(|_| RunWithError::ReplFailed)?;
244 Rc::new(RefCell::new(
245 Vfs::new_host_root(root).map_err(|_| RunWithError::ReplFailed)?,
246 ))
247 } else {
248 Rc::new(RefCell::new(
249 serialization::load_from_file(path).unwrap_or_default(),
250 ))
251 };
252 if vm_session.borrow().is_guest_primary() {
253 if let Err(e) = session_store::apply_guest_primary_startup(&mut vfs.borrow_mut(), path) {
254 let _ = writeln!(stderr, "dev_shell: guest-primary session: {e}");
255 }
256 }
257 repl::run(&vfs, &vm_session, false, path, stdin, stdout, stderr)
258 .map_err(|()| RunWithError::ReplFailed)
259}
260
261#[cfg(test)]
262mod tests;