1use color_eyre::eyre::eyre;
2use std::path::PathBuf;
3
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7pub type Result<T> = color_eyre::eyre::Result<T>;
9
10pub const RIMLOC_SCHEMA_VERSION: u32 = 1;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct TransUnit {
17 pub key: String,
18 pub source: Option<String>,
20 pub path: PathBuf,
22 pub line: Option<usize>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct PoEntry {
29 pub key: String,
30 pub value: String,
31 pub reference: Option<String>,
34}
35
36#[derive(Debug, Error)]
38pub enum RimLocError {
39 #[error("{0}")]
40 Other(String),
41}
42
43pub fn parse_simple_po(input: &str) -> Result<Vec<PoEntry>> {
46 let mut entries = Vec::new();
47 let mut cur_ref: Option<String> = None;
48 let mut cur_id: Option<String> = None;
49
50 fn unquote(raw: &str) -> String {
51 let trimmed = raw.trim();
52 if trimmed.starts_with('"') && trimmed.ends_with('"') && trimmed.len() >= 2 {
53 trimmed[1..trimmed.len() - 1].to_string()
54 } else {
55 trimmed.to_string()
56 }
57 }
58
59 for line in input.lines() {
60 let trimmed = line.trim();
61 if trimmed.is_empty() {
62 continue;
63 }
64 if let Some(rest) = trimmed.strip_prefix("#:") {
65 cur_ref = Some(rest.trim().to_string());
66 continue;
67 }
68 if let Some(rest) = trimmed.strip_prefix("msgid") {
69 let eq = rest
70 .trim_start()
71 .strip_prefix(' ')
72 .unwrap_or(rest)
73 .trim_start_matches('=');
74 cur_id = Some(unquote(eq.trim()));
75 continue;
76 }
77 if let Some(rest) = trimmed.strip_prefix("msgstr") {
78 let eq = rest
79 .trim_start()
80 .strip_prefix(' ')
81 .unwrap_or(rest)
82 .trim_start_matches('=');
83 let val = unquote(eq.trim());
84 if let Some(id) = cur_id.take() {
85 entries.push(PoEntry {
86 key: id,
87 value: val,
88 reference: cur_ref.take(),
89 });
90 } else {
91 return Err(eyre!("Malformed PO entry: msgstr without msgid"));
92 }
93 }
94 }
95
96 Ok(entries)
97}