1use heck::CamelCase;
2use std::fs;
3use std::path::Path;
4use std::process::Command;
5use std::{env, path::PathBuf};
6use tempfile::NamedTempFile;
7
8pub fn build_dsp(dsp_file: &str) {
9    let out_dir = env::var_os("OUT_DIR").unwrap();
10    let dest_path = Path::new(&out_dir).join("dsp.rs");
11    FaustBuilder::new(dsp_file, dest_path.to_str().unwrap()).build()
12}
13
14pub fn build_dsp_to_destination(dsp_file: &str, dest_path: &str) {
15    FaustBuilder::new(dsp_file, dest_path).build()
16}
17
18pub struct FaustBuilder {
19    faust_path: Option<String>,
20    in_file: String,
21    out_file: String,
22    arch_file: Option<String>,
23    module_name: String,
25    struct_name: Option<String>,
27    use_double: bool,
28    faust_args: Vec<String>,
29}
30
31impl Default for FaustBuilder {
32    fn default() -> Self {
33        Self {
34            faust_path: None,
35            in_file: "".into(),
36            out_file: "".into(),
37            arch_file: None,
38            struct_name: None,
39            module_name: "dsp".into(),
40            use_double: false,
41            faust_args: vec![],
42        }
43    }
44}
45
46impl FaustBuilder {
47    pub fn new(in_file: &str, out_file: &str) -> Self {
48        Self {
49            in_file: in_file.to_string(),
50            out_file: out_file.to_string(),
51            ..Default::default()
52        }
53    }
54
55    pub fn set_struct_name(mut self, struct_name: &str) -> Self {
56        self.struct_name = Some(struct_name.to_string());
57        self
58    }
59    pub fn set_module_name(mut self, module_name: &str) -> Self {
60        self.module_name = module_name.to_string();
61        self
62    }
63
64    pub fn set_use_double(mut self, use_double: bool) -> Self {
65        self.use_double = use_double;
66        self
67    }
68
69    pub fn set_faust_path(mut self, faust_path: &str) -> Self {
70        self.faust_path = Some(faust_path.to_string());
71        self
72    }
73    pub fn set_arch_file(mut self, arch_file: &str) -> Self {
74        self.arch_file = Some(arch_file.to_string());
75        self
76    }
77    pub fn faust_arg(mut self, arg: &str) -> Self {
79        self.faust_args.push(arg.to_string());
80        self
81    }
82
83    pub fn build(&self) {
84        eprintln!("cargo:rerun-if-changed={}", self.in_file);
85
86        let dest_path = PathBuf::from(&self.out_file);
87
88        let default_arch = NamedTempFile::new().expect("failed creating temporary file");
89        let template_code = include_str!("../faust-template.rs");
90        fs::write(default_arch.path(), template_code).expect("failed writing temporary file");
91        let default_template = &default_arch.path().to_str().unwrap().to_owned();
92
93        let template_file = match &self.arch_file {
94            Some(arch_file) => arch_file,
95            None => default_template,
96        };
97
98        let target_file = NamedTempFile::new().expect("failed creating temporary file");
99
100        let faust = self.faust_path.clone().unwrap_or("faust".to_owned());
102        let mut output = Command::new(faust);
103
104        let struct_name = match &self.struct_name {
105            Some(struct_name) => struct_name.clone(),
106            None => {
107                let dsp_path = PathBuf::from(&self.in_file);
108                dsp_path
109                    .file_stem()
110                    .unwrap()
111                    .to_string_lossy()
112                    .to_string()
113                    .to_camel_case()
114            }
115        };
116
117        output
118            .arg("-a")
119            .arg(template_file)
120            .arg("-lang")
121            .arg("rust")
122            .arg("-t")
123            .arg("0")
124            .arg("-cn")
125            .arg(&struct_name);
126
127        if self.use_double {
128            output.arg("-double");
129        }
130
131        for arg in self.faust_args.clone() {
132            output.arg(arg);
133        }
134
135        output.arg(&self.in_file).arg("-o").arg(target_file.path());
136
137        let output = output.output().expect("Failed to execute command");
138        if !output.status.success() {
143            panic!(
144                "faust compilation failed: {}",
145                String::from_utf8(output.stderr).unwrap()
146            );
147        }
148
149        let dsp_code = fs::read(target_file).unwrap();
150        let dsp_code = String::from_utf8(dsp_code).unwrap();
151        let dsp_code = dsp_code.replace("<<moduleName>>", &self.module_name);
152        let dsp_code = dsp_code.replace("<<structName>>", &struct_name);
153
154        fs::write(&dest_path, dsp_code).expect("failed to write to destination path");
155
156        eprintln!("Wrote module:\n{}", dest_path.to_str().unwrap());
157    }
158    pub fn build_xml(&self, out_dir: &str) {
159        eprintln!("cargo:rerun-if-changed={}", self.in_file);
160        let mut output = Command::new(self.faust_path.clone().unwrap_or("faust".to_owned()));
161
162        let struct_name = match &self.struct_name {
163            Some(struct_name) => struct_name.clone(),
164            None => {
165                let dsp_path = PathBuf::from(&self.in_file);
166                dsp_path
167                    .file_stem()
168                    .unwrap()
169                    .to_string_lossy()
170                    .to_string()
171                    .to_camel_case()
172            }
173        };
174
175        output
176            .arg("-lang")
177            .arg("rust")
178            .arg("-xml")
179            .arg("--output-dir")
180            .arg(out_dir)
181            .arg("-t")
182            .arg("0")
183            .arg("-cn")
184            .arg(&struct_name);
185
186        if self.use_double {
187            output.arg("-double");
188        }
189
190        for arg in self.faust_args.clone() {
191            output.arg(arg);
192        }
193        let output = output
194            .arg(&self.in_file)
195            .output()
196            .expect("Failed to execute command");
197        if !output.status.success() {
202            panic!(
203                "faust compilation failed: {}",
204                String::from_utf8(output.stderr).unwrap()
205            );
206        }
207    }
208}