1use std::fmt::Display;
2use std::ops::{Deref, DerefMut};
3use std::str::FromStr;
4use std::sync::atomic::AtomicUsize;
5use std::sync::{Arc, LazyLock};
6
7use camino::Utf8Path;
8use cpclib_common::itertools::Itertools;
9use cpclib_runner::emucontrol::EMUCTRL_CMD;
10use cpclib_runner::runner::assembler::uz80::UZ80_CMD;
11use cpclib_runner::runner::assembler::{RASM_CMD, RasmVersion, SJASMPLUS_CMD, VASM_CMD};
12use cpclib_runner::runner::convgeneric::CONVGENERIC_CMD;
13use cpclib_runner::runner::disassembler::ExternDisassembler;
14use cpclib_runner::runner::disassembler::disark::{DISARK_CMD, DisarkVersion};
15use cpclib_runner::runner::emulator::caprice_forever::CAPRICEFOREVER_CMD;
16use cpclib_runner::runner::emulator::cpcemupower::CPCEMUPOWER_CMD;
17use cpclib_runner::runner::emulator::{
18 ACE_CMD, AMSPIRIT_CMD, CPCEC_CMD, SUGARBOX_V2_CMD, WINAPE_CMD
19};
20#[cfg(feature = "fap")]
21use cpclib_runner::runner::fap::FAP_CMD;
22use cpclib_runner::runner::hspcompiler::HSPC_CMD;
23use cpclib_runner::runner::impdisc::IMPDISC_CMD;
24use cpclib_runner::runner::martine::MARTINE_CMD;
25use cpclib_runner::runner::tracker::at3::AT_CMD;
26use cpclib_runner::runner::tracker::at3::extra::{
27 SongToAkg, SongToAkm, SongToAky, SongToEvents, SongToRaw, SongToSoundEffects, SongToVgm,
28 SongToWav, SongToYm
29};
30use cpclib_runner::runner::tracker::chipnsfx::CHIPNSFX_CMD;
31use fancy_regex::Regex;
32use serde::de::{Error, Visitor};
33use serde::{Deserialize, Deserializer};
34
35use crate::event::BndBuilderObserver;
36use crate::execute;
37use crate::runners::assembler::Assembler;
38use crate::runners::disassembler::Disassembler;
39use crate::runners::emulator::Emulator;
40use crate::runners::hideur::HIDEUR_CMD;
41use crate::runners::tracker::{SongConverter, Tracker};
42
43#[derive(Clone, Debug, PartialEq, Eq, Hash)]
44pub enum InnerTask {
45 Assembler(Assembler, StandardTaskArguments),
46 BndBuild(StandardTaskArguments),
47 Convgeneric(StandardTaskArguments),
48 Cp(StandardTaskArguments),
49 Crunch(StandardTaskArguments),
50 Disassembler(Disassembler, StandardTaskArguments),
51 Disc(StandardTaskArguments),
52 Echo(StandardTaskArguments),
53 Emulator(Emulator, StandardTaskArguments),
54 Extern(StandardTaskArguments),
55 #[cfg(feature = "fap")]
56 Fap(StandardTaskArguments),
57 Hideur(StandardTaskArguments),
58 HspCompiler(StandardTaskArguments),
59 ImgConverter(StandardTaskArguments),
60 ImpDsk(StandardTaskArguments),
61 Martine(StandardTaskArguments),
62 Mkdir(StandardTaskArguments),
63 Rm(StandardTaskArguments),
64 Snapshot(StandardTaskArguments),
65 SongConverter(SongConverter, StandardTaskArguments),
66 Tracker(Tracker, StandardTaskArguments),
67 Xfer(StandardTaskArguments)
68}
69
70#[derive(Debug, PartialEq, Eq, Hash, Clone)]
71pub struct Task {
72 inner: InnerTask,
73 id: usize
74}
75
76impl<'de> Deserialize<'de> for Task {
77 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
78 where D: Deserializer<'de> {
79 InnerTask::deserialize(deserializer).map(|t| t.into())
80 }
81}
82impl Display for Task {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 write!(f, "{}", &self.inner)
85 }
86}
87
88impl From<InnerTask> for Task {
89 fn from(value: InnerTask) -> Self {
90 Self {
91 inner: value,
92 id: Self::next_id()
93 }
94 }
95}
96
97impl Task {
98 pub fn execute<E: BndBuilderObserver + 'static>(
99 &self,
100 observer: &Arc<E>
101 ) -> Result<(), String> {
102 execute(self, observer)
103 }
104
105 fn next_id() -> usize {
106 static COUNTER: AtomicUsize = AtomicUsize::new(1);
107
108 unsafe { COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed) }
109 }
110
111 pub fn id(&self) -> usize {
112 self.id
113 }
114
115 pub fn new_basm(args: &str) -> Self {
116 InnerTask::new_basm(args).into()
117 }
118
119 pub fn new_bndbuild(args: &str) -> Self {
120 InnerTask::new_bndbuild(args).into()
121 }
122
123 pub fn new_echo(args: &str) -> Self {
124 InnerTask::new_echo(args).into()
125 }
126
127 pub fn new_imgconverter(args: &str) -> Self {
128 InnerTask::new_imgconverter(args).into()
129 }
130
131 pub fn new_rm(args: &str) -> Self {
132 InnerTask::new_rm(args).into()
133 }
134
135 pub fn set_ignore_errors(mut self, flag: bool) -> Self {
136 let new = self.inner.clone().set_ignore_errors(flag);
137 self.inner = new;
138 self
139 }
140}
141
142impl Deref for Task {
143 type Target = InnerTask;
144
145 fn deref(&self) -> &Self::Target {
146 &self.inner
147 }
148}
149
150impl DerefMut for Task {
151 fn deref_mut(&mut self) -> &mut Self::Target {
152 &mut self.inner
153 }
154}
155
156pub const EMUCTRL_CMDS: &[&str] = &[EMUCTRL_CMD, "emu", "emuctrl", "emucontrol"];
158pub const ACE_CMDS: &[&str] = &[ACE_CMD, "acedl"];
159pub const WINAPE_CMDS: &[&str] = &[WINAPE_CMD];
160pub const CPCEC_CMDS: &[&str] = &[CPCEC_CMD];
161pub const AMSPIRIT_CMDS: &[&str] = &[AMSPIRIT_CMD];
162pub const SUGARBOX_CMDS: &[&str] = &[SUGARBOX_V2_CMD];
163pub const CPCEMUPOWER_CMDS: &[&str] = &[CPCEMUPOWER_CMD];
164pub const CAPRICEFOREVER_CMDS: &[&str] = &[CAPRICEFOREVER_CMD];
165
166pub const BASM_CMDS: &[&str] = &["basm", "assemble"];
167pub const ORGAMS_CMDS: &[&str] = &["orgams"];
168pub const RASM_CMDS: &[&str] = &[RASM_CMD];
169pub const SJASMPLUS_CMDS: &[&str] = &[SJASMPLUS_CMD];
170pub const UZ80_CMDS: &[&str] = &[UZ80_CMD];
171pub const VASM_CMDS: &[&str] = &[VASM_CMD];
172
173pub const BDASM_CMDS: &[&str] = &["bdasm", "dz80"];
174pub const DISARK_CMDS: &[&str] = &[DISARK_CMD];
175
176pub const AT_CMDS: &[&str] = &[AT_CMD, "ArkosTracker3"];
177pub const CHIPNSFX_CMDS: &[&str] = &[CHIPNSFX_CMD];
178
179pub const HSPC_CMDS: &[&str] = &[HSPC_CMD, "hspc"];
180
181pub const CP_CMDS: &[&str] = &["cp", "copy"];
182pub const MKDIR_CMDS: &[&str] = &["mkdir"];
183pub const RM_CMDS: &[&str] = &["rm", "del"];
184
185pub const BNDBUILD_CMDS: &[&str] = &["bndbuild", "build"];
186pub const CONVGENERIC_CMDS: &[&str] = &[CONVGENERIC_CMD];
187pub const DISC_CMDS: &[&str] = &["dsk", "disc"];
188pub const ECHO_CMDS: &[&str] = &["echo", "print"];
189pub const EXTERN_CMDS: &[&str] = &["extern"];
190#[cfg(feature = "fap")]
191pub const FAP_CMDS: &[&str] = &[FAP_CMD];
192pub const IMG2CPC_CMDS: &[&str] = &["img2cpc", "imgconverter"];
193pub const HIDEUR_CMDS: &[&str] = &[HIDEUR_CMD];
194pub const IMPDISC_CMDS: &[&str] = &[IMPDISC_CMD, "impdisc"];
195pub const MARTINE_CMDS: &[&str] = &[MARTINE_CMD];
196pub const SNA_CMDS: &[&str] = &["sna", "snpashot"];
197pub const XFER_CMDS: &[&str] = &["xfer", "cpcwifi", "m4"];
198
199pub const CRUNCH_CMDS: &[&str] = &["crunch", "compress"];
200
201pub const SONG2AKM_CMDS: &[&str] = &[SongToAkm::CMD];
202pub const SONG2AKG_CMDS: &[&str] = &[SongToAkg::CMD];
203pub const SONG2AKY_CMDS: &[&str] = &[SongToAky::CMD];
204pub const SONG2EVENTS_CMDS: &[&str] = &[SongToEvents::CMD];
205pub const SONG2RAW_CMDS: &[&str] = &[SongToRaw::CMD];
206pub const SONG2SOUNDEFFECTS_CMDS: &[&str] = &[SongToSoundEffects::CMD];
207pub const SONG2VGM_CMDS: &[&str] = &[SongToVgm::CMD];
208pub const SONG2WAV_CMDS: &[&str] = &[SongToWav::CMD];
209pub const SONG2YM_CMDS: &[&str] = &[SongToYm::CMD];
210
211impl Display for InnerTask {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 let (cmd, s) = match self {
214 Self::Assembler(a, s) => (a.get_command(), s),
215 Self::BndBuild(s) => (BNDBUILD_CMDS[0], s),
216 Self::Convgeneric(s) => (CONVGENERIC_CMDS[0], s),
217 Self::Cp(s) => (CP_CMDS[0], s),
218 Self::Crunch(s) => (CRUNCH_CMDS[0], s),
219 Self::Disassembler(d, s) => (d.get_command(), s),
220 Self::Disc(s) => (DISC_CMDS[0], s),
221 Self::Echo(s) => (ECHO_CMDS[0], s),
222 Self::Emulator(e, s) => (e.get_command(), s),
223 Self::Extern(s) => (EXTERN_CMDS[0], s),
224 #[cfg(feature = "fap")]
225 Self::Fap(s) => (FAP_CMDS[0], s),
226 Self::Hideur(s) => (HIDEUR_CMDS[0], s),
227 Self::HspCompiler(s) => (HSPC_CMDS[0], s),
228 Self::ImgConverter(s) => (IMG2CPC_CMDS[0], s),
229 Self::ImpDsk(s) => (IMPDISC_CMDS[0], s),
230 Self::Martine(s) => (MARTINE_CMDS[0], s),
231 Self::Mkdir(s) => (MKDIR_CMDS[0], s),
232 Self::Rm(s) => (RM_CMDS[0], s),
233 Self::Snapshot(s) => (SNA_CMDS[0], s),
234 Self::SongConverter(t, s) => (t.get_command(), s),
235 Self::Tracker(t, s) => (t.get_command(), s),
236 Self::Xfer(s) => (XFER_CMDS[0], s),
237 #[cfg(feature = "fap")]
238 Self::Fap(s) => (FAP_CMDS[0], s),
239 Self::Snapshot(s) => (SNA_CMDS[0], s)
240 };
241
242 write!(
243 f,
244 "{}{} {}",
245 if s.ignore_error { "-" } else { "" },
246 cmd,
247 s.args
248 )
249 }
250}
251
252macro_rules! is_some_cmd {
253 ($($code:ident), *) => {
254 $(
255 paste::paste! {
256 #[inline]
257 pub fn [<is_ $code:lower _cmd>](code: &str) -> bool {
258 [< $code:upper _CMDS>] .iter().contains(&code)
259 }
260 }
261 )*
262
263 };
264}
265
266#[rustfmt::skip]
267is_some_cmd!(
268 ace, amspirit, at,
269 basm, bdasm, bndbuild,
270 capriceforever, chipnsfx, convgeneric, crunch, cp, cpcec, cpcemupower,
271 disark, disc,
272 echo, emuctrl, r#extern,
273 hideur,hspc,
274 img2cpc, impdisc,
275 martine, mkdir,
276 orgams,
277 rasm, rm,
278 sjasmplus, sna, sugarbox,
279 song2akm,
280 song2akg,
281 song2aky,
282 song2events,
283 song2raw,
284 song2soundeffects,
285 song2vgm,
286 song2wav,
287 song2ym,
288 uz80,
289 vasm,
290 winape,
291 xfer
292);
293
294#[cfg(feature = "fap")]
295is_some_cmd!(fap);
296
297#[cfg(not(feature = "fap"))]
298pub fn is_fap_cmd(code: &str) -> bool {
299 false
300}
301
302impl<'de> Deserialize<'de> for InnerTask {
303 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
304 where D: Deserializer<'de> {
305 struct Line;
306 impl Visitor<'_> for Line {
307 type Value = InnerTask;
308
309 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
310 where E: serde::de::Error {
311 let (code, next) = v.split_once(" ").unwrap_or((v, ""));
312 let (code, ignore) = if code.starts_with("-") {
313 (&code[1..], true)
314 }
315 else {
316 (code, false)
317 };
318 let std = StandardTaskArguments {
319 args: next.to_owned(),
320 ignore_error: ignore
321 };
322
323 if is_ace_cmd(code) {
324 Ok(InnerTask::Emulator(Emulator::new_ace_default(), std))
325 }
326 else if is_at_cmd(code) {
327 Ok(InnerTask::Tracker(Tracker::new_at3_default(), std))
328 }
329 else if is_chipnsfx_cmd(code) {
330 Ok(InnerTask::Tracker(Tracker::new_chipnsfx_default(), std))
331 }
332 else if is_song2akm_cmd(code) {
333 Ok(InnerTask::SongConverter(
334 SongConverter::new_song_to_akm_default(),
335 std
336 ))
337 }
338 else if is_song2aky_cmd(code) {
339 Ok(InnerTask::SongConverter(
340 SongConverter::new_song_to_aky_default(),
341 std
342 ))
343 }
344 else if is_song2akg_cmd(code) {
345 Ok(InnerTask::SongConverter(
346 SongConverter::new_song_to_akg_default(),
347 std
348 ))
349 }
350 else if is_song2events_cmd(code) {
351 Ok(InnerTask::SongConverter(
352 SongConverter::new_song_to_events_default(),
353 std
354 ))
355 }
356 else if is_song2raw_cmd(code) {
357 Ok(InnerTask::SongConverter(
358 SongConverter::new_song_to_raw_default(),
359 std
360 ))
361 }
362 else if is_song2soundeffects_cmd(code) {
363 Ok(InnerTask::SongConverter(
364 SongConverter::new_song_to_sound_effects_default(),
365 std
366 ))
367 }
368 else if is_song2vgm_cmd(code) {
369 Ok(InnerTask::SongConverter(
370 SongConverter::new_song_to_vgm_default(),
371 std
372 ))
373 }
374 else if is_song2wav_cmd(code) {
375 Ok(InnerTask::SongConverter(
376 SongConverter::new_song_to_wav_default(),
377 std
378 ))
379 }
380 else if is_song2ym_cmd(code) {
381 Ok(InnerTask::SongConverter(
382 SongConverter::new_song_to_ym_default(),
383 std
384 ))
385 }
386 else if is_crunch_cmd(code) {
387 Ok(InnerTask::Crunch(std))
388 }
389 else if is_convgeneric_cmd(code) {
390 Ok(InnerTask::Convgeneric(std))
391 }
392 else if is_cpcec_cmd(code) {
393 Ok(InnerTask::Emulator(Emulator::new_cpcec_default(), std))
394 }
395 else if is_amspirit_cmd(code) {
396 Ok(InnerTask::Emulator(Emulator::new_amspirit_default(), std))
397 }
398 else if is_sugarbox_cmd(code) {
399 Ok(InnerTask::Emulator(Emulator::new_sugarbox_default(), std))
400 }
401 else if is_winape_cmd(code) {
402 Ok(InnerTask::Emulator(Emulator::new_winape_default(), std))
403 }
404 else if is_cpcemupower_cmd(code) {
405 Ok(InnerTask::Emulator(
406 Emulator::new_cpcemupower_default(),
407 std
408 ))
409 }
410 else if is_capriceforever_cmd(code) {
411 Ok(InnerTask::Emulator(
412 Emulator::new_capriceforever_default(),
413 std
414 ))
415 }
416 else if is_emuctrl_cmd(code) {
417 Ok(InnerTask::Emulator(Emulator::new_facade(), std))
418 }
419 else if is_basm_cmd(code) {
420 Ok(InnerTask::Assembler(Assembler::Basm, std))
421 }
422 else if is_bdasm_cmd(code) {
423 Ok(InnerTask::Disassembler(Disassembler::Bdasm, std))
424 }
425 else if is_disark_cmd(code) {
426 Ok(InnerTask::Disassembler(
427 Disassembler::Extern(ExternDisassembler::Disark(DisarkVersion::default())),
428 std
429 ))
430 }
431 else if is_fap_cmd(code) {
432 #[cfg(feature = "fap")]
433 let res = Ok(InnerTask::Fap(std));
434
435 #[cfg(not(feature = "fap"))]
436 let res = unreachable!();
437
438 res
439 }
440 else if is_orgams_cmd(code) {
441 Ok(InnerTask::Assembler(Assembler::Orgams, std))
442 }
443 else if is_rasm_cmd(code) {
444 Ok(InnerTask::Assembler(
445 Assembler::Extern(cpclib_runner::runner::assembler::ExternAssembler::Rasm(
446 RasmVersion::default()
447 )),
448 std
449 ))
450 }
451 else if is_uz80_cmd(code) {
452 Ok(InnerTask::Assembler(
453 Assembler::Extern(cpclib_runner::runner::assembler::ExternAssembler::Uz80(
454 Default::default()
455 )),
456 std
457 ))
458 }
459 else if is_sjasmplus_cmd(code) {
460 Ok(InnerTask::Assembler(
461 Assembler::Extern(
462 cpclib_runner::runner::assembler::ExternAssembler::Sjasmplus(
463 Default::default()
464 )
465 ),
466 std
467 ))
468 }
469 else if is_vasm_cmd(code) {
470 Ok(InnerTask::Assembler(
471 Assembler::Extern(cpclib_runner::runner::assembler::ExternAssembler::Vasm(
472 Default::default()
473 )),
474 std
475 ))
476 }
477 else if is_sna_cmd(code) {
478 Ok(InnerTask::Snapshot(std))
479 }
480 else if is_bndbuild_cmd(code) {
481 Ok(InnerTask::BndBuild(std))
482 }
483 else if is_disc_cmd(code) {
484 Ok(InnerTask::Disc(std))
485 }
486 else if is_echo_cmd(code) {
487 Ok(InnerTask::Echo(std))
488 }
489 else if is_extern_cmd(code) {
490 Ok(InnerTask::Extern(std))
491 }
492 else if is_hideur_cmd(code) {
493 Ok(InnerTask::Hideur(std))
494 }
495 else if is_img2cpc_cmd(code) {
496 Ok(InnerTask::ImgConverter(std))
497 }
498 else if is_impdisc_cmd(code) {
499 Ok(InnerTask::ImpDsk(std))
500 }
501 else if is_hspc_cmd(code) {
502 Ok(InnerTask::HspCompiler(std))
503 }
504 else if is_martine_cmd(code) {
505 Ok(InnerTask::Martine(std))
506 }
507 else if is_xfer_cmd(code) {
508 Ok(InnerTask::Xfer(std))
509 }
510 else if is_cp_cmd(code) {
511 Ok(InnerTask::Cp(std))
512 }
513 else if is_mkdir_cmd(code) {
514 Ok(InnerTask::Mkdir(std))
515 }
516 else if is_rm_cmd(code) {
517 Ok(InnerTask::Rm(std))
518 }
519 else {
520 Err(Error::custom(format!("{code} is an invalid command")))
521 }
522 }
523
524 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
525 formatter.write_str("Expecting a command")
526 }
527 }
528
529 deserializer.deserialize_str(Line)
530 }
531}
532
533impl FromStr for Task {
534 type Err = String;
535
536 fn from_str(s: &str) -> Result<Self, Self::Err> {
537 InnerTask::from_str(s).map(|t| t.into())
538 }
539}
540impl FromStr for InnerTask {
541 type Err = String;
542
543 fn from_str(s: &str) -> Result<Self, Self::Err> {
544 serde_yaml::from_str(s).map_err(|e| e.to_string())
545 }
546}
547
548impl InnerTask {
549 pub fn new_basm(args: &str) -> Self {
550 Self::Assembler(Assembler::Basm, StandardTaskArguments::new(args))
551 }
552
553 pub fn new_bndbuild(args: &str) -> Self {
554 Self::BndBuild(StandardTaskArguments::new(args))
555 }
556
557 pub fn new_dsk(args: &str) -> Self {
558 Self::Disc(StandardTaskArguments::new(args))
559 }
560
561 pub fn new_rm(args: &str) -> Self {
562 Self::Rm(StandardTaskArguments::new(args))
563 }
564
565 pub fn new_echo(args: &str) -> Self {
566 Self::Echo(StandardTaskArguments::new(args))
567 }
568
569 pub fn new_imgconverter(args: &str) -> Self {
570 Self::ImgConverter(StandardTaskArguments::new(args))
571 }
572
573 pub fn replace_automatic_variables(
574 &mut self,
575 first_dep: Option<&Utf8Path>,
576 first_tgt: Option<&Utf8Path>
577 ) -> Result<(), String> {
578 self.standard_task_arguments_mut()
579 .replace_automatic_variables(first_dep, first_tgt)
580 }
581
582 fn standard_task_arguments(&self) -> &StandardTaskArguments {
583 match self {
584 InnerTask::Assembler(_, t)
585 | InnerTask::BndBuild(t)
586 | InnerTask::Convgeneric(t)
587 | InnerTask::Crunch(t)
588 | InnerTask::Cp(t)
589 | InnerTask::Disassembler(_, t)
590 | InnerTask::Disc(t)
591 | InnerTask::ImpDsk(t)
592 | InnerTask::Echo(t)
593 | InnerTask::Extern(t)
594 | InnerTask::Hideur(t)
595 | InnerTask::HspCompiler(t)
596 | InnerTask::ImgConverter(t)
597 | InnerTask::Martine(t)
598 | InnerTask::Mkdir(t)
599 | InnerTask::Rm(t)
600 | InnerTask::Xfer(t)
601 | InnerTask::Emulator(_, t)
602 | InnerTask::Snapshot(t)
603 | InnerTask::SongConverter(_, t)
604 | InnerTask::Tracker(_, t) => t,
605 #[cfg(feature = "fap")]
606 InnerTask::Fap(t) => t
607 }
608 }
609
610 fn standard_task_arguments_mut(&mut self) -> &mut StandardTaskArguments {
611 match self {
612 InnerTask::Assembler(_, t)
613 | InnerTask::BndBuild(t)
614 | InnerTask::Convgeneric(t)
615 | InnerTask::Crunch(t)
616 | InnerTask::Cp(t)
617 | InnerTask::Disassembler(_, t)
618 | InnerTask::Disc(t)
619 | InnerTask::Echo(t)
620 | InnerTask::Emulator(_, t)
621 | InnerTask::Extern(t)
622 | InnerTask::Hideur(t)
623 | InnerTask::HspCompiler(t)
624 | InnerTask::ImgConverter(t)
625 | InnerTask::ImpDsk(t)
626 | InnerTask::BndBuild(t)
627 | InnerTask::Martine(t)
628 | InnerTask::Mkdir(t)
629 | InnerTask::Rm(t)
630 | InnerTask::Snapshot(t)
631 | InnerTask::SongConverter(_, t)
632 | InnerTask::Tracker(_, t)
633 | InnerTask::Xfer(t) => t,
634 #[cfg(feature = "fap")]
635 InnerTask::Fap(t) => t
636 }
637 }
638
639 pub fn args(&self) -> &str {
640 &self.standard_task_arguments().args
641 }
642
643 pub fn ignore_errors(&self) -> bool {
644 self.standard_task_arguments().ignore_error
645 }
646
647 pub fn set_ignore_errors(mut self, ignore: bool) -> Self {
648 self.standard_task_arguments_mut().ignore_error = ignore;
649 self
650 }
651
652 pub fn is_phony(&self) -> bool {
654 match self {
655 InnerTask::Assembler(..) => false, InnerTask::BndBuild(_) => false,
657 InnerTask::Convgeneric(_) => false,
658 InnerTask::Cp(_) => false,
659 InnerTask::Crunch(_) => false,
660 InnerTask::Disassembler(..) => false,
661 InnerTask::Disc(_) => false,
662 InnerTask::Echo(_) => true,
663 InnerTask::Emulator(..) => true,
664 InnerTask::Extern(_) => false,
665 #[cfg(feature = "fap")]
666 InnerTask::Fap(..) => true,
667 InnerTask::Hideur(_) => false,
668 InnerTask::HspCompiler(_) => false,
669 InnerTask::ImgConverter(_) => false,
670 InnerTask::ImpDsk(_) => false,
671 InnerTask::Martine(t) => false,
672 InnerTask::Mkdir(_) => false,
673 InnerTask::Rm(_s) => false, InnerTask::Snapshot(_) => false,
675 InnerTask::SongConverter(_, t) => false,
676 InnerTask::Tracker(_, t) => true, InnerTask::Xfer(_) => true
678 }
679 }
680}
681
682#[derive(Deserialize, Clone, PartialEq, Debug, Eq, Hash)]
683pub struct StandardTaskArguments {
684 args: String,
685 ignore_error: bool
686}
687
688impl StandardTaskArguments {
689 pub fn new<S: Into<String>>(args: S) -> Self {
690 Self {
691 args: args.into(),
692 ignore_error: false
693 }
694 }
695
696 fn replace_automatic_variables(
699 &mut self,
700 first_dep: Option<&Utf8Path>,
701 first_tgt: Option<&Utf8Path>
702 ) -> Result<(), String> {
703 static RE_FIRST_DEP: LazyLock<Regex> =
704 LazyLock::new(|| Regex::new(r"\${1}(?!\$)<").unwrap()); static RE_FIRST_TGT: LazyLock<Regex> =
706 LazyLock::new(|| Regex::new(r"\${1}(?!\$)@").unwrap());
707
708 let initial = self.args.clone();
709
710 if let Some(first_dep) = first_dep {
711 self.args = RE_FIRST_DEP
712 .replace_all(&self.args, first_dep.as_str())
713 .into_owned();
714 }
715 else if RE_FIRST_DEP.is_match(&self.args).unwrap() {
716 self.args = initial;
717 return Err(format!(
718 "{} contains $<, but there are no available dependencies.",
719 self.args
720 ));
721 }
722
723 if let Some(first_tgt) = first_tgt {
724 self.args = RE_FIRST_TGT
725 .replace_all(&self.args, first_tgt.as_str())
726 .into_owned();
727 }
728 else if RE_FIRST_TGT.is_match(&self.args).unwrap() {
729 self.args = initial;
730 return Err(format!(
731 "{} contains $@, but there are no available targets.",
732 self.args
733 ));
734 }
735 Ok(())
736 }
737}
738
739#[cfg(test)]
740mod test {
741 use super::InnerTask;
742 use crate::task::StandardTaskArguments;
743
744 #[test]
745 fn test_automatic_arguments() {
746 let mut no_args = StandardTaskArguments::new("a b");
748 assert!(dbg!(
749 no_args.replace_automatic_variables(None, None).is_ok()
750 ));
751 assert_eq!(no_args.args, "a b");
752
753 let mut no_args = StandardTaskArguments::new("a b");
754 assert!(dbg!(
755 no_args
756 .replace_automatic_variables(Some("a".into()), None)
757 .is_ok()
758 ));
759 assert_eq!(no_args.args, "a b");
760
761 let mut no_args = StandardTaskArguments::new("a b");
762 assert!(dbg!(
763 no_args
764 .replace_automatic_variables(None, Some("b".into()))
765 .is_ok()
766 ));
767 assert_eq!(no_args.args, "a b");
768
769 let mut no_args = StandardTaskArguments::new("a b");
770 assert!(dbg!(
771 no_args
772 .replace_automatic_variables(Some("a".into()), Some("b".into()))
773 .is_ok()
774 ));
775 assert_eq!(no_args.args, "a b");
776
777 let mut no_args = StandardTaskArguments::new("$@ b");
779 assert!(dbg!(
780 no_args.replace_automatic_variables(None, None).is_err()
781 ));
782 assert_eq!(no_args.args, "$@ b");
783
784 let mut no_args = StandardTaskArguments::new("$@ b");
785 assert!(dbg!(
786 no_args
787 .replace_automatic_variables(Some("a".into()), None)
788 .is_err()
789 ));
790 assert_eq!(no_args.args, "$@ b");
791
792 let mut no_args = StandardTaskArguments::new("$@ b");
793 assert!(dbg!(
794 no_args
795 .replace_automatic_variables(None, Some("b".into()))
796 .is_ok()
797 ));
798 assert_eq!(no_args.args, "b b");
799
800 let mut no_args = StandardTaskArguments::new("$@ b");
801 assert!(dbg!(
802 no_args
803 .replace_automatic_variables(Some("a".into()), Some("b".into()))
804 .is_ok()
805 ));
806 assert_eq!(no_args.args, "b b");
807
808 let mut no_args = StandardTaskArguments::new("$@ $<");
810 assert!(dbg!(
811 no_args.replace_automatic_variables(None, None).is_err()
812 ));
813 assert_eq!(no_args.args, "$@ $<");
814
815 let mut no_args = StandardTaskArguments::new("$@ $<");
816 assert!(dbg!(
817 no_args
818 .replace_automatic_variables(Some("a".into()), None)
819 .is_err()
820 ));
821 assert_eq!(no_args.args, "$@ $<");
822
823 let mut no_args = StandardTaskArguments::new("$@ $<");
824 assert!(dbg!(
825 no_args
826 .replace_automatic_variables(None, Some("b".into()))
827 .is_err()
828 ));
829 assert_eq!(no_args.args, "$@ $<");
830
831 let mut no_args = StandardTaskArguments::new("$@ $<");
832 assert!(dbg!(
833 no_args
834 .replace_automatic_variables(Some("a".into()), Some("b".into()))
835 .is_ok()
836 ));
837 assert_eq!(no_args.args, "b a");
838
839 }
846
847 #[test]
848 fn test_deserialize_task() {
849 let yaml = "basm toto.asm -o toto.o";
850 let task: InnerTask = serde_yaml::from_str(yaml).unwrap();
851 assert_eq!(
852 task,
853 InnerTask::Assembler(
854 crate::runners::assembler::Assembler::Basm,
855 StandardTaskArguments {
856 args: "toto.asm -o toto.o".to_owned(),
857 ignore_error: false
858 }
859 )
860 );
861
862 let yaml = "-basm toto.asm -o toto.o";
863 let task: InnerTask = serde_yaml::from_str(yaml).unwrap();
864 assert_eq!(
865 task,
866 InnerTask::Assembler(
867 crate::runners::assembler::Assembler::Basm,
868 StandardTaskArguments {
869 args: "toto.asm -o toto.o".to_owned(),
870 ignore_error: true
871 }
872 )
873 );
874 }
875}