1#![allow(rustdoc::invalid_html_tags)]
11
12use std::ffi::OsString;
13use std::sync::OnceLock;
14
15use clap::Parser;
16
17pub mod assemble;
18pub mod cleanup;
19pub mod csv_join;
20pub mod cues;
21pub mod daw;
22pub mod db_profile;
23pub mod episode_summary;
24pub mod gui;
25pub mod import;
26pub mod init;
27pub mod master;
28pub mod migrate;
29pub mod migrate_workspace;
30pub mod mp3_hash;
31pub mod parse;
32pub mod parsed_csv;
33pub mod produce;
34pub mod publish;
35pub mod regen;
36pub mod removal;
37pub mod remove_episode;
38pub mod remove_show;
39pub mod sample;
40pub mod scan;
41pub mod setup;
42pub mod sfx;
43pub mod sfx_csv;
44pub mod sfx_hydrate;
45pub mod sfx_impact;
46pub mod sfx_lib;
47pub mod sfx_match;
48pub mod sfx_restore;
49pub mod splice;
50pub mod status;
51pub mod stem_compare;
52pub mod stem_log;
53pub mod stem_verify;
54pub mod studio_onboard;
55pub mod use_cmd;
56pub mod voices;
57
58static PROG: OnceLock<String> = OnceLock::new();
61
62pub fn set_prog(prog: &str) {
63 let _ = PROG.set(prog.to_string());
64}
65
66pub fn prog() -> &'static str {
67 PROG.get().map(String::as_str).unwrap_or("xil")
68}
69
70pub fn argv_line(args: &[OsString]) -> String {
72 let mut s = prog().to_string();
73 for a in args {
74 s.push(' ');
75 s.push_str(&a.to_string_lossy());
76 }
77 s
78}
79
80#[derive(Debug)]
86pub struct SysExit(pub String);
87
88impl std::fmt::Display for SysExit {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 f.write_str(&self.0)
91 }
92}
93
94impl std::error::Error for SysExit {}
95
96pub fn finish(result: anyhow::Result<i32>) -> anyhow::Result<i32> {
99 match result {
100 Err(e) => match e.downcast::<SysExit>() {
101 Ok(SysExit(msg)) => {
102 if prog().starts_with("xil ") {
103 xil_core::log::error(&msg);
104 } else {
105 eprintln!("{msg}");
106 }
107 Ok(1)
108 }
109 Err(other) => Err(other),
110 },
111 ok => ok,
112 }
113}
114
115pub fn parse_or_exit<T: Parser>(prog: &str, args: &[OsString]) -> Result<T, i32> {
122 let argv = std::iter::once(OsString::from(prog)).chain(args.iter().cloned());
123 match T::try_parse_from(argv) {
124 Ok(t) => Ok(t),
125 Err(e) => {
126 let _ = e.print();
127 Err(e.exit_code())
128 }
129 }
130}
131
132pub fn get_or_empty(
135 map: &serde_json::Map<String, serde_json::Value>,
136 key: &str,
137) -> serde_json::Value {
138 map.get(key)
139 .cloned()
140 .unwrap_or(serde_json::Value::String(String::new()))
141}
142
143pub fn get_or_blank(
146 map: &serde_json::Map<String, serde_json::Value>,
147 key: &str,
148) -> serde_json::Value {
149 use serde_json::Value;
150 match map.get(key) {
151 Some(v) if truthy(v) => v.clone(),
152 _ => Value::String(String::new()),
153 }
154}
155
156pub fn py_str(v: &serde_json::Value) -> String {
160 use serde_json::Value;
161 match v {
162 Value::Null => "None".into(),
163 Value::Bool(true) => "True".into(),
164 Value::Bool(false) => "False".into(),
165 Value::String(s) => s.clone(),
166 other => xil_core::pycsv::cell(other),
167 }
168}
169
170pub fn truthy(v: &serde_json::Value) -> bool {
171 use serde_json::Value;
172 match v {
173 Value::Null | Value::Bool(false) => false,
174 Value::Bool(true) => true,
175 Value::Number(n) => n.as_f64() != Some(0.0),
176 Value::String(s) => !s.is_empty(),
177 Value::Array(a) => !a.is_empty(),
178 Value::Object(o) => !o.is_empty(),
179 }
180}
181
182#[cfg(test)]
184pub static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());