use super::escape::escape;
use crate::catalog::Catalog;
use crate::message::MessageView;
use std::cmp::Ordering;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
fn display_width(content: &str) -> usize {
content.chars().count()
}
fn wrap(content: &str) -> Vec<&str> {
let mut spaces: Vec<usize> = content.match_indices(' ').map(|m| m.0 + 1).collect();
spaces.insert(0, 0);
if *spaces.last().unwrap() < content.len() {
spaces.push(content.len());
}
let mut spaces = spaces.iter().peekable();
let mut result: Vec<&str> = Vec::new();
let mut prev_width = 0;
let mut prev_index = 0;
let mut last_line_index = 0;
while let Some(space) = spaces.next() {
let begin = *space;
let end = match spaces.peek() {
Some(next_space) => **next_space,
None => {
break;
}
};
let segment_width = display_width(&content[begin..end]);
if prev_index == 0 || prev_width + segment_width <= 77 {
prev_width += segment_width;
prev_index = end;
} else {
result.push(&content[last_line_index..prev_index]);
last_line_index = prev_index;
prev_index = end;
prev_width = segment_width;
}
}
result.push(&content[last_line_index..]);
result
}
fn write_field<W: Write>(
writer: &mut BufWriter<W>,
field_name: &str,
content: &str,
) -> Result<(), std::io::Error> {
let escaped_content = escape(content);
if content.match_indices('\n').count() <= 1
&& field_name.len() + display_width(escaped_content.as_str()) <= 78
{
writer.write_all(field_name.as_bytes())?;
writer.write_all(b" \"")?;
writer.write_all(escaped_content.as_bytes())?;
writer.write_all(b"\"\n")?;
} else {
writer.write_all(field_name.as_bytes())?;
writer.write_all(b" \"\"\n")?;
let lines: Vec<&str> = escaped_content.split_inclusive("\\n").collect();
for line in lines {
let wrapped = wrap(line);
for folded_line in wrapped {
writer.write_all(b"\"")?;
writer.write_all(folded_line.as_bytes())?;
writer.write_all(b"\"\n")?;
}
}
}
Ok(())
}
fn write_internal<W: Write>(
catalog: &Catalog,
writer: &mut BufWriter<W>,
comparator: Option<Box<dyn FnMut(&&dyn MessageView, &&dyn MessageView) -> Ordering>>,
) -> Result<(), std::io::Error> {
if !catalog.preheader.is_empty() {
for line in &catalog.preheader {
if line.is_empty() {
writer.write_all(b"#\n")?;
} else {
writer.write_all(b"# ")?;
writer.write_all(line.as_bytes())?;
writer.write_all(b"\n")?;
}
}
}
writer.write_all(b"msgid \"\"\n")?;
write_field(writer, "msgstr", catalog.metadata.export_for_po().as_str())?;
writer.write_all(b"\n")?;
let messages = if let Some(comparator) = comparator {
let mut sorting = catalog.messages().collect::<Vec<&dyn MessageView>>();
sorting.sort_by(comparator);
sorting
} else {
catalog.messages().collect::<Vec<&dyn MessageView>>()
};
for message in messages {
if !message.translator_comments().is_empty() {
for line in message.translator_comments().split('\n') {
writer.write_all(b"# ")?;
writer.write_all(line.as_bytes())?;
writer.write_all(b"\n")?;
}
}
if !message.extracted_comments().is_empty() {
for line in message.extracted_comments().split('\n') {
writer.write_all(b"#. ")?;
writer.write_all(line.as_bytes())?;
writer.write_all(b"\n")?;
}
}
if !message.source().is_empty() {
for line in message.source().split('\n') {
writer.write_all(b"#: ")?;
writer.write_all(line.as_bytes())?;
writer.write_all(b"\n")?;
}
}
if !message.flags().is_empty() {
writer.write_all(b"#, ")?;
writer.write_all(message.flags().to_string().as_bytes())?;
writer.write_all(b"\n")?;
}
if let Some(ctxt) = message.msgctxt() {
write_field(writer, "msgctxt", ctxt)?;
}
if message.is_singular() {
write_field(writer, "msgid", message.msgid())?;
write_field(writer, "msgstr", message.msgstr().unwrap())?;
} else {
write_field(writer, "msgid", message.msgid())?;
write_field(writer, "msgid_plural", message.msgid_plural().unwrap())?;
let plurals = message.msgstr_plural().unwrap();
for (i, plural) in plurals.iter().enumerate() {
write_field(writer, format!("msgstr[{}]", i).as_str(), plural.as_str())?;
}
}
writer.write_all(b"\n")?;
}
writer.flush()?;
Ok(())
}
pub fn write<W: Write>(catalog: &Catalog, writer: &mut BufWriter<W>) -> Result<(), std::io::Error> {
write_internal(catalog, writer, None)
}
pub fn write_to_file(catalog: &Catalog, path: &Path) -> Result<(), std::io::Error> {
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
write_internal(catalog, &mut writer, None)
}
pub fn write_sort_by<W: Write>(
catalog: &Catalog,
writer: &mut BufWriter<W>,
comparator: Box<dyn FnMut(&&dyn MessageView, &&dyn MessageView) -> Ordering>,
) -> Result<(), std::io::Error> {
write_internal(catalog, writer, Some(comparator))
}
pub fn write_to_file_sort_by(
catalog: &Catalog,
path: &Path,
comparator: Box<dyn FnMut(&&dyn MessageView, &&dyn MessageView) -> Ordering>,
) -> Result<(), std::io::Error> {
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
write_internal(catalog, &mut writer, Some(comparator))
}