166 lines
4.6 KiB
Python
Executable File
166 lines
4.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
fix_malformed.py — Corrige ficheiros .po com msgstr mal formados.
|
|
|
|
Problemas detectados e corrigidos:
|
|
1. Aspa de fecho escapada (linha termina com \")
|
|
2. Sequência de escape inválida (backslash + espaço)
|
|
3. Aspas internas não escapadas
|
|
|
|
Se existirem linhas de continuação, usa-as como tradução correcta.
|
|
Se não houver continuação, fecha a aspa em falta.
|
|
|
|
Uso:
|
|
python3 fix_malformed.py plugin-pt_PT.po [plugin2-pt_PT.po ...]
|
|
find /path/to/library -name "*-pt_PT.po" | xargs python3 fix_malformed.py
|
|
|
|
Author: Descomplicar®
|
|
Version: 1.1.0
|
|
"""
|
|
import sys
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
def last_quote_is_escaped(s: str) -> bool:
|
|
"""Verifica se a última aspas está escapada com backslash."""
|
|
if not s.endswith('"'):
|
|
return False
|
|
count = 0
|
|
i = len(s) - 2
|
|
while i >= 0 and s[i] == '\\':
|
|
count += 1
|
|
i -= 1
|
|
return count % 2 == 1
|
|
|
|
|
|
def has_invalid_escape(s: str) -> bool:
|
|
"""Detecta sequências de escape inválidas (backslash + espaço/tab)."""
|
|
return bool(re.search(r'\\[ \t]', s))
|
|
|
|
|
|
def has_unescaped_internal_quote(s: str) -> bool:
|
|
"""Detecta aspas não escapadas no interior da string."""
|
|
i = 0
|
|
while i < len(s):
|
|
if s[i] == '\\':
|
|
i += 2
|
|
continue
|
|
if s[i] == '"':
|
|
return True
|
|
i += 1
|
|
return False
|
|
|
|
|
|
def is_malformed(line: str) -> bool:
|
|
"""Determina se uma linha msgstr está mal formada."""
|
|
if not line.startswith('msgstr "'):
|
|
return False
|
|
content = line[7:]
|
|
# Vazio é válido
|
|
if content == '""':
|
|
return False
|
|
# Não termina em aspas, ou a aspas final está escapada
|
|
if not content.endswith('"') or last_quote_is_escaped(content):
|
|
return True
|
|
# Escape inválido no interior
|
|
if has_invalid_escape(content):
|
|
return True
|
|
# Aspas internas não escapadas
|
|
inner = content[1:-1]
|
|
if has_unescaped_internal_quote(inner):
|
|
return True
|
|
return False
|
|
|
|
|
|
def process_file(filepath: str) -> tuple[int, bool]:
|
|
"""
|
|
Processa um ficheiro .po e corrige malformações.
|
|
Devolve (número de correcções, sucesso do .mo).
|
|
"""
|
|
path = Path(filepath)
|
|
if not path.exists():
|
|
print(f" ERRO: ficheiro não encontrado: {filepath}", file=sys.stderr)
|
|
return 0, False
|
|
|
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
lines = f.readlines()
|
|
|
|
new_lines = []
|
|
i = 0
|
|
fixes = 0
|
|
|
|
while i < len(lines):
|
|
line = lines[i].rstrip('\n')
|
|
|
|
if is_malformed(line):
|
|
# Verificar se há linhas de continuação
|
|
continuation = []
|
|
j = i + 1
|
|
while j < len(lines) and lines[j].rstrip('\n').startswith('"'):
|
|
continuation.append(lines[j])
|
|
j += 1
|
|
|
|
if continuation:
|
|
# Usar continuação como tradução correcta
|
|
new_lines.append('msgstr ""\n')
|
|
for c in continuation:
|
|
new_lines.append(c)
|
|
fixes += 1
|
|
i = j
|
|
continue
|
|
elif not line.endswith('"') or last_quote_is_escaped(line):
|
|
# Fechar aspa em falta
|
|
new_lines.append(line + '"\n')
|
|
fixes += 1
|
|
i += 1
|
|
continue
|
|
|
|
new_lines.append(lines[i])
|
|
i += 1
|
|
|
|
if fixes > 0:
|
|
shutil.copy2(filepath, filepath + '.bak_malformed')
|
|
with open(filepath, 'w', encoding='utf-8') as f:
|
|
f.writelines(new_lines)
|
|
|
|
mo_path = filepath.replace('.po', '.mo')
|
|
r = subprocess.run(
|
|
['msgfmt', filepath, '-o', mo_path],
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
)
|
|
mo_ok = r.returncode == 0
|
|
mo_status = "OK" if mo_ok else f"ERRO: {r.stderr.decode()[:80]}"
|
|
print(f" {path.name}: {fixes} correcção(ões) | .mo: {mo_status}")
|
|
return fixes, mo_ok
|
|
else:
|
|
print(f" {path.name}: sem malformações")
|
|
return 0, True
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print("Uso: python3 fix_malformed.py <ficheiro.po> [...]")
|
|
print(" find /path -name '*-pt_PT.po' | xargs python3 fix_malformed.py")
|
|
sys.exit(1)
|
|
|
|
total_fixes = 0
|
|
total_files = 0
|
|
errors = 0
|
|
|
|
for filepath in sys.argv[1:]:
|
|
total_files += 1
|
|
fixes, ok = process_file(filepath)
|
|
total_fixes += fixes
|
|
if not ok:
|
|
errors += 1
|
|
|
|
print(f"\nTotal: {total_fixes} correcções em {total_files} ficheiro(s) | Erros .mo: {errors}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|