| 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 | |