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