1use anyhow::{Result, anyhow, bail};
2
3use crate::formats::eite::eite_state::EiteState;
4use crate::formats::eite::formats::get_format_id;
5use crate::formats::eite::kv::{kv_get_value, kv_has_value, kv_set_value};
6use crate::formats::eite::util::pred::is_valid_ident;
7
8pub fn setting_string_to_array(s: &str) -> Vec<String> {
15 let mut out = Vec::new();
16 for seg in s.split(',') {
17 if seg.is_empty() {
18 continue;
19 }
20 if let Some((k, v)) = seg.split_once(':') {
21 if !k.is_empty() {
22 out.push(k.to_string());
23 out.push(v.to_string());
24 }
25 }
26 }
27 out
28}
29
30pub fn get_settings_for_format(
35 state: &EiteState,
36 format: &str,
37 direction: &str,
38) -> Result<Vec<String>> {
39 let format_id = get_format_id(format)?;
40 let raw = if direction == "in" {
41 get_import_settings(state, format_id)
42 } else {
43 get_export_settings(state, format_id)
44 };
45 Ok(setting_string_to_array(&raw))
46}
47
48pub fn get_setting_for_format(
52 state: &EiteState,
53 format: &str,
54 direction: &str,
55 key: &str,
56) -> Result<String> {
57 let kv = get_settings_for_format(state, format, direction)?;
58 let mut i = 0;
59 while i + 1 < kv.len() {
60 if kv[i] == key {
61 return Ok(kv[i + 1].clone());
62 }
63 i += 2;
64 }
65 Ok(String::new())
66}
67
68pub fn get_enabled_variants_for_format(
73 state: &EiteState,
74 format: &str,
75 direction: &str,
76) -> Result<Vec<String>> {
77 let variants_str =
78 get_setting_for_format(state, format, direction, "variants")?;
79 if variants_str.is_empty() {
80 return Ok(vec![]);
81 }
82 Ok(variants_str
83 .split(' ')
84 .filter(|s| !s.is_empty())
85 .map(std::string::ToString::to_string)
86 .collect())
87}
88
89pub fn get_preferred_language_for_format(
98 state: &EiteState,
99 format: &str,
100 direction: &str,
101) -> Result<String> {
102 let variants = get_enabled_variants_for_format(state, format, direction)?;
103 for v in &variants {
104 if v.starts_with("lang_") {
105 return Ok(v.clone());
106 }
107 }
108 Ok(state.env_language.clone())
109}
110
111pub fn get_preferred_code_language_for_format(
117 state: &EiteState,
118 format: &str,
119 direction: &str,
120) -> Result<String> {
121 let variants = get_enabled_variants_for_format(state, format, direction)?;
122 for v in &variants {
123 if let Some(stripped) = v.strip_prefix("pl_") {
124 return Ok(stripped.to_string());
125 }
126 }
127 Ok(state.env_code_language.clone())
128}
129
130pub fn get_import_settings(state: &EiteState, format_id: usize) -> String {
134 state
135 .import_settings
136 .get(format_id)
137 .cloned()
138 .unwrap_or_default()
139}
140
141pub fn get_export_settings(state: &EiteState, format_id: usize) -> String {
145 state
146 .export_settings
147 .get(format_id)
148 .cloned()
149 .unwrap_or_default()
150}
151
152fn set_import_settings(state: &mut EiteState, format_id: usize, new_val: &str) {
154 if state.import_settings.len() <= format_id {
155 state.import_settings.resize(format_id + 1, String::new());
156 }
157 state.import_settings[format_id] = new_val.to_string();
158}
159
160fn set_export_settings(state: &mut EiteState, format_id: usize, new_val: &str) {
162 if state.export_settings.len() <= format_id {
163 state.export_settings.resize(format_id + 1, String::new());
164 }
165 state.export_settings[format_id] = new_val.to_string();
166}
167
168pub fn push_import_settings(
173 state: &mut EiteState,
174 format_id: usize,
175 new_setting_string: &str,
176) -> Result<()> {
177 let current = get_import_settings(state, format_id);
178 state.import_deferred_settings_stack.push(current);
179 set_import_settings(state, format_id, new_setting_string);
180 Ok(())
181}
182
183pub fn push_export_settings(
187 state: &mut EiteState,
188 format_id: usize,
189 new_setting_string: &str,
190) -> Result<()> {
191 let current = get_export_settings(state, format_id);
192 state.export_deferred_settings_stack.push(current);
193 set_export_settings(state, format_id, new_setting_string);
194 Ok(())
195}
196
197pub fn pop_import_settings(
201 state: &mut EiteState,
202 format_id: usize,
203) -> Result<()> {
204 let val = state.import_deferred_settings_stack.pop().ok_or_else(|| {
205 anyhow!("pop_import_settings: empty import deferred stack")
206 })?;
207 set_import_settings(state, format_id, &val);
208 Ok(())
209}
210
211pub fn pop_export_settings(
215 state: &mut EiteState,
216 format_id: usize,
217) -> Result<()> {
218 let val = state.export_deferred_settings_stack.pop().ok_or_else(|| {
219 anyhow!("pop_export_settings: empty export deferred stack")
220 })?;
221 set_export_settings(state, format_id, &val);
222 Ok(())
223}
224
225pub fn setting_array_to_string(kv: &[String]) -> Result<String> {
230 if !kv.len().is_multiple_of(2) {
231 return Err(anyhow!(
232 "setting_array_to_string: key/value array length must be even (got {})",
233 kv.len()
234 ));
235 }
236 let mut out = String::new();
237 for pair in kv.chunks(2) {
238 let key = &pair[0];
239 let value = &pair[1];
240 if !is_valid_ident(key) {
241 bail!("setting_array_to_string: invalid identifier '{key}'");
242 }
243 out.push_str(key);
244 out.push(':');
245 out.push_str(value);
246 out.push(',');
247 }
248 Ok(out)
249}
250
251fn import_kv(state: &EiteState, format_id: usize) -> Vec<String> {
254 setting_string_to_array(get_import_settings(state, format_id).as_str())
255}
256
257fn export_kv(state: &EiteState, format_id: usize) -> Vec<String> {
260 setting_string_to_array(get_export_settings(state, format_id).as_str())
261}
262
263fn set_import_kv(
265 state: &mut EiteState,
266 format_id: usize,
267 kv: &[String],
268) -> Result<()> {
269 let s = setting_array_to_string(kv)?;
270 set_import_settings(state, format_id, &s);
271 Ok(())
272}
273
274fn set_export_kv(
276 state: &mut EiteState,
277 format_id: usize,
278 kv: &[String],
279) -> Result<()> {
280 let s = setting_array_to_string(kv)?;
281 set_export_settings(state, format_id, &s);
282 Ok(())
283}
284
285pub fn get_format_import_setting(
287 state: &EiteState,
288 format: &str,
289 key: &str,
290) -> Result<String> {
291 let id = get_format_id(format)?;
292 let kv = import_kv(state, id);
293 Ok(kv_get_value(&kv, key))
294}
295
296pub fn get_format_export_setting(
298 state: &EiteState,
299 format: &str,
300 key: &str,
301) -> Result<String> {
302 let id = get_format_id(format)?;
303 let kv = export_kv(state, id);
304 Ok(kv_get_value(&kv, key))
305}
306
307pub fn set_format_import_setting(
309 state: &mut EiteState,
310 format: &str,
311 key: &str,
312 value: &str,
313) -> Result<()> {
314 let id = get_format_id(format)?;
315 let kv = import_kv(state, id);
316 let new_kv = kv_set_value(kv, key, value);
317 set_import_kv(state, id, &new_kv)
318}
319
320pub fn set_format_export_setting(
322 state: &mut EiteState,
323 format: &str,
324 key: &str,
325 value: &str,
326) -> Result<()> {
327 let id = get_format_id(format)?;
328 let kv = export_kv(state, id);
329 let new_kv = kv_set_value(kv, key, value);
330 set_export_kv(state, id, &new_kv)
331}
332
333pub fn push_format_import_setting(
335 state: &mut EiteState,
336 format: &str,
337 key: &str,
338 value: &str,
339) -> Result<String> {
340 let prev = get_format_import_setting(state, format, key)?;
341 set_format_import_setting(state, format, key, value)?;
342 Ok(prev)
343}
344
345pub fn pop_format_import_setting(
347 state: &mut EiteState,
348 format: &str,
349 key: &str,
350 previous_value: &str,
351) -> Result<()> {
352 set_format_import_setting(state, format, key, previous_value)
353}
354
355pub fn push_format_export_setting(
357 state: &mut EiteState,
358 format: &str,
359 key: &str,
360 value: &str,
361) -> Result<String> {
362 let prev = get_format_export_setting(state, format, key)?;
363 set_format_export_setting(state, format, key, value)?;
364 Ok(prev)
365}
366
367pub fn pop_format_export_setting(
369 state: &mut EiteState,
370 format: &str,
371 key: &str,
372 previous_value: &str,
373) -> Result<()> {
374 set_format_export_setting(state, format, key, previous_value)
375}
376
377pub fn get_format_import_settings(
379 state: &EiteState,
380 format: &str,
381) -> Result<Vec<String>> {
382 let id = get_format_id(format)?;
383 Ok(import_kv(state, id))
384}
385
386pub fn get_format_export_settings(
388 state: &EiteState,
389 format: &str,
390) -> Result<Vec<String>> {
391 let id = get_format_id(format)?;
392 Ok(export_kv(state, id))
393}
394
395pub fn set_format_import_settings(
397 state: &mut EiteState,
398 format: &str,
399 settings: &[String],
400) -> Result<()> {
401 let id = get_format_id(format)?;
402 set_import_kv(state, id, settings)
403}
404
405pub fn set_format_export_settings(
407 state: &mut EiteState,
408 format: &str,
409 settings: &[String],
410) -> Result<()> {
411 let id = get_format_id(format)?;
412 set_export_kv(state, id, settings)
413}
414
415pub fn has_import_setting(
418 state: &EiteState,
419 format: &str,
420 direction: &str,
421 key: &str,
422) -> Result<bool> {
423 let settings = get_settings_for_format(state, format, direction)?;
425 Ok(kv_has_value(&settings, key))
426}
427
428pub fn get_exec_option(
430 state: &EiteState,
431 exec_id: usize,
432 key: &str,
433) -> Result<String> {
434 Ok(state
435 .get_exec_option(exec_id, key)?
436 .unwrap_or_else(String::new))
437}
438
439pub fn get_exec_options(
441 state: &EiteState,
442 exec_id: usize,
443) -> Result<Vec<String>> {
444 state.get_exec_settings(exec_id)
445}
446
447pub fn set_exec_option(
449 state: &mut EiteState,
450 exec_id: usize,
451 key: &str,
452 value: &str,
453) -> Result<()> {
454 state.set_exec_option(exec_id, key, value)
455}
456
457#[cfg(test)]
458#[allow(clippy::unwrap_in_result, clippy::panic_in_result_fn)]
459mod tests {
460 use anyhow::Result;
461
462 use crate::formats::eite::{eite_state::EiteState, formats::get_format_id};
463
464 use super::*;
465
466 #[crate::ctb_test]
467 fn test_setting_string_to_array_basic() {
468 let s = "a:1,b:2,c:hello_world,";
469 let arr = setting_string_to_array(s);
470 assert_eq!(
471 arr,
472 vec!["a", "1", "b", "2", "c", "hello_world"]
473 .into_iter()
474 .map(|s| s.to_string())
475 .collect::<Vec<_>>()
476 );
477 let s = "language:lang_en,";
478 let arr = setting_string_to_array(s);
479 assert_eq!(
480 arr,
481 vec!["language", "lang_en"]
482 .into_iter()
483 .map(|s| s.to_string())
484 .collect::<Vec<_>>()
485 );
486 }
487
488 #[crate::ctb_test]
489 fn test_get_setting_for_format_absent() {
490 let mut state = EiteState::new();
491 state.import_settings.push(String::new());
493 let v = get_setting_for_format(&state, "fmt0", "in", "missing");
494 if v.is_err() {
501 let arr = setting_string_to_array("");
503 assert!(arr.is_empty());
504 } else {
505 assert_eq!(v.unwrap(), "");
506 }
507 }
508
509 #[crate::ctb_test]
510 fn test_push_pop_import_settings() {
511 let mut state = EiteState::new();
512 set_import_settings(&mut state, 2, "a:1,");
514 assert_eq!(get_import_settings(&state, 2), "a:1,");
515
516 push_import_settings(&mut state, 2, "b:2,").unwrap();
517 assert_eq!(get_import_settings(&state, 2), "b:2,");
518 assert_eq!(state.import_deferred_settings_stack.len(), 1);
519
520 pop_import_settings(&mut state, 2).unwrap();
521 assert_eq!(get_import_settings(&state, 2), "a:1,");
522 assert!(state.import_deferred_settings_stack.is_empty());
523 }
524
525 #[crate::ctb_test]
526 fn test_push_pop_export_settings() {
527 let mut state = EiteState::new();
528 set_export_settings(&mut state, 0, "x:9,");
529 push_export_settings(&mut state, 0, "y:10,").unwrap();
530 assert_eq!(get_export_settings(&state, 0), "y:10,");
531 pop_export_settings(&mut state, 0).unwrap();
532 assert_eq!(get_export_settings(&state, 0), "x:9,");
533 }
534
535 #[crate::ctb_test]
536 fn test_get_enabled_variants_for_format_empty() {
537 let mut state = EiteState::new();
538 set_export_settings(&mut state, 0, "");
539 if let Ok(vs) = get_enabled_variants_for_format(&state, "fmt0", "out") {
542 assert!(vs.is_empty());
543 }
544 }
545
546 #[crate::ctb_test]
547 fn test_preferred_language_variant() {
548 let mut state = EiteState::new();
549 state.env_language = "lang_default".to_string();
550 set_import_settings(
551 &mut state,
552 0,
553 "variants:pl_rust lang_en something_else,",
554 );
555 if let Ok(lang) =
556 get_preferred_language_for_format(&state, "fmt0", "in")
557 {
558 assert_eq!(lang, "lang_en");
560 }
561 }
562
563 #[crate::ctb_test]
564 fn test_preferred_code_language_variant() {
565 let mut state = EiteState::new();
566 state.env_code_language = "rust".to_string();
567 set_export_settings(
568 &mut state,
569 1,
570 "variants:lang_en pl_python pl_rust,",
571 );
572 if let Ok(code_lang) =
573 get_preferred_code_language_for_format(&state, "fmtX", "out")
574 {
575 assert_eq!(code_lang, "python");
577 }
578 }
579
580 #[crate::ctb_test]
581 fn test_setting_array_to_string_basic() -> Result<()> {
582 let kv = vec![
583 "alpha".to_string(),
584 "1".to_string(),
585 "beta".to_string(),
586 "two".to_string(),
587 ];
588 let s = setting_array_to_string(&kv)?;
589 assert_eq!(s, "alpha:1,beta:two,");
590 Ok(())
591 }
592
593 #[crate::ctb_test]
594 fn test_setting_array_to_string_reject_odd() {
595 let kv = vec!["only".to_string()];
596 assert!(setting_array_to_string(&kv).is_err());
597 }
598
599 #[crate::ctb_test]
600 fn basenb_wrapper_settings_do_not_panic() -> Result<()> {
601 let fmt_id = get_format_id("utf8")?;
602 let mut state = EiteState::new();
603 push_export_settings(&mut state, fmt_id, "variants:dcBasenb,")?;
604 pop_export_settings(&mut state, fmt_id)?;
605 Ok(())
606 }
607
608 #[crate::ctb_test]
609 fn test_import_setting_roundtrip() -> Result<()> {
610 let fmt = "semanticToText";
611 let mut state = EiteState::new();
612 set_format_import_setting(&mut state, fmt, "language", "lang_en")?;
614 assert_eq!(
615 get_format_import_setting(&state, fmt, "language")?,
616 "lang_en"
617 );
618 let prev =
620 push_format_import_setting(&mut state, fmt, "language", "lang_fr")?;
621 assert_eq!(prev, "lang_en");
622 assert_eq!(
623 get_format_import_setting(&mut state, fmt, "language")?,
624 "lang_fr"
625 );
626 pop_format_import_setting(&mut state, fmt, "language", &prev)?;
627 assert_eq!(
628 get_format_import_setting(&mut state, fmt, "language")?,
629 "lang_en"
630 );
631 Ok(())
632 }
633}