isabelle_client/
server.rs

1use std::{
2    io::{self, BufRead, BufReader},
3    process::{Command, ExitStatus, Stdio},
4};
5
6/// A running Isabelle server instance.
7pub struct IsabelleServer {
8    handle: Option<std::process::Child>,
9    port: u32,
10    passwd: String,
11    name: String,
12}
13
14impl IsabelleServer {
15    /// Returns the port of the running server instance.
16    pub fn port(&self) -> u32 {
17        self.port
18    }
19
20    /// Returns the password of the running server instance.
21    pub fn password(&self) -> &str {
22        &self.passwd
23    }
24
25    pub fn name(&self) -> &str {
26        &self.name
27    }
28
29    /// Kills the running server instance, if it was started by this process.
30    pub fn exit(&mut self) -> io::Result<()> {
31        exit(&self.name)?;
32
33        // Wait for the Child to terminate
34        if let Some(mut handle) = self.handle.take() {
35            if let Ok(None) = handle.try_wait() {
36                handle.kill()?;
37            }
38        }
39
40        Ok(())
41    }
42}
43
44/// Runs the Isabelle server and returns an [IsabelleServer] instance containing port and password.
45/// If a server is already running with the given name, the function returns the port and password of the instance.
46/// If no server is running with the given name, the function starts a new server.
47/// The server not shut down, even if this terminates.
48/// To stop it, you need to call [IsabelleServer::exit] or [exit].
49///
50/// # Arguments
51///
52/// - `name` - The name of the server instance (default is `isabelle`).
53///
54/// # Returns
55///
56/// An [IsabelleServer] instance containing name, port, and password.
57///
58///
59/// # Example
60///
61/// ```rust
62/// use isabelle_client::server::run_server;
63/// let mut server = run_server(Some("test")).unwrap();
64/// assert!(server.port() > 0);
65/// assert!(!server.password().is_empty());
66/// server.exit();
67/// ```
68pub fn run_server(name: Option<&str>) -> io::Result<IsabelleServer> {
69    let name = name.unwrap_or("isabelle").to_string();
70    let mut handle = Command::new("isabelle")
71        .arg("server")
72        .arg("-n")
73        .arg(&name)
74        .stdout(Stdio::piped())
75        .spawn()?;
76
77    let stdout = handle.stdout.take().unwrap();
78
79    let mut stdout_buf = vec![];
80    let newline = b'\n';
81    // Read until newline
82    BufReader::new(stdout).read_until(newline, &mut stdout_buf)?;
83
84    let stdout_str = String::from_utf8(stdout_buf)
85        .unwrap()
86        .replace('\\', "")
87        .trim()
88        .to_string();
89
90    let addr_re = regex::Regex::new(r#".* = .*:(.*) \(password "(.*)"\)"#).unwrap();
91    let caps = addr_re.captures(&stdout_str).unwrap();
92
93    let port = caps.get(1).unwrap().as_str().parse::<u32>().unwrap();
94    let passwd = caps.get(2).unwrap().as_str().to_owned();
95
96    let server = if handle.try_wait()?.is_none() {
97        IsabelleServer {
98            handle: Some(handle),
99            port,
100            passwd,
101            name,
102        }
103    } else {
104        IsabelleServer {
105            handle: None,
106            port,
107            passwd,
108            name,
109        }
110    };
111
112    Ok(server)
113}
114
115/// Exists the Isabelle server with the given name.
116pub fn exit(name: &str) -> io::Result<ExitStatus> {
117    let mut child = Command::new("isabelle")
118        .arg("server")
119        .arg("-n")
120        .arg(name)
121        .arg("-x")
122        .spawn()?;
123    child.wait()
124}
125
126mod tests {
127
128    #![allow(unused_imports)] // rust-analyzer thinks these are unused, but are not
129    use super::run_server;
130    use serial_test::serial;
131
132    #[test]
133    #[serial]
134    fn test_run_server() {
135        let mut server = run_server(Some("test")).unwrap();
136        assert!(server.port > 0);
137        assert!(!server.passwd.is_empty());
138        server.exit().unwrap();
139    }
140}