diff --git a/src/prefs.rs b/src/prefs.rs index 4dc51c4bc..89748119f 100644 --- a/src/prefs.rs +++ b/src/prefs.rs @@ -261,17 +261,9 @@ impl PreferenceManager { // Note: if current_dir() also fails, unwrap_or_default yields an empty PathBuf, // and the result may remain relative. #[cfg(not(feature = "include-zip"))] - let rules_dir = match rules_dir.canonicalize() { - Err(_e) => { - if rules_dir.is_absolute() { - rules_dir - } else { - std::env::current_dir() - .unwrap_or_default() - .join(&rules_dir) - } - }, - Ok(rules_dir) => rules_dir, + let rules_dir = match canonicalize_shim(&rules_dir) { + Err(e) => bail!("set_rules_dir: could not canonicalize path {}: {}", rules_dir.display(), e), + Ok(rules_dir) => rules_dir, }; self.set_rules_dir(&rules_dir)?; diff --git a/src/shim_filesystem.rs b/src/shim_filesystem.rs index 14c5b26fb..d539a6300 100644 --- a/src/shim_filesystem.rs +++ b/src/shim_filesystem.rs @@ -281,15 +281,81 @@ cfg_if! { crate::interface::set_rules_dir("Rules").unwrap(); // force reinitialization after the change } } else { + use std::collections::{HashMap, HashSet}; + use std::sync::RwLock; + + static IN_MEMORY_FILES: RwLock>> = RwLock::new(None); + static IN_MEMORY_DIRS: RwLock>> = RwLock::new(None); + + pub fn add_in_memory_file(path: &str, content: &str) { + { + let mut files = IN_MEMORY_FILES.write().unwrap(); + files.get_or_insert_with(HashMap::new).insert(path.to_string(), content.to_string()); + } + + let path_obj = Path::new(path); + { + let mut dirs = IN_MEMORY_DIRS.write().unwrap(); + let dirs = dirs.get_or_insert_with(HashSet::new); + for parent in path_obj.ancestors().skip(1) { + let parent_str = parent.to_str().unwrap_or_default(); + if !parent_str.is_empty() { + dirs.insert(parent_str.to_string()); + } + } + } + } + + pub fn canonicalize_shim(path: &Path) -> std::io::Result { + use std::ffi::OsStr; + let dot = OsStr::new("."); + let dot_dot = OsStr::new(".."); + let mut result = PathBuf::new(); + for part in path.iter() { + if dot == part { + continue; + } else if dot_dot == part { + result.pop(); + } else { + result.push(part); + } + } + return Ok(result); + } + + fn get_in_memory_key(path: &Path) -> String { + canonicalize_shim(path).unwrap_or_else(|_| path.to_path_buf()).to_str().unwrap_or_default().to_string() + } + pub fn is_file_shim(path: &Path) -> bool { + if IN_MEMORY_FILES.read().unwrap().as_ref().is_some_and(|f| f.contains_key(&get_in_memory_key(path))) { + return true; + } return path.is_file(); } pub fn is_dir_shim(path: &Path) -> bool { + if IN_MEMORY_DIRS.read().unwrap().as_ref().is_some_and(|d| d.contains(&get_in_memory_key(path))) { + return true; + } return path.is_dir(); } pub fn find_files_in_dir_that_ends_with_shim(dir: &Path, ending: &str) -> Vec { + let dir_name = get_in_memory_key(dir); + let mut answer = Vec::new(); + if let Some(files) = IN_MEMORY_FILES.read().unwrap().as_ref() { + for file_name in files.keys() { + if file_name.starts_with(&dir_name) && file_name.ends_with(ending) { + let name = Path::new(file_name).file_name().unwrap_or_default().to_str().unwrap_or_default(); + answer.push(name.to_string()); + } + } + } + if !answer.is_empty() { + return answer; + } + match dir.read_dir() { Err(_) => return vec![], // empty Ok(read_dir) => { @@ -340,27 +406,6 @@ cfg_if! { } } - /// Resolves the path to an absolute, canonical form using the OS. - /// If `canonicalize()` fails (e.g., ACCESS_DENIED in containers), falls back to: - /// - returning the path as-is if it is already absolute, - /// - prepending the current working directory if it is relative. - /// Note: the fallback does not resolve symlinks or normalize `..`/`.` segments. - pub fn canonicalize_shim(path: &Path) -> std::io::Result { - match path.canonicalize() { - Ok(p) => Ok(p), - Err(_) => { - if path.is_absolute() { - Ok(path.to_path_buf()) - } else { - // Prepend cwd to make the relative path absolute. - // unwrap_or_default yields an empty PathBuf if cwd is unavailable, - // in which case the returned path will still be relative. - Ok(std::env::current_dir().unwrap_or_default().join(path)) - } - } - } - } - // Tests extract language/braille zips in parallel. During extraction, individual YAML // files can briefly exist in a partially-written state (created/truncated before the // full contents are flushed). Guard both extraction and YAML reads with the same lock @@ -379,6 +424,10 @@ cfg_if! { } pub fn read_to_string_shim(path: &Path) -> Result { + if let Some(content) = IN_MEMORY_FILES.read().unwrap().as_ref().and_then(|f| f.get(&get_in_memory_key(path)).cloned()) { + return Ok(content); + } + let path = match path.canonicalize() { Ok(path) => path, Err(_) => path.to_path_buf(), @@ -490,4 +539,18 @@ mod tests { assert_eq!("test-value", read_to_string_shim(&existing_file).unwrap()); assert!(read_to_string_shim(&test_dir.path().join("does-not-exist.yaml")).is_err()); } + + #[test] + fn in_memory_filesystem_shim() { + add_in_memory_file("virtual/rules/Languages/en/test_Rules.yaml", "key: value"); + let file_path = Path::new("virtual/rules/Languages/en/test_Rules.yaml"); + let dir_path = Path::new("virtual/rules/Languages/en"); + + assert!(is_file_shim(file_path)); + assert!(is_dir_shim(dir_path)); + assert_eq!(read_to_string_shim(file_path).unwrap(), "key: value"); + + let found = find_files_in_dir_that_ends_with_shim(dir_path, "_Rules.yaml"); + assert_eq!(found, vec!["test_Rules.yaml".to_string()]); + } } diff --git a/tests/Languages/pt/ClearSpeak/sets.rs b/tests/Languages/pt/ClearSpeak/sets.rs index ff2ee45cd..87923aeac 100644 --- a/tests/Languages/pt/ClearSpeak/sets.rs +++ b/tests/Languages/pt/ClearSpeak/sets.rs @@ -1,27 +1,27 @@ -/// Testes de conjuntos e pertinência — pt-BR, ClearSpeak e SimpleSpeak. -/// -/// POR QUE ESTE ARQUIVO EXISTE -/// O par ∈ / ∊ falou "é membro de" com as preferências padrão durante duas -/// rodadas inteiras sem ninguém notar, porque o pt não tinha NENHUM teste de -/// pertinência — nem ClearSpeak, nem SimpleSpeak. O ∉ foi corrigido sozinho -/// numa rodada e os outros dois ficaram para trás. Ver ACHADOS 7.8. -/// -/// A invariante que este arquivo trava: **∈ (0x2208) e ∊ (0x220a) são -/// variantes de forma do mesmo símbolo e devem falar igual em todos os ramos** -/// — nas cinco preferências de ClearSpeak_SetMemberSymbol, dentro e fora de -/// um conjunto, nos dois estilos de fala. -/// -/// COMO AS EXPECTATIVAS FORAM ESCRITAS -/// Todas saíram da saída real do motor, nunca de tradução do inglês. O método -/// é o do cabeçalho de tests/Languages/pt/shared.rs: rodar, comparar `left` -/// com `right`, e se o gerado estiver certo em português, copiá-lo; se estiver -/// errado, consertar a REGRA, nunca o teste. -/// -/// Cada teste de pertinência fixa ClearSpeak_SetMemberSymbol explicitamente, -/// inclusive no caso 'Auto'. Isso é de propósito: as preferências de -/// ClearSpeak não são reinicializadas entre testes na mesma thread, então um -/// teste que dependesse do padrão implícito poderia passar ou falhar conforme -/// a ordem de execução. +//! Testes de conjuntos e pertinência — pt-BR, ClearSpeak e SimpleSpeak. +//! +//! POR QUE ESTE ARQUIVO EXISTE +//! O par ∈ / ∊ falou "é membro de" com as preferências padrão durante duas +//! rodadas inteiras sem ninguém notar, porque o pt não tinha NENHUM teste de +//! pertinência — nem ClearSpeak, nem SimpleSpeak. O ∉ foi corrigido sozinho +//! numa rodada e os outros dois ficaram para trás. Ver ACHADOS 7.8. +//! +//! A invariante que este arquivo trava: **∈ (0x2208) e ∊ (0x220a) são +//! variantes de forma do mesmo símbolo e devem falar igual em todos os ramos** +//! — nas cinco preferências de ClearSpeak_SetMemberSymbol, dentro e fora de +//! um conjunto, nos dois estilos de fala. +//! +//! COMO AS EXPECTATIVAS FORAM ESCRITAS +//! Todas saíram da saída real do motor, nunca de tradução do inglês. O método +//! é o do cabeçalho de tests/Languages/pt/shared.rs: rodar, comparar `left` +//! com `right`, e se o gerado estiver certo em português, copiá-lo; se estiver +//! errado, consertar a REGRA, nunca o teste. +//! +//! Cada teste de pertinência fixa ClearSpeak_SetMemberSymbol explicitamente, +//! inclusive no caso 'Auto'. Isso é de propósito: as preferências de +//! ClearSpeak não são reinicializadas entre testes na mesma thread, então um +//! teste que dependesse do padrão implícito poderia passar ou falhar conforme +//! a ordem de execução. use crate::common::*; use anyhow::Result; diff --git a/tests/Languages/pt/SimpleSpeak/functions.rs b/tests/Languages/pt/SimpleSpeak/functions.rs index a656c2da0..1429d1447 100644 --- a/tests/Languages/pt/SimpleSpeak/functions.rs +++ b/tests/Languages/pt/SimpleSpeak/functions.rs @@ -1,18 +1,18 @@ -/// TESTES DO IDIOMA pt — como reconciliar com as regras -/// -/// As strings esperadas abaixo foram escritas para refletir a intenção -/// das regras em Rules/Languages/pt. Vírgulas e ponto-e-vírgula marcam -/// PAUSAS inseridas pelo motor e podem divergir levemente na primeira -/// execução. Método do guia oficial de tradutores: -/// 1. rode `cargo test Languages::pt` -/// 2. para cada falha, compare `left` (esperado) com `right` (gerado) -/// 3. se o GERADO estiver correto em português, copie-o para o teste; -/// se estiver errado, conserte a REGRA, nunca o teste. -/// -/// ATENÇÃO ao histórico: o arquivo espanhol equivalente continha testes -/// que não refletiam as regras (ex.: esperava "logaritmo natural" com a -/// regra dizendo "natural log") e dano de busca-e-troca (ex.: "eigh"→"8"). -/// Não herde strings esperadas de es/ sem conferir. +//! TESTES DO IDIOMA pt — como reconciliar com as regras +//! +//! As strings esperadas abaixo foram escritas para refletir a intenção +//! das regras em Rules/Languages/pt. Vírgulas e ponto-e-vírgula marcam +//! PAUSAS inseridas pelo motor e podem divergir levemente na primeira +//! execução. Método do guia oficial de tradutores: +//! 1. rode `cargo test Languages::pt` +//! 2. para cada falha, compare `left` (esperado) com `right` (gerado) +//! 3. se o GERADO estiver correto em português, copie-o para o teste; +//! se estiver errado, conserte a REGRA, nunca o teste. +//! +//! ATENÇÃO ao histórico: o arquivo espanhol equivalente continha testes +//! que não refletiam as regras (ex.: esperava "logaritmo natural" com a +//! regra dizendo "natural log") e dano de busca-e-troca (ex.: "eigh"→"8"). +//! Não herde strings esperadas de es/ sem conferir. use crate::common::*; use anyhow::Result; diff --git a/tests/Languages/pt/escuta.rs b/tests/Languages/pt/escuta.rs index 812be28be..661cf4c85 100644 --- a/tests/Languages/pt/escuta.rs +++ b/tests/Languages/pt/escuta.rs @@ -2,12 +2,12 @@ use crate::common::*; fn falar(estilo: &str, verbosidade: &str, mathml: &str) -> String { set_rules_dir(abs_rules_dir_path()).unwrap(); - set_preference("Language", "pt".to_string()).unwrap(); - set_preference("SpeechStyle", estilo.to_string()).unwrap(); - set_preference("Verbosity", verbosidade.to_string()).unwrap(); - set_preference("SpeechOverrides_CapitalLetters", "".to_string()).unwrap(); - set_preference("ClearSpeak_SetMemberSymbol", "Auto".to_string()).unwrap(); - match set_mathml(mathml.to_string()) { + set_preference("Language", "pt").unwrap(); + set_preference("SpeechStyle", estilo).unwrap(); + set_preference("Verbosity", verbosidade).unwrap(); + set_preference("SpeechOverrides_CapitalLetters", "").unwrap(); + set_preference("ClearSpeak_SetMemberSymbol", "Auto").unwrap(); + match set_mathml(mathml) { Ok(_) => match get_spoken_text() { Ok(fala) => regex::Regex::new(r" +").unwrap().replace_all(&fala, " ").to_string(), Err(e) => format!("[ERRO ao falar: {}]", errors_to_string(&e)), diff --git a/tests/Languages/pt/intent.rs b/tests/Languages/pt/intent.rs index df164bc1b..cd43e0251 100644 --- a/tests/Languages/pt/intent.rs +++ b/tests/Languages/pt/intent.rs @@ -1,17 +1,16 @@ -/// TESTES DO IDIOMA pt — como reconciliar com as regras -/// -/// As strings esperadas abaixo foram escritas para refletir a intenção -/// das regras em Rules/Languages/pt. Vírgulas e ponto-e-vírgula marcam -/// PAUSAS inseridas pelo motor e podem divergir levemente na primeira -/// execução. Método do guia oficial de tradutores: -/// 1. rode `cargo test Languages::pt` -/// 2. para cada falha, compare `left` (esperado) com `right` (gerado) -/// 3. se o GERADO estiver correto em português, copie-o para o teste; -/// se estiver errado, conserte a REGRA, nunca o teste. -/// +//! TESTES DO IDIOMA pt — como reconciliar com as regras +//! +//! As strings esperadas abaixo foram escritas para refletir a intenção +//! das regras em Rules/Languages/pt. Vírgulas e ponto-e-vírgula marcam +//! PAUSAS inseridas pelo motor e podem divergir levemente na primeira +//! execução. Método do guia oficial de tradutores: +//! 1. rode `cargo test Languages::pt` +//! 2. para cada falha, compare `left` (esperado) com `right` (gerado) +//! 3. se o GERADO estiver correto em português, copie-o para o teste; +//! se estiver errado, conserte a REGRA, nunca o teste. -/// Tests for rules shared between various speech styles: -/// * this has tests focused on the various alphabets +// Tests for rules shared between various speech styles: +// * this has tests focused on the various alphabets use crate::common::*; use anyhow::Result; diff --git a/tests/Languages/pt/shared.rs b/tests/Languages/pt/shared.rs index cf860236b..50a28d49a 100644 --- a/tests/Languages/pt/shared.rs +++ b/tests/Languages/pt/shared.rs @@ -1,21 +1,21 @@ -/// TESTES DO IDIOMA pt — como reconciliar com as regras -/// -/// As strings esperadas abaixo foram escritas para refletir a intenção -/// das regras em Rules/Languages/pt. Vírgulas e ponto-e-vírgula marcam -/// PAUSAS inseridas pelo motor e podem divergir levemente na primeira -/// execução. Método do guia oficial de tradutores: -/// 1. rode `cargo test Languages::pt` -/// 2. para cada falha, compare `left` (esperado) com `right` (gerado) -/// 3. se o GERADO estiver correto em português, copie-o para o teste; -/// se estiver errado, conserte a REGRA, nunca o teste. -/// -/// ATENÇÃO ao histórico: o arquivo espanhol equivalente continha testes -/// que não refletiam as regras (ex.: esperava "logaritmo natural" com a -/// regra dizendo "natural log") e dano de busca-e-troca (ex.: "eigh"→"8"). -/// Não herde strings esperadas de es/ sem conferir. - -/// Tests for rules shared between various speech styles: -/// * modified var +//! TESTES DO IDIOMA pt — como reconciliar com as regras +//! +//! As strings esperadas abaixo foram escritas para refletir a intenção +//! das regras em Rules/Languages/pt. Vírgulas e ponto-e-vírgula marcam +//! PAUSAS inseridas pelo motor e podem divergir levemente na primeira +//! execução. Método do guia oficial de tradutores: +//! 1. rode `cargo test Languages::pt` +//! 2. para cada falha, compare `left` (esperado) com `right` (gerado) +//! 3. se o GERADO estiver correto em português, copie-o para o teste; +//! se estiver errado, conserte a REGRA, nunca o teste. +//! +//! ATENÇÃO ao histórico: o arquivo espanhol equivalente continha testes +//! que não refletiam as regras (ex.: esperava "logaritmo natural" com a +//! regra dizendo "natural log") e dano de busca-e-troca (ex.: "eigh"→"8"). +//! Não herde strings esperadas de es/ sem conferir. + +// Tests for rules shared between various speech styles: +// * modified var use crate::common::*; use anyhow::Result; diff --git a/tests/Languages/pt/units.rs b/tests/Languages/pt/units.rs index 47bd73056..cf0048f0d 100644 --- a/tests/Languages/pt/units.rs +++ b/tests/Languages/pt/units.rs @@ -1,21 +1,21 @@ -/// TESTES DO IDIOMA pt — como reconciliar com as regras -/// -/// As strings esperadas abaixo foram escritas para refletir a intenção -/// das regras em Rules/Languages/pt. Vírgulas e ponto-e-vírgula marcam -/// PAUSAS inseridas pelo motor e podem divergir levemente na primeira -/// execução. Método do guia oficial de tradutores: -/// 1. rode `cargo test Languages::pt` -/// 2. para cada falha, compare `left` (esperado) com `right` (gerado) -/// 3. se o GERADO estiver correto em português, copie-o para o teste; -/// se estiver errado, conserte a REGRA, nunca o teste. -/// -/// ATENÇÃO ao histórico: o arquivo espanhol equivalente continha testes -/// que não refletiam as regras (ex.: esperava "logaritmo natural" com a -/// regra dizendo "natural log") e dano de busca-e-troca (ex.: "eigh"→"8"). -/// Não herde strings esperadas de es/ sem conferir. - -/// Tests for rules shared between various speech styles: -/// * modified var +//! TESTES DO IDIOMA pt — como reconciliar com as regras +//! +//! As strings esperadas abaixo foram escritas para refletir a intenção +//! das regras em Rules/Languages/pt. Vírgulas e ponto-e-vírgula marcam +//! PAUSAS inseridas pelo motor e podem divergir levemente na primeira +//! execução. Método do guia oficial de tradutores: +//! 1. rode `cargo test Languages::pt` +//! 2. para cada falha, compare `left` (esperado) com `right` (gerado) +//! 3. se o GERADO estiver correto em português, copie-o para o teste; +//! se estiver errado, conserte a REGRA, nunca o teste. +//! +//! ATENÇÃO ao histórico: o arquivo espanhol equivalente continha testes +//! que não refletiam as regras (ex.: esperava "logaritmo natural" com a +//! regra dizendo "natural log") e dano de busca-e-troca (ex.: "eigh"→"8"). +//! Não herde strings esperadas de es/ sem conferir. + +// Tests for rules shared between various speech styles: +// * modified var use crate::common::*; use anyhow::Result;