pub mod command;
pub mod completion;
pub mod host_text;
pub mod parser;
pub mod sandbox;
pub mod script;
pub mod serialization;
pub mod session_store;
pub mod todo_io;
pub mod vfs;
pub mod vm;
pub mod workspace;
mod repl;
use std::cell::RefCell;
use std::io::{self, BufReader, IsTerminal};
use std::path::Path;
use std::rc::Rc;
use vfs::Vfs;
#[derive(Debug)]
pub enum RunWithError {
Usage,
ReplFailed,
}
impl std::fmt::Display for RunWithError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Usage => f.write_str("usage error"),
Self::ReplFailed => f.write_str("repl failed"),
}
}
}
impl std::error::Error for RunWithError {}
pub fn run_main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect();
let is_tty = io::stdin().is_terminal();
let mut stdin = BufReader::new(io::stdin());
let mut stdout = io::stdout();
let mut stderr = io::stderr();
run_main_from_args(&args, is_tty, &mut stdin, &mut stdout, &mut stderr)
}
#[allow(clippy::too_many_lines)] pub fn run_main_from_args<R, W1, W2>(
args: &[String],
is_tty: bool,
stdin: &mut R,
stdout: &mut W1,
stderr: &mut W2,
) -> Result<(), Box<dyn std::error::Error>>
where
R: std::io::BufRead + std::io::Read,
W1: std::io::Write,
W2: std::io::Write,
{
let positionals: Vec<&str> = args
.iter()
.skip(1)
.filter(|a| *a != "-e" && *a != "-f")
.map(String::as_str)
.collect();
let set_e = args.iter().skip(1).any(|a| a == "-e");
let run_script = args.iter().skip(1).any(|a| a == "-f");
if run_script {
if positionals.len() != 1 {
writeln!(stderr, "usage: dev_shell [-e] -f script.dsh")?;
return Err(Box::new(std::io::Error::other("usage")));
}
let script_path = positionals[0];
let script_src = match host_text::read_host_text(Path::new(script_path)) {
Ok(s) => s,
Err(e) => {
writeln!(stderr, "dev_shell: {script_path}: {e}")?;
return Err(e.into());
}
};
let bin_path = Path::new(".dev_shell.bin");
#[cfg(unix)]
vm::export_devshell_workspace_root_env();
let vm_session = if cfg!(unix) {
vm::try_session_rc_or_host(stderr)
} else {
match vm::try_session_rc(stderr) {
Ok(s) => s,
Err(()) => return Err(Box::new(std::io::Error::other("vm session"))),
}
};
let vfs: Rc<RefCell<Vfs>> =
if cfg!(unix) && vm_session.borrow().is_host_only() && !cfg!(test) {
let root = vm::vm_workspace_host_root();
std::fs::create_dir_all(&root)?;
Rc::new(RefCell::new(Vfs::new_host_root(root)?))
} else {
let v = match serialization::load_from_file(bin_path) {
Ok(v) => v,
Err(e) => {
if e.kind() != io::ErrorKind::NotFound {
let _ =
writeln!(stderr, "Failed to load {}: {}", bin_path.display(), e);
}
Vfs::new()
}
};
Rc::new(RefCell::new(v))
};
if vm_session.borrow().is_guest_primary() {
if let Err(e) =
session_store::apply_guest_primary_startup(&mut vfs.borrow_mut(), bin_path)
{
let _ = writeln!(stderr, "dev_shell: guest-primary session: {e}");
}
}
script::run_script(
&vfs,
&vm_session,
&script_src,
bin_path,
set_e,
stdin,
stdout,
stderr,
)
.map_err(|e| Box::new(std::io::Error::other(e.to_string())) as Box<dyn std::error::Error>)
} else {
let path = match positionals.as_slice() {
[] => Path::new(".dev_shell.bin"),
[p] => Path::new(p),
_ => {
writeln!(stderr, "usage: dev_shell [options] [path]")?;
return Err(Box::new(std::io::Error::other("usage")));
}
};
#[cfg(unix)]
vm::export_devshell_workspace_root_env();
let vm_session = if cfg!(unix) {
vm::try_session_rc_or_host(stderr)
} else {
match vm::try_session_rc(stderr) {
Ok(s) => s,
Err(()) => return Err(Box::new(std::io::Error::other("vm session"))),
}
};
#[cfg(all(unix, not(test)))]
if vm::should_delegate_lima_shell(&vm_session, is_tty, run_script) {
let vfs = Rc::new(RefCell::new(Vfs::new()));
vm_session
.borrow_mut()
.ensure_ready(&vfs.borrow(), vfs.borrow().cwd())
.map_err(|e| {
Box::new(std::io::Error::other(e.to_string())) as Box<dyn std::error::Error>
})?;
let err = vm_session.borrow().exec_lima_interactive_shell();
return Err(Box::new(err));
}
let vfs: Rc<RefCell<Vfs>> =
if cfg!(unix) && vm_session.borrow().is_host_only() && !cfg!(test) {
let root = vm::vm_workspace_host_root();
std::fs::create_dir_all(&root)?;
Rc::new(RefCell::new(Vfs::new_host_root(root)?))
} else {
let v = match serialization::load_from_file(path) {
Ok(v) => v,
Err(e) => {
if e.kind() == io::ErrorKind::NotFound {
if positionals.len() > 1 {
let _ = writeln!(stderr, "File not found, starting with empty VFS");
}
} else {
let _ = writeln!(stderr, "Failed to load {}: {}", path.display(), e);
}
Vfs::new()
}
};
Rc::new(RefCell::new(v))
};
if vm_session.borrow().is_guest_primary() {
if let Err(e) = session_store::apply_guest_primary_startup(&mut vfs.borrow_mut(), path)
{
let _ = writeln!(stderr, "dev_shell: guest-primary session: {e}");
}
}
repl::run(&vfs, &vm_session, is_tty, path, stdin, stdout, stderr).map_err(|()| {
Box::new(std::io::Error::other("repl error")) as Box<dyn std::error::Error>
})?;
Ok(())
}
}
pub fn run_with<R, W1, W2>(
args: &[String],
stdin: &mut R,
stdout: &mut W1,
stderr: &mut W2,
) -> Result<(), RunWithError>
where
R: std::io::BufRead + std::io::Read,
W1: std::io::Write,
W2: std::io::Write,
{
let path = match args {
[] | [_] => Path::new(".dev_shell.bin"),
[_, path] => Path::new(path),
_ => {
let _ = writeln!(stderr, "usage: dev_shell [path]");
return Err(RunWithError::Usage);
}
};
#[cfg(unix)]
vm::export_devshell_workspace_root_env();
let vm_session = if cfg!(unix) {
vm::try_session_rc_or_host(stderr)
} else {
vm::try_session_rc(stderr).map_err(|()| RunWithError::ReplFailed)?
};
let vfs: Rc<RefCell<Vfs>> = if cfg!(unix) && vm_session.borrow().is_host_only() && !cfg!(test) {
let root = vm::vm_workspace_host_root();
std::fs::create_dir_all(&root).map_err(|_| RunWithError::ReplFailed)?;
Rc::new(RefCell::new(
Vfs::new_host_root(root).map_err(|_| RunWithError::ReplFailed)?,
))
} else {
Rc::new(RefCell::new(
serialization::load_from_file(path).unwrap_or_default(),
))
};
if vm_session.borrow().is_guest_primary() {
if let Err(e) = session_store::apply_guest_primary_startup(&mut vfs.borrow_mut(), path) {
let _ = writeln!(stderr, "dev_shell: guest-primary session: {e}");
}
}
repl::run(&vfs, &vm_session, false, path, stdin, stdout, stderr)
.map_err(|()| RunWithError::ReplFailed)
}
#[cfg(test)]
mod tests;