use std::io;
use std::path::PathBuf;
pub use crate::{
autostart_register as register, autostart_render_registration as render_registration,
autostart_unregister as unregister,
};
#[derive(Debug, Clone, Copy)]
pub struct AutostartProgram<'a> {
pub identifier: &'a str,
pub label: &'a str,
pub description: &'a str,
pub program: &'a std::path::Path,
pub start_argument: &'a str,
pub stop_argument: &'a str,
}
#[derive(Debug)]
pub enum AutostartError {
Resolve(String),
Io(io::Error),
InitSystem(String),
}
impl std::fmt::Display for AutostartError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Resolve(detail) => write!(f, "could not resolve autostart location: {detail}"),
Self::Io(error) => write!(f, "autostart file operation failed: {error}"),
Self::InitSystem(detail) => write!(f, "init system rejected autostart: {detail}"),
}
}
}
impl std::error::Error for AutostartError {}
impl From<io::Error> for AutostartError {
fn from(error: io::Error) -> Self {
Self::Io(error)
}
}
pub type Registration = PathBuf;
#[allow(dead_code)]
pub(crate) fn shell_quote_single(value: &str) -> String {
let mut out = String::with_capacity(value.len() + 2);
out.push('\'');
for ch in value.chars() {
if ch == '\'' {
out.push_str("'\\''");
} else {
out.push(ch);
}
}
out.push('\'');
out
}
#[allow(dead_code)]
pub(crate) fn xml_escape(value: &str) -> String {
let mut out = String::with_capacity(value.len());
for ch in value.chars() {
match ch {
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'&' => out.push_str("&"),
'"' => out.push_str("""),
'\'' => out.push_str("'"),
other => out.push(other),
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shell_quote_wraps_simple_path() {
assert_eq!(shell_quote_single("/usr/bin/foo"), "'/usr/bin/foo'");
}
#[test]
fn shell_quote_escapes_embedded_single_quote() {
assert_eq!(shell_quote_single("o'malley"), "'o'\\''malley'");
}
#[test]
fn xml_escape_handles_metacharacters() {
assert_eq!(
xml_escape("a<b&c>d\"e'f"),
"a<b&c>d"e'f"
);
}
#[test]
fn a_registration_names_the_program_it_starts() {
let program = std::path::Path::new("/opt/example/bin/exampled");
let rendered = render_registration(&AutostartProgram {
identifier: "example-daemon",
label: "com.example.daemon",
description: "example supervisor",
program,
start_argument: "start",
stop_argument: "stop",
});
assert!(
rendered.contains("exampled"),
"registration must name the program: {rendered}"
);
assert!(
rendered.contains("start"),
"registration must say how to start it: {rendered}"
);
}
}