1//! Defines `Catalog` struct and its iterators.
2
3mod iterator;
4
5use crate::{
6 message::CatalogMessageMutView, message::Message, message::MessageKey, message::MessageView,
7 metadata::CatalogMetadata,
8};
9pub use iterator::{Iter, IterMut, MessageMutProxy};
10use std::collections::btree_map::BTreeMap;
11
12/// `Catalog` struct represents a collection of _Messages_ stored in a `.po` or `.mo` file.
13pub struct Catalog {
14 /// Metadata of the catalog.
15 pub metadata: CatalogMetadata,
16 pub(crate) messages: Vec<Option<Message>>,
17 pub(crate) map: BTreeMap<MessageKey, usize>,
18}
19
20impl Catalog {
21 pub(crate) fn empty() -> Self {
22 Self {
23 metadata: CatalogMetadata::default(),
24 messages: vec![],
25 map: BTreeMap::new(),
26 }
27 }
28
29 /// Create a new catalog with a given metadata.
30 pub fn new(metadata: CatalogMetadata) -> Self {
31 Self {
32 metadata,
33 ..Self::empty()
34 }
35 }
36
37 /// Count number of messages in the catalog.
38 pub fn count(&self) -> usize {
39 self.messages().count()
40 }
41
42 /// Is the catalog empty?
43 pub fn is_empty(&self) -> bool {
44 self.count() == 0
45 }
46
47 /// Get an iterator over immutable messages in the catalog.
48 pub fn messages(&self) -> Iter {
49 Iter::begin(self)
50 }
51
52 /// Get an iterator over messages in the catalog that allows mutating a message in-place.
53 pub fn messages_mut(&mut self) -> IterMut {
54 IterMut::begin(self)
55 }
56
57 /// Find a message in the catalog by msgctxt, msgid and msgid_plural fields. All three fields
58 /// have to fully match. Returns None if the message is not found.
59 pub fn find_message(
60 &self,
61 msgctxt: Option<&str>,
62 msgid: &str,
63 msgid_plural: Option<&str>,
64 ) -> Option<&dyn MessageView> {
65 let key = MessageKey::gen(msgctxt, msgid, msgid_plural);
66 if let Some(&index) = self.map.get(&key) {
67 Some(self.messages[index].as_ref().unwrap())
68 } else {
69 None
70 }
71 }
72
73 /// Find a message in the catalog by msgctxt, msgid and msgid_plural fields and get a mutable view.
74 /// All three fields have to fully match. Returns None if the message is not found.
75 pub fn find_message_mut(
76 &mut self,
77 msgctxt: Option<&str>,
78 msgid: &str,
79 msgid_plural: Option<&str>,
80 ) -> Option<MessageMutProxy> {
81 let key = MessageKey::gen(msgctxt, msgid, msgid_plural);
82 if let Some(&index) = self.map.get(&key) {
83 Some(MessageMutProxy::at(self, index))
84 } else {
85 None
86 }
87 }
88
89 /// Delete a message from the catalog by msgctxt, msgid and msgid_plural fields. All three fields
90 /// have to fully match. Returns true if the message is found and deleted. Returns false
91 /// if the message does not exist in the catalog.
92 pub fn delete_message(
93 &mut self,
94 msgctxt: Option<&str>,
95 msgid: &str,
96 msgid_plural: Option<&str>,
97 ) -> bool {
98 if let Some(mut m) = self.find_message_mut(msgctxt, msgid, msgid_plural) {
99 m.delete();
100 true
101 } else {
102 false
103 }
104 }
105
106 /// Detach a message from the catalog by msgctxt, msgid and msgid_plural fields and get
107 /// the `Message` object as return value. If the message does not exist then returns None.
108 pub fn detach_message(
109 &mut self,
110 msgctxt: Option<&str>,
111 msgid: &str,
112 msgid_plural: Option<&str>,
113 ) -> Option<Message> {
114 self.find_message_mut(msgctxt, msgid, msgid_plural)
115 .map(|mut m| m.detach())
116 }
117
118 /// Append a new message to the end of the catalog.
119 /// If a message with the exact same `msgctxt`, `msgid` and `msgid_plural` fields already exists
120 /// in the catalog, then that message is replaced instead.
121 pub fn append_or_update(&mut self, m: Message) {
122 let key = MessageKey::from(&m);
123 if let Some(&index) = self.map.get(&key) {
124 self.messages[index] = Some(m);
125 } else {
126 let index = self.messages.len();
127 self.messages.push(Some(m));
128 self.map.insert(key, index);
129 }
130 }
131}
132