1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![warn(missing_docs, rustdoc::broken_intra_doc_links)]
3#[cfg(feature = "catalog")]
99#[cfg_attr(docsrs, doc(cfg(feature = "catalog")))]
100mod api;
101mod borrowed;
102pub mod diagnostic_codes;
103mod line_state;
104mod merge;
105mod parse;
106mod scan;
107mod serialize;
108mod text;
109mod utf8;
110
111#[cfg(feature = "catalog")]
112#[cfg_attr(docsrs, doc(cfg(feature = "catalog")))]
113pub use api::{
114 AiProvenance, ApiError, COMPILED_CATALOG_ARTIFACT_SCHEMA_VERSION, CatalogAuditChecks,
115 CatalogAuditDiagnostic, CatalogAuditIcuOptions, CatalogAuditMessageRef, CatalogAuditOptions,
116 CatalogAuditReport, CatalogAuditSummary, CatalogCombineInput, CatalogCombineResult,
117 CatalogCombineSelection, CatalogCombineStats, CatalogConflictStrategy, CatalogConvertResult,
118 CatalogCoverageMessage, CatalogCoverageOptions, CatalogCoverageReport,
119 CatalogFileCombineResult, CatalogFileConvertResult, CatalogFileFormat, CatalogLocaleCoverage,
120 CatalogLocaleReview, CatalogMachineTranslationMessage, CatalogMachineTranslationReview,
121 CatalogMachineTranslationStatus, CatalogMessage, CatalogMessageKey, CatalogMessageStatus,
122 CatalogMode, CatalogOrigin, CatalogReviewOptions, CatalogReviewReport, CatalogReviewSummary,
123 CatalogReviewTranslation, CatalogSemantics, CatalogSourceChange, CatalogSourceChangeKind,
124 CatalogSourceChangeReport, CatalogStats, CatalogStorageFormat, CatalogTranslationChange,
125 CatalogTranslationChangeReport, CatalogUpdateInput, CatalogUpdateResult,
126 CombineCatalogFilesOptions, CombineCatalogOptions, CompileCatalogArtifactIcuOptions,
127 CompileCatalogArtifactOptions, CompileCatalogArtifactReportOptions,
128 CompileCatalogArtifactReportSelection, CompileCatalogOptions,
129 CompileSelectedCatalogArtifactOptions, CompiledCatalog, CompiledCatalogArtifact,
130 CompiledCatalogArtifactReport, CompiledCatalogDiagnostic, CompiledCatalogIdDescription,
131 CompiledCatalogIdIndex, CompiledCatalogMissingMessage, CompiledCatalogProvenanceReport,
132 CompiledCatalogPseudolocalizationOptions, CompiledCatalogResolution,
133 CompiledCatalogResolutionKind, CompiledCatalogTranslationKind, CompiledCatalogUnavailableId,
134 CompiledKeyStrategy, CompiledMessage, CompiledTranslation, ConvertCatalogFileOptions,
135 ConvertCatalogOptions, DescribeCompiledIdsReport, Diagnostic, DiagnosticSeverity,
136 EffectiveTranslation, EffectiveTranslationRef, ExtractedMessage, ExtractedPluralMessage,
137 ExtractedSingularMessage, IcuFormatterSupportPolicy, IcuPseudolocalizationOptions,
138 IcuSyntaxPolicy, MachineMetadata, NormalizedParsedCatalog, ObsoleteInfo, ObsoleteStrategy,
139 OrderBy, ParseCatalogOptions, ParsedCatalog, PlaceholderCommentMode, PluralEncoding,
140 PluralSource, RenderOptions, SourceExtractedMessage, TranslationShape,
141 UpdateCatalogFileOptions, UpdateCatalogOptions, audit_catalogs, canonicalize_icu_with_policy,
142 combine_catalog_files, combine_catalogs, compile_catalog_artifact,
143 compile_catalog_artifact_report, compile_catalog_artifact_selected, compiled_key,
144 compiled_key_with_policy, convert_catalog, convert_catalog_file, machine_translation_hash,
145 measure_catalog_coverage, parse_catalog, parse_catalog_for_review,
146 pseudolocalize_compiled_catalog_artifact, review_catalogs, update_catalog, update_catalog_file,
147};
148pub use borrowed::{
149 BorrowedHeader, BorrowedMsgStr, BorrowedPoFile, BorrowedPoItem, parse_po_borrowed,
150};
151pub use diagnostic_codes::DiagnosticCode;
152pub use merge::{MergeMessageInput, merge_catalog};
153pub use parse::{parse_po, parse_po_bytes};
154pub use serialize::stringify_po;
155pub use text::{escape_string, extract_quoted, extract_quoted_cow, unescape_string};
156
157use core::{
158 fmt,
159 iter::FusedIterator,
160 ops::{Deref, DerefMut, Index},
161 slice,
162};
163
164#[cfg(feature = "serde")]
165use serde::{Deserialize, Serialize};
166
167use smallvec::{IntoIter as SmallVecIntoIter, SmallVec};
168
169#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
177#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
178#[cfg_attr(feature = "serde", serde(transparent))]
179pub struct PoVec<T>(SmallVec<[T; 1]>);
180
181impl<T> PoVec<T> {
182 #[must_use]
184 pub fn new() -> Self {
185 Self(SmallVec::new())
186 }
187
188 #[must_use]
190 pub fn with_capacity(capacity: usize) -> Self {
191 Self(SmallVec::with_capacity(capacity))
192 }
193
194 #[must_use]
196 pub fn len(&self) -> usize {
197 self.0.len()
198 }
199
200 #[must_use]
202 pub fn is_empty(&self) -> bool {
203 self.0.is_empty()
204 }
205
206 #[must_use]
208 pub fn as_slice(&self) -> &[T] {
209 self.0.as_slice()
210 }
211
212 #[must_use]
214 pub fn as_mut_slice(&mut self) -> &mut [T] {
215 self.0.as_mut_slice()
216 }
217
218 pub fn iter(&self) -> slice::Iter<'_, T> {
220 self.as_slice().iter()
221 }
222
223 pub fn iter_mut(&mut self) -> slice::IterMut<'_, T> {
225 self.as_mut_slice().iter_mut()
226 }
227
228 pub fn push(&mut self, value: T) {
230 self.0.push(value);
231 }
232
233 pub fn clear(&mut self) {
235 self.0.clear();
236 }
237
238 #[must_use]
240 pub fn into_vec(self) -> Vec<T> {
241 self.0.into_vec()
242 }
243}
244
245impl<T> AsRef<[T]> for PoVec<T> {
246 fn as_ref(&self) -> &[T] {
247 self.as_slice()
248 }
249}
250
251impl<T> AsMut<[T]> for PoVec<T> {
252 fn as_mut(&mut self) -> &mut [T] {
253 self.as_mut_slice()
254 }
255}
256
257impl<T> Deref for PoVec<T> {
258 type Target = [T];
259
260 fn deref(&self) -> &Self::Target {
261 self.as_slice()
262 }
263}
264
265impl<T> DerefMut for PoVec<T> {
266 fn deref_mut(&mut self) -> &mut Self::Target {
267 self.as_mut_slice()
268 }
269}
270
271impl<T> Extend<T> for PoVec<T> {
272 fn extend<I>(&mut self, iter: I)
273 where
274 I: IntoIterator<Item = T>,
275 {
276 self.0.extend(iter);
277 }
278}
279
280impl<T> From<Vec<T>> for PoVec<T> {
281 fn from(value: Vec<T>) -> Self {
282 Self(SmallVec::from_vec(value))
283 }
284}
285
286impl<T, const N: usize> From<[T; N]> for PoVec<T> {
287 fn from(value: [T; N]) -> Self {
288 Self(value.into_iter().collect())
289 }
290}
291
292impl<T> FromIterator<T> for PoVec<T> {
293 fn from_iter<I>(iter: I) -> Self
294 where
295 I: IntoIterator<Item = T>,
296 {
297 Self(iter.into_iter().collect())
298 }
299}
300
301impl<T> From<PoVec<T>> for Vec<T> {
302 fn from(value: PoVec<T>) -> Self {
303 value.into_vec()
304 }
305}
306
307impl<T> IntoIterator for PoVec<T> {
308 type Item = T;
309 type IntoIter = PoVecIntoIter<T>;
310
311 fn into_iter(self) -> Self::IntoIter {
312 PoVecIntoIter {
313 inner: self.0.into_iter(),
314 }
315 }
316}
317
318impl<'a, T> IntoIterator for &'a PoVec<T> {
319 type Item = &'a T;
320 type IntoIter = slice::Iter<'a, T>;
321
322 fn into_iter(self) -> Self::IntoIter {
323 self.iter()
324 }
325}
326
327impl<'a, T> IntoIterator for &'a mut PoVec<T> {
328 type Item = &'a mut T;
329 type IntoIter = slice::IterMut<'a, T>;
330
331 fn into_iter(self) -> Self::IntoIter {
332 self.iter_mut()
333 }
334}
335
336impl<T, U> PartialEq<[U]> for PoVec<T>
337where
338 T: PartialEq<U>,
339{
340 fn eq(&self, other: &[U]) -> bool {
341 self.as_slice() == other
342 }
343}
344
345impl<T, U> PartialEq<&[U]> for PoVec<T>
346where
347 T: PartialEq<U>,
348{
349 fn eq(&self, other: &&[U]) -> bool {
350 self.as_slice() == *other
351 }
352}
353
354impl<T, U> PartialEq<Vec<U>> for PoVec<T>
355where
356 T: PartialEq<U>,
357{
358 fn eq(&self, other: &Vec<U>) -> bool {
359 self.as_slice() == other.as_slice()
360 }
361}
362
363pub struct PoVecIntoIter<T> {
365 inner: SmallVecIntoIter<[T; 1]>,
366}
367
368impl<T> Iterator for PoVecIntoIter<T> {
369 type Item = T;
370
371 fn next(&mut self) -> Option<Self::Item> {
372 self.inner.next()
373 }
374
375 fn size_hint(&self) -> (usize, Option<usize>) {
376 self.inner.size_hint()
377 }
378}
379
380impl<T> DoubleEndedIterator for PoVecIntoIter<T> {
381 fn next_back(&mut self) -> Option<Self::Item> {
382 self.inner.next_back()
383 }
384}
385
386impl<T> ExactSizeIterator for PoVecIntoIter<T> {}
387
388impl<T> FusedIterator for PoVecIntoIter<T> {}
389
390#[derive(Debug, Clone, PartialEq, Eq, Default)]
392#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
393#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
394pub struct PoFile {
395 pub comments: Vec<String>,
397 pub extracted_comments: Vec<String>,
399 pub headers: Vec<Header>,
401 pub items: Vec<PoItem>,
403}
404
405#[derive(Debug, Clone, PartialEq, Eq, Default)]
407#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
408#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
409pub struct Header {
410 pub key: String,
412 pub value: String,
414}
415
416#[derive(Debug, Clone, PartialEq, Eq, Default)]
418#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
419#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
420pub struct PoItem {
421 pub msgid: String,
423 pub msgctxt: Option<String>,
425 pub references: PoVec<String>,
427 pub msgid_plural: Option<String>,
429 pub msgstr: MsgStr,
431 pub comments: PoVec<String>,
433 pub extracted_comments: PoVec<String>,
435 pub flags: PoVec<String>,
443 pub metadata: PoVec<(String, String)>,
445 pub obsolete: bool,
447 pub nplurals: usize,
449}
450
451impl PoItem {
452 #[must_use]
454 pub fn new(nplurals: usize) -> Self {
455 Self {
456 nplurals,
457 ..Self::default()
458 }
459 }
460
461 pub(crate) fn clear_for_reuse(&mut self, nplurals: usize) {
462 self.msgid.clear();
463 self.msgctxt = None;
464 self.references.clear();
465 self.msgid_plural = None;
466 self.msgstr = MsgStr::None;
467 self.comments.clear();
468 self.extracted_comments.clear();
469 self.flags.clear();
470 self.metadata.clear();
471 self.obsolete = false;
472 self.nplurals = nplurals;
473 }
474}
475
476#[derive(Debug, Clone, PartialEq, Eq, Default)]
478#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
479#[cfg_attr(
480 feature = "serde",
481 serde(tag = "kind", content = "value", rename_all = "snake_case")
482)]
483pub enum MsgStr {
484 #[default]
486 None,
487 Singular(String),
489 Plural(Vec<String>),
491}
492
493impl MsgStr {
494 #[must_use]
501 pub fn plural(values: Vec<String>) -> Self {
502 Self::Plural(values)
503 }
504
505 #[must_use]
507 pub const fn is_empty(&self) -> bool {
508 matches!(self, Self::None)
509 }
510
511 #[must_use]
513 pub fn len(&self) -> usize {
514 match self {
515 Self::None => 0,
516 Self::Singular(_) => 1,
517 Self::Plural(values) => values.len(),
518 }
519 }
520
521 #[must_use]
523 pub fn first(&self) -> Option<&str> {
524 match self {
525 Self::None => None,
526 Self::Singular(value) => Some(value.as_str()),
527 Self::Plural(values) => values.first().map(String::as_str),
528 }
529 }
530
531 #[must_use]
533 pub fn get(&self, index: usize) -> Option<&str> {
534 match self {
535 Self::Singular(value) if index == 0 => Some(value.as_str()),
536 Self::None | Self::Singular(_) => None,
537 Self::Plural(values) => values.get(index).map(String::as_str),
538 }
539 }
540
541 #[must_use]
543 pub fn iter(&self) -> MsgStrIter<'_> {
544 match self {
545 Self::None => MsgStrIter::empty(),
546 Self::Singular(value) => MsgStrIter::single(value.as_str()),
547 Self::Plural(values) => MsgStrIter::many(values.iter()),
548 }
549 }
550
551 #[must_use]
553 pub fn into_vec(self) -> Vec<String> {
554 match self {
555 Self::None => Vec::new(),
556 Self::Singular(value) => vec![value],
557 Self::Plural(values) => values,
558 }
559 }
560}
561
562impl From<String> for MsgStr {
563 fn from(value: String) -> Self {
564 Self::Singular(value)
565 }
566}
567
568impl From<Vec<String>> for MsgStr {
569 fn from(values: Vec<String>) -> Self {
570 match values.len() {
571 0 => Self::None,
572 1 => Self::Singular(values.into_iter().next().expect("single msgstr value")),
573 _ => Self::Plural(values),
574 }
575 }
576}
577
578impl<'a> IntoIterator for &'a MsgStr {
579 type Item = &'a str;
580 type IntoIter = MsgStrIter<'a>;
581
582 fn into_iter(self) -> Self::IntoIter {
583 self.iter()
584 }
585}
586
587impl Index<usize> for MsgStr {
588 type Output = String;
589
590 fn index(&self, index: usize) -> &Self::Output {
591 match self {
592 Self::None => panic!("msgstr index out of bounds: no translations present"),
593 Self::Singular(value) if index == 0 => value,
594 Self::Singular(_) => panic!("msgstr index out of bounds: singular translation"),
595 Self::Plural(values) => &values[index],
596 }
597 }
598}
599
600pub struct MsgStrIter<'a> {
602 inner: MsgStrIterInner<'a>,
603}
604
605enum MsgStrIterInner<'a> {
606 Empty,
607 Single(Option<&'a str>),
608 Many(std::slice::Iter<'a, String>),
609}
610
611impl<'a> MsgStrIter<'a> {
612 const fn empty() -> Self {
613 Self {
614 inner: MsgStrIterInner::Empty,
615 }
616 }
617
618 const fn single(value: &'a str) -> Self {
619 Self {
620 inner: MsgStrIterInner::Single(Some(value)),
621 }
622 }
623
624 const fn many(iter: std::slice::Iter<'a, String>) -> Self {
625 Self {
626 inner: MsgStrIterInner::Many(iter),
627 }
628 }
629}
630
631impl<'a> Iterator for MsgStrIter<'a> {
632 type Item = &'a str;
633
634 fn next(&mut self) -> Option<Self::Item> {
635 match &mut self.inner {
636 MsgStrIterInner::Empty => None,
637 MsgStrIterInner::Single(value) => value.take(),
638 MsgStrIterInner::Many(iter) => iter.next().map(String::as_str),
639 }
640 }
641}
642
643#[derive(Debug, Clone, PartialEq, Eq)]
645#[non_exhaustive]
646pub struct SerializeOptions {
647 pub fold_length: usize,
649 pub compact_multiline: bool,
651}
652
653impl Default for SerializeOptions {
654 fn default() -> Self {
655 Self {
656 fold_length: 80,
657 compact_multiline: true,
658 }
659 }
660}
661
662impl SerializeOptions {
663 #[must_use]
665 pub fn with_fold_length(mut self, fold_length: usize) -> Self {
666 self.fold_length = fold_length;
667 self
668 }
669
670 #[must_use]
672 pub fn with_compact_multiline(mut self, compact_multiline: bool) -> Self {
673 self.compact_multiline = compact_multiline;
674 self
675 }
676}
677
678#[derive(Debug, Clone, Copy, PartialEq, Eq)]
680pub struct ParsePosition {
681 offset: usize,
682 line: usize,
683 column: usize,
684}
685
686impl ParsePosition {
687 #[must_use]
692 pub const fn new(offset: usize, line: usize, column: usize) -> Self {
693 Self {
694 offset,
695 line,
696 column,
697 }
698 }
699
700 #[must_use]
702 pub const fn offset(self) -> usize {
703 self.offset
704 }
705
706 #[must_use]
708 pub const fn line(self) -> usize {
709 self.line
710 }
711
712 #[must_use]
714 pub const fn column(self) -> usize {
715 self.column
716 }
717}
718
719#[derive(Debug, Clone, PartialEq, Eq)]
721pub struct ParseError {
722 message: String,
723 position: Option<ParsePosition>,
724}
725
726impl ParseError {
727 #[must_use]
729 pub fn new(message: impl Into<String>) -> Self {
730 Self {
731 message: message.into(),
732 position: None,
733 }
734 }
735
736 #[must_use]
738 pub fn with_position(message: impl Into<String>, position: ParsePosition) -> Self {
739 Self {
740 message: message.into(),
741 position: Some(position),
742 }
743 }
744
745 #[must_use]
747 pub fn message(&self) -> &str {
748 &self.message
749 }
750
751 #[must_use]
753 pub const fn position(&self) -> Option<ParsePosition> {
754 self.position
755 }
756
757 pub(crate) fn with_position_if_missing(mut self, position: ParsePosition) -> Self {
758 self.position.get_or_insert(position);
759 self
760 }
761}
762
763impl fmt::Display for ParseError {
764 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
765 f.write_str(&self.message)
766 }
767}
768
769impl std::error::Error for ParseError {}
770
771#[cfg(test)]
772mod tests {
773 use super::{MsgStr, ParseError, ParsePosition, PoVec, SerializeOptions};
774
775 #[cfg(feature = "serde")]
776 use super::{Header, PoFile, PoItem};
777
778 #[test]
779 fn parse_error_accessors_preserve_message_and_optional_position() {
780 let error = ParseError::new("invalid PO string");
781 assert_eq!(error.message(), "invalid PO string");
782 assert_eq!(error.position(), None);
783 assert_eq!(error.to_string(), "invalid PO string");
784
785 let position = ParsePosition::new(12, 2, 3);
786 let positioned = ParseError::with_position("invalid PO string", position);
787 assert_eq!(positioned.message(), "invalid PO string");
788 assert_eq!(positioned.position(), Some(position));
789 assert_eq!(positioned.position().map(ParsePosition::offset), Some(12));
790 assert_eq!(positioned.position().map(ParsePosition::line), Some(2));
791 assert_eq!(positioned.position().map(ParsePosition::column), Some(3));
792 assert_eq!(positioned.to_string(), "invalid PO string");
793 }
794
795 #[test]
796 fn msgstr_get_returns_none_for_empty_values() {
797 let msgstr = MsgStr::None;
798
799 assert_eq!(msgstr.get(0), None);
800 }
801
802 #[test]
803 fn msgstr_get_returns_singular_value_at_zero() {
804 let msgstr = MsgStr::from("Hallo".to_owned());
805
806 assert_eq!(msgstr.get(0), Some("Hallo"));
807 assert_eq!(msgstr.get(1), None);
808 }
809
810 #[test]
811 fn msgstr_get_returns_plural_values_by_index() {
812 let msgstr = MsgStr::from(vec!["eins".to_owned(), "viele".to_owned()]);
813
814 assert_eq!(msgstr.get(0), Some("eins"));
815 assert_eq!(msgstr.get(1), Some("viele"));
816 assert_eq!(msgstr.get(2), None);
817 }
818
819 #[test]
820 fn msgstr_plural_constructor_preserves_single_slot_shape() {
821 let values = vec!["translation".to_owned()];
822 let plural = MsgStr::plural(values.clone());
823
824 assert_eq!(plural, MsgStr::Plural(values.clone()));
825 assert_ne!(plural, MsgStr::from(values.clone()));
826 assert_eq!(plural.len(), 1);
827 assert_eq!(plural.first(), Some("translation"));
828 assert_eq!(plural.into_vec(), values);
829 }
830
831 #[test]
832 fn msgstr_helpers_cover_empty_singular_and_plural_shapes() {
833 let empty = MsgStr::from(Vec::<String>::new());
834 assert!(empty.is_empty());
835 assert_eq!(empty.len(), 0);
836 assert_eq!(empty.first(), None);
837 assert_eq!(empty.iter().count(), 0);
838 assert_eq!(empty.into_vec(), Vec::<String>::new());
839
840 let singular = MsgStr::from(vec!["Hallo".to_owned()]);
841 assert!(!singular.is_empty());
842 assert_eq!(singular.len(), 1);
843 assert_eq!(singular.first(), Some("Hallo"));
844 assert_eq!((&singular).into_iter().collect::<Vec<_>>(), vec!["Hallo"]);
845 assert_eq!(singular[0], "Hallo");
846 assert_eq!(singular.into_vec(), vec!["Hallo"]);
847
848 let plural = MsgStr::from(vec!["eins".to_owned(), "viele".to_owned()]);
849 assert_eq!(plural.len(), 2);
850 assert_eq!(plural.first(), Some("eins"));
851 assert_eq!(plural.iter().collect::<Vec<_>>(), vec!["eins", "viele"]);
852 assert_eq!(plural[1], "viele");
853 assert_eq!(plural.into_vec(), vec!["eins", "viele"]);
854 }
855
856 #[test]
857 fn povec_keeps_slice_iteration_and_vec_conversion_ergonomics() {
858 let mut values = PoVec::new();
859 assert!(values.is_empty());
860
861 values.push("src/app.rs:10".to_owned());
862 values.extend(["src/app.rs:20".to_owned()]);
863
864 assert_eq!(values.len(), 2);
865 assert_eq!(
866 values.as_slice(),
867 ["src/app.rs:10".to_owned(), "src/app.rs:20".to_owned()]
868 );
869 assert_eq!(
870 values.iter().map(String::as_str).collect::<Vec<_>>(),
871 ["src/app.rs:10", "src/app.rs:20"]
872 );
873
874 let from_vec = PoVec::from(vec!["fuzzy".to_owned()]);
875 assert_eq!(from_vec, vec!["fuzzy".to_owned()]);
876 assert_eq!(Vec::<String>::from(from_vec), vec!["fuzzy".to_owned()]);
877 }
878
879 #[test]
880 fn povec_trait_views_cover_mutable_slice_and_reference_iteration() {
881 let mut values = PoVec::with_capacity(2);
882 values.extend([1, 2]);
883
884 assert_eq!(AsRef::<[i32]>::as_ref(&values), [1, 2]);
885
886 AsMut::<[i32]>::as_mut(&mut values)[0] = 3;
887 values.as_mut_slice()[1] = 4;
888 for value in &mut values {
889 *value += 1;
890 }
891 values.iter_mut().for_each(|value| *value *= 2);
892
893 let as_slice: &[i32] = &values;
894 assert_eq!(as_slice, [8, 10]);
895
896 let as_mut_slice: &mut [i32] = &mut values;
897 as_mut_slice[0] += 1;
898
899 let expected = [9, 10];
900 assert!(values == expected[..]);
901 assert!(values == expected.as_slice());
902 }
903
904 #[test]
905 fn povec_owned_iterator_preserves_values_without_exposing_backing_type() {
906 let values = PoVec::from(["one".to_owned(), "other".to_owned()]);
907
908 assert_eq!(
909 values.into_iter().collect::<Vec<_>>(),
910 vec!["one".to_owned(), "other".to_owned()]
911 );
912 }
913
914 #[test]
915 fn povec_owned_iterator_supports_double_ended_size_hints() {
916 let mut iter = PoVec::from([1, 2, 3]).into_iter();
917
918 assert_eq!(iter.size_hint(), (3, Some(3)));
919 assert_eq!(iter.next_back(), Some(3));
920 assert_eq!(iter.size_hint(), (2, Some(2)));
921 assert_eq!(iter.next(), Some(1));
922 assert_eq!(iter.next_back(), Some(2));
923 assert_eq!(iter.next(), None);
924 assert_eq!(iter.next_back(), None);
925 }
926
927 #[test]
928 fn serialize_option_builders_set_fields() {
929 let options = SerializeOptions::default()
930 .with_fold_length(120)
931 .with_compact_multiline(false);
932
933 assert_eq!(options.fold_length, 120);
934 assert!(!options.compact_multiline);
935 }
936
937 #[cfg(feature = "serde")]
938 #[test]
939 fn po_file_serde_round_trips_owned_document_shape() {
940 let file = PoFile {
941 comments: vec!["translator note".to_owned()],
942 headers: vec![Header {
943 key: "Language".to_owned(),
944 value: "de".to_owned(),
945 }],
946 items: vec![PoItem {
947 msgid: "Hello".to_owned(),
948 msgstr: MsgStr::from("Hallo".to_owned()),
949 references: vec!["src/app.rs:10".to_owned()].into(),
950 nplurals: 1,
951 ..PoItem::default()
952 }],
953 ..PoFile::default()
954 };
955
956 let json = serde_json::to_value(&file).expect("PO file serialization must succeed");
957 assert_eq!(json["items"][0]["msgstr"]["kind"], "singular");
958 assert_eq!(json["items"][0]["msgstr"]["value"], "Hallo");
959
960 let roundtrip: PoFile =
961 serde_json::from_value(json).expect("PO file deserialization must succeed");
962 assert_eq!(roundtrip, file);
963 }
964}