Skip to main content

ferrocat_po/
lib.rs

1//! Performance-first PO parsing and serialization.
2
3mod api;
4mod borrowed;
5mod merge;
6mod parse;
7mod scan;
8mod serialize;
9mod text;
10
11pub use api::{
12    ApiError, CatalogMessage, CatalogMessageExtra, CatalogMessageKey, CatalogOrigin, CatalogStats,
13    CatalogUpdateInput, CatalogUpdateResult, Diagnostic, DiagnosticSeverity, EffectiveTranslation,
14    EffectiveTranslationRef, ExtractedMessage, ExtractedPluralMessage, ExtractedSingularMessage,
15    NormalizedParsedCatalog, ObsoleteStrategy, OrderBy, ParseCatalogOptions, ParsedCatalog,
16    PlaceholderCommentMode, PluralEncoding, PluralSource, SourceExtractedMessage, TranslationShape,
17    UpdateCatalogFileOptions, UpdateCatalogOptions, parse_catalog, update_catalog,
18    update_catalog_file,
19};
20pub use borrowed::{
21    BorrowedHeader, BorrowedMsgStr, BorrowedPoFile, BorrowedPoItem, parse_po_borrowed,
22};
23pub use merge::{ExtractedMessage as MergeExtractedMessage, merge_catalog};
24pub use parse::parse_po;
25pub use serialize::stringify_po;
26pub use text::{escape_string, extract_quoted, extract_quoted_cow, unescape_string};
27
28use core::{fmt, ops::Index};
29
30#[derive(Debug, Clone, PartialEq, Eq, Default)]
31pub struct PoFile {
32    pub comments: Vec<String>,
33    pub extracted_comments: Vec<String>,
34    pub headers: Vec<Header>,
35    pub items: Vec<PoItem>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Default)]
39pub struct Header {
40    pub key: String,
41    pub value: String,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Default)]
45pub struct PoItem {
46    pub msgid: String,
47    pub msgctxt: Option<String>,
48    pub references: Vec<String>,
49    pub msgid_plural: Option<String>,
50    pub msgstr: MsgStr,
51    pub comments: Vec<String>,
52    pub extracted_comments: Vec<String>,
53    pub flags: Vec<String>,
54    pub metadata: Vec<(String, String)>,
55    pub obsolete: bool,
56    pub nplurals: usize,
57}
58
59impl PoItem {
60    pub fn new(nplurals: usize) -> Self {
61        Self {
62            nplurals,
63            ..Self::default()
64        }
65    }
66
67    pub(crate) fn clear_for_reuse(&mut self, nplurals: usize) {
68        self.msgid.clear();
69        self.msgctxt = None;
70        self.references.clear();
71        self.msgid_plural = None;
72        self.msgstr = MsgStr::None;
73        self.comments.clear();
74        self.extracted_comments.clear();
75        self.flags.clear();
76        self.metadata.clear();
77        self.obsolete = false;
78        self.nplurals = nplurals;
79    }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Default)]
83pub enum MsgStr {
84    #[default]
85    None,
86    Singular(String),
87    Plural(Vec<String>),
88}
89
90impl MsgStr {
91    pub fn is_empty(&self) -> bool {
92        matches!(self, Self::None)
93    }
94
95    pub fn len(&self) -> usize {
96        match self {
97            Self::None => 0,
98            Self::Singular(_) => 1,
99            Self::Plural(values) => values.len(),
100        }
101    }
102
103    pub fn first(&self) -> Option<&String> {
104        match self {
105            Self::None => None,
106            Self::Singular(value) => Some(value),
107            Self::Plural(values) => values.first(),
108        }
109    }
110
111    pub fn first_str(&self) -> Option<&str> {
112        self.first().map(String::as_str)
113    }
114
115    pub fn iter(&self) -> MsgStrIter<'_> {
116        match self {
117            Self::None => MsgStrIter::empty(),
118            Self::Singular(value) => MsgStrIter::single(value),
119            Self::Plural(values) => MsgStrIter::many(values.iter()),
120        }
121    }
122
123    pub fn into_vec(self) -> Vec<String> {
124        match self {
125            Self::None => Vec::new(),
126            Self::Singular(value) => vec![value],
127            Self::Plural(values) => values,
128        }
129    }
130}
131
132impl From<String> for MsgStr {
133    fn from(value: String) -> Self {
134        Self::Singular(value)
135    }
136}
137
138impl From<Vec<String>> for MsgStr {
139    fn from(values: Vec<String>) -> Self {
140        match values.len() {
141            0 => Self::None,
142            1 => Self::Singular(values.into_iter().next().expect("single msgstr value")),
143            _ => Self::Plural(values),
144        }
145    }
146}
147
148impl Index<usize> for MsgStr {
149    type Output = String;
150
151    fn index(&self, index: usize) -> &Self::Output {
152        match self {
153            Self::None => panic!("msgstr index out of bounds: no translations present"),
154            Self::Singular(value) if index == 0 => value,
155            Self::Singular(_) => panic!("msgstr index out of bounds: singular translation"),
156            Self::Plural(values) => &values[index],
157        }
158    }
159}
160
161pub struct MsgStrIter<'a> {
162    inner: MsgStrIterInner<'a>,
163}
164
165enum MsgStrIterInner<'a> {
166    Empty,
167    Single(Option<&'a String>),
168    Many(std::slice::Iter<'a, String>),
169}
170
171impl<'a> MsgStrIter<'a> {
172    fn empty() -> Self {
173        Self {
174            inner: MsgStrIterInner::Empty,
175        }
176    }
177
178    fn single(value: &'a String) -> Self {
179        Self {
180            inner: MsgStrIterInner::Single(Some(value)),
181        }
182    }
183
184    fn many(iter: std::slice::Iter<'a, String>) -> Self {
185        Self {
186            inner: MsgStrIterInner::Many(iter),
187        }
188    }
189}
190
191impl<'a> Iterator for MsgStrIter<'a> {
192    type Item = &'a String;
193
194    fn next(&mut self) -> Option<Self::Item> {
195        match &mut self.inner {
196            MsgStrIterInner::Empty => None,
197            MsgStrIterInner::Single(value) => value.take(),
198            MsgStrIterInner::Many(iter) => iter.next(),
199        }
200    }
201}
202
203#[derive(Debug, Clone, PartialEq, Eq)]
204pub struct SerializeOptions {
205    pub fold_length: usize,
206    pub compact_multiline: bool,
207}
208
209impl Default for SerializeOptions {
210    fn default() -> Self {
211        Self {
212            fold_length: 80,
213            compact_multiline: true,
214        }
215    }
216}
217
218#[derive(Debug, Clone, PartialEq, Eq)]
219pub struct ParseError {
220    message: String,
221}
222
223impl ParseError {
224    pub fn new(message: impl Into<String>) -> Self {
225        Self {
226            message: message.into(),
227        }
228    }
229}
230
231impl fmt::Display for ParseError {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        f.write_str(&self.message)
234    }
235}
236
237impl std::error::Error for ParseError {}