muse_codes/
client_async.rs1use crate::cli::MuseExecBuilder;
9use crate::error::{Error, Result};
10use crate::io::{MusePayload, MuseRecord};
11use tokio::io::{AsyncBufReadExt, BufReader, Lines};
12use tokio::process::{Child, ChildStderr, ChildStdout};
13
14pub struct ExecRun {
16 child: Child,
17 lines: Lines<BufReader<ChildStdout>>,
18 stderr_task: tokio::task::JoinHandle<String>,
20}
21
22impl ExecRun {
23 pub async fn spawn(builder: &MuseExecBuilder) -> Result<Self> {
25 let mut child = builder.spawn().await?;
26 let stdout = child
27 .stdout
28 .take()
29 .ok_or_else(|| Error::Protocol("failed to get stdout".to_string()))?;
30 let stderr = child
31 .stderr
32 .take()
33 .ok_or_else(|| Error::Protocol("failed to get stderr".to_string()))?;
34 Ok(Self {
35 child,
36 lines: BufReader::new(stdout).lines(),
37 stderr_task: spawn_stderr_collector(stderr),
38 })
39 }
40
41 pub async fn next_record(&mut self) -> Result<Option<MuseRecord>> {
43 loop {
44 match self.lines.next_line().await? {
45 None => return Ok(None),
46 Some(line) if line.trim().is_empty() => continue,
47 Some(line) => return Ok(Some(serde_json::from_str(&line)?)),
48 }
49 }
50 }
51
52 pub async fn wait_terminal<F>(mut self, mut on_record: F) -> Result<crate::io::RunTerminal>
59 where
60 F: FnMut(&MuseRecord),
61 {
62 while let Some(record) = self.next_record().await? {
63 on_record(&record);
64 if let Ok(MusePayload::RunTerminal(t)) = record.typed_payload() {
65 return Ok(t);
66 }
67 }
68 let status = self.child.wait().await?;
69 let stderr = self.stderr_task.await.unwrap_or_default();
70 Err(Error::Protocol(format!(
71 "stream ended without run.terminal.* (exit {:?}); stderr:\n{}",
72 status.code(),
73 stderr.trim()
74 )))
75 }
76
77 pub async fn kill(&mut self) -> Result<()> {
79 self.child.kill().await?;
80 Ok(())
81 }
82}
83
84fn spawn_stderr_collector(stderr: ChildStderr) -> tokio::task::JoinHandle<String> {
85 tokio::spawn(async move {
86 let mut out = String::new();
87 let mut lines = BufReader::new(stderr).lines();
88 while let Ok(Some(line)) = lines.next_line().await {
89 #[cfg(feature = "async-client")]
90 log::debug!(target: "muse_codes::stderr", "{line}");
91 out.push_str(&line);
92 out.push('\n');
93 }
94 out
95 })
96}