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