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
56fn vm_session_for_entry<W: std::io::Write>(
57 stderr: &mut W,
58) -> Result<Rc<RefCell<vm::SessionHolder>>, Box<dyn std::error::Error>> {
59 if cfg!(unix) {
60 Ok(vm::try_session_rc_or_host(stderr))
61 } else {
62 vm::try_session_rc(stderr).map_err(|_| {
63 Box::new(std::io::Error::other("vm session")) as Box<dyn std::error::Error>
64 })
65 }
66}
67
68fn run_script_mode<R, W1, W2>(
69 positionals: &[&str],
70 set_e: bool,
71 stdin: &mut R,
72 stdout: &mut W1,
73 stderr: &mut W2,
74) -> Result<(), Box<dyn std::error::Error>>
75where
76 R: std::io::BufRead + std::io::Read,
77 W1: std::io::Write,
78 W2: std::io::Write,
79{
80 if positionals.len() != 1 {
81 writeln!(stderr, "usage: dev_shell [-e] -f script.dsh")?;
82 return Err(Box::new(std::io::Error::other("usage")));
83 }
84 let script_path = positionals[0];
85 let script_src = match host_text::read_host_text(Path::new(script_path)) {
86 Ok(s) => s,
87 Err(e) => {
88 writeln!(stderr, "dev_shell: {script_path}: {e}")?;
89 return Err(e.into());
90 }
91 };
92 let bin_path = Path::new(".dev_shell.bin");
93 #[cfg(unix)]
94 vm::export_devshell_workspace_root_env();
95 let vm_session = vm_session_for_entry(stderr)?;
96 let vfs: Rc<RefCell<Vfs>> = if cfg!(unix) && vm_session.borrow().is_host_only() && !cfg!(test) {
97 let root = vm::vm_workspace_host_root();
98 std::fs::create_dir_all(&root)?;
99 Rc::new(RefCell::new(Vfs::new_host_root(root)?))
100 } else {
101 let v = match serialization::load_from_file(bin_path) {
102 Ok(v) => v,
103 Err(e) => {
104 if e.kind() != io::ErrorKind::NotFound {
105 let _ = writeln!(stderr, "Failed to load {}: {}", bin_path.display(), e);
106 }
107 Vfs::new()
108 }
109 };
110 Rc::new(RefCell::new(v))
111 };
112 if vm_session.borrow().is_guest_primary() {
113 if let Err(e) = session_store::apply_guest_primary_startup(&mut vfs.borrow_mut(), bin_path)
114 {
115 let _ = writeln!(stderr, "dev_shell: guest-primary session: {e}");
116 }
117 }
118 script::run_script(&vfs, &vm_session, &script_src, set_e, stdin, stdout, stderr)
119 .map_err(|e| Box::new(std::io::Error::other(e.to_string())) as Box<dyn std::error::Error>)
120}
121
122fn run_repl_mode<R, W1, W2>(
123 positionals: &[&str],
124 is_tty: bool,
125 run_script: bool,
126 stdin: &mut R,
127 stdout: &mut W1,
128 stderr: &mut W2,
129) -> Result<(), Box<dyn std::error::Error>>
130where
131 R: std::io::BufRead + std::io::Read,
132 W1: std::io::Write,
133 W2: std::io::Write,
134{
135 #[cfg(any(test, not(unix)))]
136 let _ = run_script;
137
138 let path = match positionals {
139 [] => Path::new(".dev_shell.bin"),
140 [p] => Path::new(p),
141 _ => {
142 writeln!(stderr, "usage: dev_shell [options] [path]")?;
143 return Err(Box::new(std::io::Error::other("usage")));
144 }
145 };
146 #[cfg(unix)]
147 vm::export_devshell_workspace_root_env();
148 let vm_session = vm_session_for_entry(stderr)?;
149
150 #[cfg(all(unix, not(test)))]
151 if vm::should_delegate_lima_shell(&vm_session, is_tty, run_script) {
152 let vfs = Rc::new(RefCell::new(Vfs::new()));
153 vm_session
154 .borrow_mut()
155 .ensure_ready(&vfs.borrow(), vfs.borrow().cwd())
156 .map_err(|e| {
157 Box::new(std::io::Error::other(e.to_string())) as Box<dyn std::error::Error>
158 })?;
159 let err = vm_session.borrow().exec_lima_interactive_shell();
160 return Err(Box::new(err));
161 }
162
163 let vfs: Rc<RefCell<Vfs>> = if cfg!(unix) && vm_session.borrow().is_host_only() && !cfg!(test) {
164 let root = vm::vm_workspace_host_root();
165 std::fs::create_dir_all(&root)?;
166 Rc::new(RefCell::new(Vfs::new_host_root(root)?))
167 } else {
168 let v = match serialization::load_from_file(path) {
169 Ok(v) => v,
170 Err(e) => {
171 if e.kind() == io::ErrorKind::NotFound {
172 if positionals.len() > 1 {
173 let _ = writeln!(stderr, "File not found, starting with empty VFS");
174 }
175 } else {
176 let _ = writeln!(stderr, "Failed to load {}: {}", path.display(), e);
177 }
178 Vfs::new()
179 }
180 };
181 Rc::new(RefCell::new(v))
182 };
183 if vm_session.borrow().is_guest_primary() {
184 if let Err(e) = session_store::apply_guest_primary_startup(&mut vfs.borrow_mut(), path) {
185 let _ = writeln!(stderr, "dev_shell: guest-primary session: {e}");
186 }
187 }
188 repl::run(&vfs, &vm_session, is_tty, path, stdin, stdout, stderr).map_err(|()| {
189 Box::new(std::io::Error::other("repl error")) as Box<dyn std::error::Error>
190 })?;
191 Ok(())
192}
193
194pub fn run_main_from_args<R, W1, W2>(
199 args: &[String],
200 is_tty: bool,
201 stdin: &mut R,
202 stdout: &mut W1,
203 stderr: &mut W2,
204) -> Result<(), Box<dyn std::error::Error>>
205where
206 R: std::io::BufRead + std::io::Read,
207 W1: std::io::Write,
208 W2: std::io::Write,
209{
210 let positionals: Vec<&str> = args
211 .iter()
212 .skip(1)
213 .filter(|a| *a != "-e" && *a != "-f")
214 .map(String::as_str)
215 .collect();
216 let set_e = args.iter().skip(1).any(|a| a == "-e");
217 let run_script = args.iter().skip(1).any(|a| a == "-f");
218
219 if run_script {
220 run_script_mode(&positionals, set_e, stdin, stdout, stderr)
221 } else {
222 run_repl_mode(&positionals, is_tty, run_script, stdin, stdout, stderr)
223 }
224}
225
226pub fn run_with<R, W1, W2>(
231 args: &[String],
232 stdin: &mut R,
233 stdout: &mut W1,
234 stderr: &mut W2,
235) -> Result<(), RunWithError>
236where
237 R: std::io::BufRead + std::io::Read,
238 W1: std::io::Write,
239 W2: std::io::Write,
240{
241 let path = match args {
242 [] | [_] => Path::new(".dev_shell.bin"),
243 [_, path] => Path::new(path),
244 _ => {
245 let _ = writeln!(stderr, "usage: dev_shell [path]");
246 return Err(RunWithError::Usage);
247 }
248 };
249 #[cfg(unix)]
250 vm::export_devshell_workspace_root_env();
251 let vm_session = if cfg!(unix) {
252 vm::try_session_rc_or_host(stderr)
253 } else {
254 vm::try_session_rc(stderr).map_err(|_| RunWithError::ReplFailed)?
255 };
256 let vfs: Rc<RefCell<Vfs>> = if cfg!(unix) && vm_session.borrow().is_host_only() && !cfg!(test) {
257 let root = vm::vm_workspace_host_root();
258 std::fs::create_dir_all(&root).map_err(|_| RunWithError::ReplFailed)?;
259 Rc::new(RefCell::new(
260 Vfs::new_host_root(root).map_err(|_| RunWithError::ReplFailed)?,
261 ))
262 } else {
263 Rc::new(RefCell::new(
264 serialization::load_from_file(path).unwrap_or_default(),
265 ))
266 };
267 if vm_session.borrow().is_guest_primary() {
268 if let Err(e) = session_store::apply_guest_primary_startup(&mut vfs.borrow_mut(), path) {
269 let _ = writeln!(stderr, "dev_shell: guest-primary session: {e}");
270 }
271 }
272 repl::run(&vfs, &vm_session, false, path, stdin, stdout, stderr)
273 .map_err(|()| RunWithError::ReplFailed)
274}
275
276#[cfg(test)]
277mod tests;