1//! Write PO files.
2
3use super::escape::escape;
4use crate::catalog::Catalog;
5use std::fs::File;
6use std::io::{BufWriter, Write};
7use std::path::Path;
8
9fn display_width(content: &str) -> usize {
10 content.chars().count()
11}
12
13fn 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/*
48fn 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
73fn 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.
103pub 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