use std::io::{BufRead as _, BufReader};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
#[derive(Debug, Clone)]
pub struct Supervisor {
pub server: String,
pub credential: String,
pub print_id: String,
pub image_id: String,
pub event_id: String,
pub spool_id: String,
pub file_name: String,
}
pub struct Standing {
pub at: Supervisor,
holding: Child,
}
impl Drop for Standing {
fn drop(&mut self) {
drop(self.holding.stdin.take());
if self.holding.wait().is_err() {
let _ = self.holding.kill();
}
}
}
fn repo_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
}
fn python_path(root: &Path) -> String {
let separator = if cfg!(windows) { ";" } else { ":" };
let justfile = std::fs::read_to_string(root.join("justfile")).expect("the justfile reads");
justfile
.lines()
.find_map(|line| line.strip_prefix("TOOL_PACKAGES :="))
.map(|value| {
value
.trim()
.trim_matches('"')
.split(':')
.collect::<Vec<_>>()
.join(separator)
})
.expect("the justfile lists the path this repository's tools live on")
}
pub fn standing(into: &Path, refusing: bool) -> Standing {
let root = repo_root();
let mut holding = Command::new("uv")
.args([
"run",
"-q",
"python",
"-m",
"release_artifacts",
"world",
"--octoprint",
])
.args(refusing.then_some("--refusing"))
.arg("--into")
.arg(into)
.current_dir(&root)
.env("PYTHONPATH", python_path(&root))
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("the world this journey drives starts");
let mut said = String::new();
let mut reading = BufReader::new(holding.stdout.take().expect("the world says where it is"));
reading
.read_line(&mut said)
.expect("the world says where it is");
if said.trim().is_empty() {
let mut why = String::new();
if let Some(stderr) = holding.stderr.as_mut() {
let _ = std::io::Read::read_to_string(stderr, &mut why);
}
let _ = holding.kill();
panic!(
"the world did not come up. Run `just octoprint-up` first; this tier drives \
a real OctoPrint and has no fixture to fall back to.\n{why}"
);
}
let described: serde_json::Value =
serde_json::from_str(said.trim()).expect("the world says where it is, as a document");
let named = |key: &str| {
described[key]
.as_str()
.unwrap_or_else(|| panic!("the world says its `{key}`"))
.to_owned()
};
let at = Supervisor {
server: named("server"),
credential: named("credential"),
print_id: named("print_id"),
image_id: named("image_id"),
event_id: named("event_id"),
spool_id: named("spool_id"),
file_name: named("file_name"),
};
assert_eq!(
at.spool_id.len(),
36,
"the world described a spool by no identifier: {:?}",
at.spool_id
);
Standing { at, holding }
}
impl Standing {
pub fn stop(&mut self) {
drop(self.holding.stdin.take());
let _ = self.holding.wait();
}
}