1 | //! Write PO files. |
2 | |
3 | use super::escape::escape; |
4 | use crate::catalog::Catalog; |
5 | use std::fs::File; |
6 | use std::io::{BufWriter, Write}; |
7 | use std::path::Path; |
8 | |
9 | fn display_width(content: &str) -> usize { |
10 | content.chars().count() |
11 | } |
12 | |
13 | fn wrap(content: &str) -> Vec<&str> { |
14 | let mut spaces: Vec<usize> = content.match_indices(' ' ).map(|m| m.0 + 1).collect(); |
15 | spaces.insert(0, 0); |
16 | if *spaces.last().unwrap() < content.len() { |
17 | spaces.push(content.len()); |
18 | } |
19 | let mut spaces = spaces.iter().peekable(); |
20 | let mut result: Vec<&str> = Vec::new(); |
21 | let mut prev_width = 0; |
22 | let mut prev_index = 0; |
23 | let mut last_line_index = 0; |
24 | while let Some(space) = spaces.next() { |
25 | let begin = *space; |
26 | let end = match spaces.peek() { |
27 | Some(next_space) => **next_space, |
28 | None => { |
29 | break; |
30 | } |
31 | }; |
32 | let segment_width = display_width(&content[begin..end]); |
33 | if prev_index == 0 || prev_width + segment_width <= 77 { |
34 | prev_width += segment_width; |
35 | prev_index = end; |
36 | } else { |
37 | result.push(&content[last_line_index..prev_index]); |
38 | last_line_index = prev_index; |
39 | prev_index = end; |
40 | prev_width = segment_width; |
41 | } |
42 | } |
43 | result.push(&content[last_line_index..]); |
44 | result |
45 | } |
46 | |
47 | /* |
48 | fn wrap(content: &str) -> Vec<String> { |
49 | let mut splits = content.split_inclusive(' '); |
50 | let mut result: Vec<String> = Vec::new(); |
51 | let mut current_line = String::new(); |
52 | let mut current_width = 0; |
53 | while let Some(segment) = splits.next() { |
54 | // println!("Segment = \"{}\"", segment); |
55 | let segment_width = display_width(segment); |
56 | // println!("Width = {} -> {}", current_width, current_width + segment_width); |
57 | if current_width + segment_width <= 77 { |
58 | current_width += segment_width; |
59 | current_line.push_str(segment); |
60 | } else { |
61 | result.push(current_line); |
62 | current_line = String::from(segment); |
63 | current_width = segment_width; |
64 | } |
65 | } |
66 | if !current_line.is_empty() { |
67 | result.push(current_line); |
68 | } |
69 | result |
70 | } |
71 | */ |
72 | |
73 | fn write_field( |
74 | writer: &mut BufWriter<std::fs::File>, |
75 | field_name: &str, |
76 | content: &str, |
77 | ) -> Result<(), std::io::Error> { |
78 | let escaped_content: String = escape(unescaped:content); |
79 | if content.match_indices(' \n' ).count() <= 1 |
80 | && field_name.len() + display_width(content:escaped_content.as_str()) <= 78 |
81 | { |
82 | writer.write_all(buf:field_name.as_bytes())?; |
83 | writer.write_all(buf:b" \"" )?; |
84 | writer.write_all(buf:escaped_content.as_bytes())?; |
85 | writer.write_all(buf:b" \"\n" )?; |
86 | } else { |
87 | writer.write_all(buf:field_name.as_bytes())?; |
88 | writer.write_all(buf:b" \"\"\n" )?; |
89 | let lines: Vec<&str> = escaped_content.split_inclusive(" \\n" ).collect(); |
90 | for line: &str in lines { |
91 | let wrapped: Vec<&str> = wrap(content:line); |
92 | for folded_line: &str in wrapped { |
93 | writer.write_all(buf:b" \"" )?; |
94 | writer.write_all(buf:folded_line.as_bytes())?; |
95 | writer.write_all(buf:b" \"\n" )?; |
96 | } |
97 | } |
98 | } |
99 | Ok(()) |
100 | } |
101 | |
102 | /// Saves a catalog to a PO file on the disk. |
103 | pub fn write(catalog: &Catalog, path: &Path) -> Result<(), std::io::Error> { |
104 | let file = File::create(path)?; |
105 | let mut writer = BufWriter::new(file); |
106 | writer.write_all(b" \nmsgid \"\"\n" )?; |
107 | write_field( |
108 | &mut writer, |
109 | "msgstr" , |
110 | catalog.metadata.export_for_po().as_str(), |
111 | )?; |
112 | writer.write_all(b" \n" )?; |
113 | for message in catalog.messages() { |
114 | if !message.comments().is_empty() { |
115 | for line in message.comments().split(' \n' ) { |
116 | writer.write_all(b"#. " )?; |
117 | writer.write_all(line.as_bytes())?; |
118 | writer.write_all(b" \n" )?; |
119 | } |
120 | } |
121 | if !message.source().is_empty() { |
122 | for line in message.source().split(' \n' ) { |
123 | writer.write_all(b"#: " )?; |
124 | writer.write_all(line.as_bytes())?; |
125 | writer.write_all(b" \n" )?; |
126 | } |
127 | } |
128 | if !message.flags().is_empty() { |
129 | writer.write_all(b"#, " )?; |
130 | writer.write_all(message.flags().to_string().as_bytes())?; |
131 | writer.write_all(b" \n" )?; |
132 | } |
133 | if !message.msgctxt().is_empty() { |
134 | write_field(&mut writer, "msgctxt" , message.msgctxt())?; |
135 | } |
136 | if message.is_singular() { |
137 | write_field(&mut writer, "msgid" , message.msgid())?; |
138 | write_field(&mut writer, "msgstr" , message.msgstr().unwrap())?; |
139 | } else { |
140 | write_field(&mut writer, "msgid" , message.msgid())?; |
141 | write_field(&mut writer, "msgid_plural" , message.msgid_plural().unwrap())?; |
142 | let plurals = message.msgstr_plural().unwrap(); |
143 | for (i, plural) in plurals.iter().enumerate() { |
144 | write_field( |
145 | &mut writer, |
146 | format!("msgstr[ {}]" , i).as_str(), |
147 | plural.as_str(), |
148 | )?; |
149 | } |
150 | } |
151 | writer.write_all(b" \n" )?; |
152 | } |
153 | writer.flush()?; |
154 | Ok(()) |
155 | } |
156 | |