gioser

tawasuyu/brahman

Rust 570TOML 215Markdown 68JSON 198261 Definiciones

⎇ 762bf95dfd770cc8bf51f21605bc2cc6cc88ea76 crates/init/arje-absorb/src/model.rs Rust · 103 líneas · 3.1 KB Historia Quién tocó qué crudo

feat(arje): arje-absorb — absorbe otros inits a una Semilla brahman 762bf95df · sergio ·

1//! El modelo intermedio: un servicio descubierto en el init ajeno,
2//! independiente de su formato de origen. Cada absorber
3//! (`sysvinit`, `runit`, `dinit`, `openrc`) produce [`ForeignService`]s;
4//! `card` los traduce a Cards brahman.
5
6/// Un servicio leído de la configuración de otro init.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct ForeignService {
9    /// Etiqueta del servicio — pasa a ser el `label` de su Card.
10    pub name: String,
11    /// Ejecutable (path absoluto, tal como existirá en el sistema).
12    pub exec: String,
13    /// Argumentos del ejecutable.
14    pub argv: Vec<String>,
15    /// Variables de entorno (clave, valor) a inyectarle.
16    pub env: Vec<(String, String)>,
17    /// Daemon supervisado, o tarea de una sola ejecución.
18    pub kind: ServiceKind,
19}
20
21/// Cómo trata arje a un servicio absorbido.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum ServiceKind {
24    /// Servicio de larga duración: arje lo supervisa y reinicia.
25    Daemon,
26    /// Tarea puntual: corre una vez y termina.
27    OneShot,
28}
29
30/// Parte una línea de comando en `(exec, argv)`, respetando comillas
31/// simples y dobles. Best-effort: no expande variables ni globs —
32/// suficiente para los campos `process`/`command` de los inits
33/// clásicos. `None` si la línea no tiene ningún token.
34pub fn split_command(line: &str) -> Option<(String, Vec<String>)> {
35    let mut tokens: Vec<String> = Vec::new();
36    let mut cur = String::new();
37    let mut in_token = false;
38    let mut chars = line.chars().peekable();
39    while let Some(c) = chars.next() {
40        match c {
41            '"' | '\'' => {
42                in_token = true;
43                let quote = c;
44                for q in chars.by_ref() {
45                    if q == quote {
46                        break;
47                    }
48                    cur.push(q);
49                }
50            }
51            c if c.is_whitespace() => {
52                if in_token {
53                    tokens.push(std::mem::take(&mut cur));
54                    in_token = false;
55                }
56            }
57            c => {
58                in_token = true;
59                cur.push(c);
60            }
61        }
62    }
63    if in_token {
64        tokens.push(cur);
65    }
66    if tokens.is_empty() {
67        return None;
68    }
69    let exec = tokens.remove(0);
70    Some((exec, tokens))
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn splits_plain_command() {
79        let (exec, argv) = split_command("/usr/sbin/sshd -D -e").unwrap();
80        assert_eq!(exec, "/usr/sbin/sshd");
81        assert_eq!(argv, ["-D", "-e"]);
82    }
83
84    #[test]
85    fn respects_double_quotes() {
86        let (exec, argv) = split_command(r#"/bin/foo "arg con espacios" bar"#).unwrap();
87        assert_eq!(exec, "/bin/foo");
88        assert_eq!(argv, ["arg con espacios", "bar"]);
89    }
90
91    #[test]
92    fn respects_single_quotes() {
93        let (exec, argv) = split_command("/bin/sh -c 'echo hola mundo'").unwrap();
94        assert_eq!(exec, "/bin/sh");
95        assert_eq!(argv, ["-c", "echo hola mundo"]);
96    }
97
98    #[test]
99    fn empty_line_yields_none() {
100        assert!(split_command("   ").is_none());
101        assert!(split_command("").is_none());
102    }
103}