xtask_todo_lib/devshell/
mod.rs1pub mod command;
4pub mod completion;
5pub mod host_text;
6pub mod parser;
7pub mod sandbox;
8pub mod script;
9pub mod serialization;
10pub mod todo_io;
11pub mod vfs;
12pub mod vm;
13
14mod repl;
15
16use std::cell::RefCell;
17use std::io::{self, BufReader, IsTerminal};
18use std::path::Path;
19use std::rc::Rc;
20
21use vfs::Vfs;
22
23#[derive(Debug)]
25pub enum RunWithError {
26 Usage,
27 ReplFailed,
28}
29
30impl std::fmt::Display for RunWithError {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 match self {
33 Self::Usage => f.write_str("usage error"),
34 Self::ReplFailed => f.write_str("repl failed"),
35 }
36 }
37}
38
39impl std::error::Error for RunWithError {}
40
41pub fn run_main() -> Result<(), Box<dyn std::error::Error>> {
46 let args: Vec<String> = std::env::args().collect();
47 let is_tty = io::stdin().is_terminal();
48 let mut stdin = BufReader::new(io::stdin());
49 let mut stdout = io::stdout();
50 let mut stderr = io::stderr();
51 run_main_from_args(&args, is_tty, &mut stdin, &mut stdout, &mut stderr)
52}
53
54pub fn run_main_from_args<R, W1, W2>(
59 args: &[String],
60 is_tty: bool,
61 stdin: &mut R,
62 stdout: &mut W1,
63 stderr: &mut W2,
64) -> Result<(), Box<dyn std::error::Error>>
65where
66 R: std::io::BufRead + std::io::Read,
67 W1: std::io::Write,
68 W2: std::io::Write,
69{
70 let positionals: Vec<&str> = args
71 .iter()
72 .skip(1)
73 .filter(|a| *a != "-e" && *a != "-f")
74 .map(String::as_str)
75 .collect();
76 let set_e = args.iter().skip(1).any(|a| a == "-e");
77 let run_script = args.iter().skip(1).any(|a| a == "-f");
78
79 if run_script {
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 let vfs = match serialization::load_from_file(bin_path) {
94 Ok(v) => v,
95 Err(e) => {
96 if e.kind() != io::ErrorKind::NotFound {
97 let _ = writeln!(stderr, "Failed to load {}: {}", bin_path.display(), e);
98 }
99 Vfs::new()
100 }
101 };
102 let vfs = Rc::new(RefCell::new(vfs));
103 let Ok(vm_session) = vm::try_session_rc(stderr) else {
104 return Err(Box::new(std::io::Error::other("vm session")));
105 };
106 script::run_script(
107 &vfs,
108 &vm_session,
109 &script_src,
110 bin_path,
111 set_e,
112 stdin,
113 stdout,
114 stderr,
115 )
116 .map_err(|e| Box::new(std::io::Error::other(e.to_string())) as Box<dyn std::error::Error>)
117 } else {
118 let path = match positionals.as_slice() {
119 [] => Path::new(".dev_shell.bin"),
120 [p] => Path::new(p),
121 _ => {
122 writeln!(stderr, "usage: dev_shell [options] [path]")?;
123 return Err(Box::new(std::io::Error::other("usage")));
124 }
125 };
126 let vfs = match serialization::load_from_file(path) {
127 Ok(v) => v,
128 Err(e) => {
129 if e.kind() == io::ErrorKind::NotFound {
130 if positionals.len() > 1 {
131 let _ = writeln!(stderr, "File not found, starting with empty VFS");
132 }
133 } else {
134 let _ = writeln!(stderr, "Failed to load {}: {}", path.display(), e);
135 }
136 Vfs::new()
137 }
138 };
139 let vfs = Rc::new(RefCell::new(vfs));
140 let Ok(vm_session) = vm::try_session_rc(stderr) else {
141 return Err(Box::new(std::io::Error::other("vm session")));
142 };
143 repl::run(&vfs, &vm_session, is_tty, path, stdin, stdout, stderr).map_err(|()| {
144 Box::new(std::io::Error::other("repl error")) as Box<dyn std::error::Error>
145 })?;
146 Ok(())
147 }
148}
149
150pub fn run_with<R, W1, W2>(
155 args: &[String],
156 stdin: &mut R,
157 stdout: &mut W1,
158 stderr: &mut W2,
159) -> Result<(), RunWithError>
160where
161 R: std::io::BufRead + std::io::Read,
162 W1: std::io::Write,
163 W2: std::io::Write,
164{
165 let path = match args {
166 [] | [_] => Path::new(".dev_shell.bin"),
167 [_, path] => Path::new(path),
168 _ => {
169 let _ = writeln!(stderr, "usage: dev_shell [path]");
170 return Err(RunWithError::Usage);
171 }
172 };
173 let vfs = serialization::load_from_file(path).unwrap_or_default();
174 let vfs = Rc::new(RefCell::new(vfs));
175 let vm_session = vm::try_session_rc(stderr).map_err(|()| RunWithError::ReplFailed)?;
176 repl::run(&vfs, &vm_session, false, path, stdin, stdout, stderr)
177 .map_err(|()| RunWithError::ReplFailed)
178}
179
180#[cfg(test)]
181mod tests;