use crate::{
config::{
config_file_names,
v1::{self as config, PathOrGlobPattern},
},
error::{self, Error, IoError},
executor, model,
progress::{Logger, relative_to},
target::Target,
};
use colored::Colorize;
use futures::stream::{self, Stream, StreamExt, TryStreamExt};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[derive(thiserror::Error, Debug)]
pub enum JsonOutputError {
#[error(transparent)]
Io(#[from] IoError),
#[error(transparent)]
Json(#[from] model::json::Error),
#[error("failed to template {template:?}")]
Template {
template: String,
#[source]
source: handlebars::RenderError,
},
#[error(transparent)]
Task(#[from] tokio::task::JoinError),
}
#[allow(clippy::cast_precision_loss)]
fn human_readable_bytes(len: usize) -> String {
human_bytes::human_bytes(len as f64)
}
impl executor::Executor {
fn resolve_json_output_path(
&self,
path: &Path,
language: model::Language,
) -> Result<PathBuf, JsonOutputError> {
#[derive(Debug, serde::Serialize)]
struct TemplateData {
language: model::Language,
}
let template = path.to_string_lossy().to_string();
let path = self
.handlebars
.render_template(&template, &TemplateData { language })
.map_err(|source| JsonOutputError::Template { template, source })?;
Ok(path.into())
}
pub(crate) async fn generate_json_outputs<F>(
&self,
config_file: &config::ConfigFile<F>,
translations: &Arc<model::Translations>,
strict: bool,
) -> Result<(), JsonOutputError> {
let config = &config_file.config;
let json_output_paths = config.languages.iter().flat_map(|language| {
config.outputs.json.iter().cloned().map(move |config| {
let output_path = self.resolve_json_output_path(&config.path, **language)?;
Ok::<_, JsonOutputError>((config, output_path, language))
})
});
stream::iter(json_output_paths)
.map(|res| async { res })
.buffer_unordered(16)
.try_for_each(|res| {
let translations = Arc::clone(translations);
async move {
let (json_config, json_output_path, language) = res;
let json_output_path = executor::resolve_path(
config_file.config_dir.as_deref(),
&json_output_path,
);
let mut json = Vec::new();
{
let mut writer = std::io::BufWriter::new(std::io::Cursor::new(&mut json));
translations.write_translations_json(
**language,
config
.template_engine
.as_ref()
.map(|tpl| tpl.as_ref().clone()),
strict,
&mut writer,
)?;
let _ = writer.flush();
}
let json = Arc::new(json);
let gzip_task = tokio::task::spawn_blocking({
let json = Arc::clone(&json);
move || crate::gzip::gzipped_size(&*json)
});
let dry_run = self.dry_run;
let write_task = tokio::task::spawn({
let json_output_path = json_output_path.clone();
let json = Arc::clone(&json);
async move {
if dry_run {
return Ok(());
}
executor::write_to_file(&json_output_path, &*json).await?;
Ok::<_, JsonOutputError>(())
}
});
let () = write_task.await??;
let num_bytes_gzip = gzip_task.await?.unwrap_or(0);
if dry_run {
println!(
"{} {} {}",
self.logger.language_log_prefix(&config.name, **language),
self.logger.dry_run_would_write(&json_output_path),
format!(
"({}, {} gzipped)",
human_readable_bytes(json.len()),
human_readable_bytes(num_bytes_gzip).bold()
)
.bright_black()
);
} else {
let displayed_path = if self.logger.use_absolute_paths {
json_output_path.display().to_string()
} else {
relative_to(
self.global_base_dir_for_display.as_deref(),
&json_output_path,
)
.display()
.to_string()
};
println!(
"{} wrote {} ({}, {} gzipped)",
self.logger.language_log_prefix(&config.name, **language),
displayed_path,
human_readable_bytes(json.len()),
human_readable_bytes(num_bytes_gzip)
.bold()
.magenta()
);
}
Ok::<_, JsonOutputError>(())
}
})
.await
}
}