command_stream/development/
repl.rs1use super::child::ManagedChild;
2use std::io;
3use std::path::PathBuf;
4use std::process::{ExitStatus, Stdio};
5use std::sync::atomic::{AtomicU64, Ordering};
6use tokio::process::Command;
7
8static NEXT_SESSION: AtomicU64 = AtomicU64::new(0);
9
10#[derive(Debug, Clone)]
12pub struct ReplOptions {
13 pub executable: PathBuf,
15 pub crate_path: Option<PathBuf>,
17 pub disable_readline: bool,
19}
20
21impl Default for ReplOptions {
22 fn default() -> Self {
23 Self {
24 executable: std::env::var_os("COMMAND_STREAM_EVCXR")
25 .map(PathBuf::from)
26 .unwrap_or_else(|| "evcxr".into()),
27 crate_path: Some(PathBuf::from(env!("CARGO_MANIFEST_DIR")))
28 .filter(|path| path.join("Cargo.toml").is_file()),
29 disable_readline: false,
30 }
31 }
32}
33
34struct SessionDirectory(PathBuf);
35impl Drop for SessionDirectory {
36 fn drop(&mut self) {
37 let _ = std::fs::remove_dir_all(&self.0);
38 }
39}
40
41pub struct RustRepl {
43 process: ManagedChild,
44 _session: SessionDirectory,
45}
46
47impl RustRepl {
48 pub async fn wait(&mut self) -> io::Result<ExitStatus> {
49 self.process.child.wait().await
50 }
51 pub async fn close(&mut self) -> io::Result<()> {
52 self.process.stop().await
53 }
54}
55
56pub fn start_repl(options: ReplOptions) -> io::Result<RustRepl> {
62 let path = std::env::temp_dir().join(format!(
63 ".command-stream-repl-{}-{}-{}",
64 std::process::id(),
65 std::time::SystemTime::now()
66 .duration_since(std::time::UNIX_EPOCH)
67 .unwrap_or_default()
68 .as_nanos(),
69 NEXT_SESSION.fetch_add(1, Ordering::Relaxed)
70 ));
71 std::fs::create_dir(&path)?;
72 let session = SessionDirectory(path);
73 let dependency = match options.crate_path {
74 Some(path) => format!(
75 "path = {}",
76 serde_json::to_string(&path.canonicalize()?.to_string_lossy())?
77 ),
78 None => format!("version = {:?}", env!("CARGO_PKG_VERSION")),
79 };
80 let facade = session.0.join("facade");
81 std::fs::create_dir(&facade)?;
82 std::fs::write(facade.join("Cargo.toml"),
83 format!("[package]\nname = \"command-stream-repl-context\"\nversion = \"0.0.0\"\nedition = \"2021\"\n[lib]\npath = \"lib.rs\"\ncrate-type = [\"dylib\"]\n[dependencies]\ncommand_stream = {{ package = \"command-stream\", {dependency} }}\ntokio = {{ version = \"1\", features = [\"full\"] }}\n"))?;
84 std::fs::write(
85 facade.join("lib.rs"),
86 "pub use command_stream::*;\npub use tokio;\n",
87 )?;
88 std::fs::write(
89 session.0.join("init.evcxr"),
90 format!(":opt 0\n:dep command_stream = {{ package = \"command-stream-repl-context\", path = {} }}\n:dep tokio = {{ version = \"1\", features = [\"full\"] }}\n", serde_json::to_string(&facade.to_string_lossy())?),
92 )?;
93 std::fs::write(session.0.join("prelude.rs"), "use command_stream::*;\n")?;
94 let mut command = Command::new(options.executable);
95 command
96 .env("EVCXR_CONFIG_DIR", &session.0)
97 .stdin(Stdio::inherit())
98 .stdout(Stdio::inherit())
99 .stderr(Stdio::inherit());
100 if options.disable_readline {
101 command.arg("--disable-readline");
102 }
103 let process = ManagedChild::spawn(&mut command, !options.disable_readline).map_err(|error| {
104 io::Error::new(error.kind(), format!("Cannot start Rust REPL: {error}. Install Rust sources with `rustup component add rust-src` and the evaluator with `cargo install --locked evcxr_repl`, or set COMMAND_STREAM_EVCXR."))
105 })?;
106 Ok(RustRepl {
107 process,
108 _session: session,
109 })
110}