Skip to main content

xil/
main.rs

1//! `xil` — unified dispatcher for the pipeline.
2//!
3//! Behaviour mirrors the Python `xil.py`: `xil <command> [args...]`,
4//! `--help` lists the commands, `--version` prints the version, an unknown
5//! command exits 2. The `xil-<command>` aliases work through `argv[0]`.
6
7mod cmd;
8mod commands;
9mod man;
10mod mix;
11mod sfxgen;
12mod tts;
13mod workers;
14
15use std::env;
16use std::ffi::OsString;
17use std::io::Write;
18use std::path::Path;
19use std::process;
20
21const VERSION: &str = env!("CARGO_PKG_VERSION");
22
23fn main() {
24    let code = match real_main() {
25        Ok(code) => code,
26        Err(err) => {
27            eprintln!("[ERROR] {err:#}");
28            1
29        }
30    };
31    process::exit(code);
32}
33
34fn real_main() -> anyhow::Result<i32> {
35    let mut argv: Vec<OsString> = env::args_os().collect();
36    let program = argv.remove(0);
37
38    // `xil-parse ...` behaves as `xil parse ...`.
39    if let Some(alias) = alias_command(&program) {
40        argv.insert(0, OsString::from(alias));
41    }
42
43    // The Python dispatcher configures logging before it looks at argv, so
44    // its own answers (help, version, an unknown command) still create the
45    // day's log file. Commands initialise the logger under their own stage.
46    let Some(first) = argv.first().map(|a| a.to_string_lossy().into_owned()) else {
47        xil_core::log::init("xil");
48        print!("{}", commands::help_text());
49        return Ok(0);
50    };
51
52    match first.as_str() {
53        "-h" | "--help" | "help" => {
54            xil_core::log::init("xil");
55            print!("{}", commands::help_text());
56            return Ok(0);
57        }
58        "-V" | "--version" | "version" => {
59            xil_core::log::init("xil");
60            println!("{VERSION}");
61            return Ok(0);
62        }
63        // Hidden: regenerate the man pages (default man/man1).
64        "--generate-man" => {
65            let dir = argv
66                .get(1)
67                .map(std::path::PathBuf::from)
68                .unwrap_or_else(|| "man/man1".into());
69            let n = man::generate(&dir)?;
70            println!("wrote {n} man pages to {}", dir.display());
71            return Ok(0);
72        }
73        _ => {}
74    }
75
76    // What Python's sys.argv[0] would be: "xil-parse" via an alias, else
77    // "xil parse" (the dispatcher rewrites argv[0] that way before handing off).
78    match alias_command(&program) {
79        Some(_) => cmd::set_prog(
80            &Path::new(&program)
81                .file_name()
82                .unwrap_or_default()
83                .to_string_lossy(),
84        ),
85        None => cmd::set_prog(&format!("xil {first}")),
86    }
87
88    let Some(spec) = commands::find(&first) else {
89        xil_core::log::init("xil");
90        xil_core::log::error(&format!("Unknown command: {first}"));
91        eprint!("{}", commands::help_text());
92        return Ok(2);
93    };
94    let args = &argv[1..];
95
96    // `xil status --toolchain`: which build this is and what the ML workers
97    // will run under.
98    if spec.name == "status" && args.iter().any(|a| a == "--toolchain") {
99        let mut out = std::io::stdout().lock();
100        writeln!(out, "rxil {VERSION}")?;
101        write!(out, "{}", workers::describe())?;
102        writeln!(out, "commands: {}", commands::COMMANDS.len())?;
103        return Ok(0);
104    }
105
106    (spec.run)(args)
107}
108
109/// `Some("parse")` for a program named `xil-parse`, `None` for plain `xil`.
110fn alias_command(program: &OsString) -> Option<String> {
111    let stem = Path::new(program).file_name()?.to_str()?;
112    let stem = stem.strip_suffix(".exe").unwrap_or(stem);
113    let rest = stem.strip_prefix("xil-")?;
114    if rest.is_empty() {
115        return None;
116    }
117    Some(rest.to_string())
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn alias_from_program_name() {
126        assert_eq!(
127            alias_command(&OsString::from("/usr/local/bin/xil-parse")),
128            Some("parse".into())
129        );
130        assert_eq!(
131            alias_command(&OsString::from("xil-sfx-match.exe")),
132            Some("sfx-match".into())
133        );
134        assert_eq!(alias_command(&OsString::from("xil")), None);
135        assert_eq!(alias_command(&OsString::from("xil-")), None);
136        assert_eq!(alias_command(&OsString::from("target/debug/xil")), None);
137    }
138}