use crate::{
config::{
settings::Settings,
v1::{self as config},
},
error::IoError,
executor, model,
};
use colored::Colorize;
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,
reason = "byte counts are well within f64's exact-integer range; precision loss is irrelevant for human-readable display"
)]
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())
}
async fn generate_json_language<F>(
&self,
config_file: &config::ConfigFile<F>,
translations: &model::Translations,
settings: &Settings,
language: model::Language,
) -> Result<(), JsonOutputError> {
let config = &config_file.config;
let json = translations.translations_json(
language,
settings
.template_engine
.as_ref()
.map(|engine| engine.as_ref().clone()),
settings.strict,
)?;
let json = Arc::new(serde_json::to_vec_pretty(&json).map_err(model::json::Error::from)?);
let gzip_task = tokio::task::spawn_blocking({
let json = Arc::clone(&json);
move || crate::gzip::gzipped_size(&*json)
});
let mut outcomes = Vec::with_capacity(config.outputs.json.len());
for output in &config.outputs.json {
let output_path = self.resolve_json_output_path(&output.path, language)?;
let output_path =
executor::resolve_path(config_file.config_dir.as_deref(), &output_path);
outcomes.push(self.write_output(&output_path, &json, settings).await?);
}
let num_bytes_gzip = gzip_task.await?.unwrap_or(0);
let prefix = self.logger.language_log_prefix(&config.name, language);
for outcome in outcomes {
let sizes = if settings.dry_run {
format!(
"({}, {} gzipped)",
human_readable_bytes(json.len()),
human_readable_bytes(num_bytes_gzip).bold()
)
.bright_black()
} else {
format!(
"({}, {} gzipped)",
human_readable_bytes(json.len()),
human_readable_bytes(num_bytes_gzip).bold().magenta()
)
.normal()
};
println!("{prefix} {outcome} {sizes}");
}
Ok(())
}
pub(crate) async fn generate_json_outputs<F>(
&self,
config_file: &config::ConfigFile<F>,
translations: &Arc<model::Translations>,
settings: &Settings,
) -> Result<(), JsonOutputError> {
if config_file.config.outputs.json.is_empty() {
return Ok(());
}
futures::future::try_join_all(config_file.config.languages.iter().map(|language| {
self.generate_json_language(config_file, translations, settings, **language)
}))
.await?;
Ok(())
}
}