Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 3 additions & 11 deletions src/prefs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down
105 changes: 84 additions & 21 deletions src/shim_filesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<HashMap<String, String>>> = RwLock::new(None);
static IN_MEMORY_DIRS: RwLock<Option<HashSet<String>>> = 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<PathBuf> {
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<String> {
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) => {
Expand Down Expand Up @@ -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<PathBuf> {
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
Expand All @@ -379,6 +424,10 @@ cfg_if! {
}

pub fn read_to_string_shim(path: &Path) -> Result<String> {
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(),
Expand Down Expand Up @@ -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()]);
}
}
48 changes: 24 additions & 24 deletions tests/Languages/pt/ClearSpeak/sets.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
30 changes: 15 additions & 15 deletions tests/Languages/pt/SimpleSpeak/functions.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
12 changes: 6 additions & 6 deletions tests/Languages/pt/escuta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
25 changes: 12 additions & 13 deletions tests/Languages/pt/intent.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
36 changes: 18 additions & 18 deletions tests/Languages/pt/shared.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
36 changes: 18 additions & 18 deletions tests/Languages/pt/units.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down