use std::fs::read_to_string;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Context, Result};
use serde_derive::Deserialize;
use toml;
#[derive(Debug)]
pub struct Crate<'a> {
pub name: String,
pub version: String,
pub path: PathBuf,
pub parent: Option<&'a Crate<'a>>,
pub config_file_path: PathBuf,
pub i18n_config: Option<I18nConfig>,
}
impl<'a> Crate<'a> {
pub fn from<P: Into<PathBuf>>(
path: P,
parent: Option<&'a Crate>,
config_file_path: P,
) -> Result<Crate<'a>> {
let path_into = path.into();
let config_file_path_into = config_file_path.into();
let cargo_path = path_into.join("Cargo.toml");
let toml_str = read_to_string(cargo_path.clone())
.with_context(|| format!("trouble reading {0:?}", cargo_path))?;
let cargo_toml: toml::Value = toml::from_str(toml_str.as_ref())
.with_context(|| format!("trouble parsing {0:?}", cargo_path))?;
let package = cargo_toml
.as_table()
.ok_or(anyhow!("Cargo.toml needs have sections (such as the \"gettext\" section when using gettext."))?
.get("package")
.ok_or(anyhow!("Cargo.toml needs to have a \"package\" section."))?
.as_table()
.ok_or(anyhow!(
"Cargo.toml's \"package\" section needs to contain values."
))?;
let name = package
.get("name")
.ok_or(anyhow!("Cargo.toml needs to specify a package name."))?
.as_str()
.ok_or(anyhow!("Cargo.toml's package name needs to be a string."))?;
let version = package
.get("version")
.ok_or(anyhow!("Cargo.toml needs to specify a package version."))?
.as_str()
.ok_or(anyhow!(
"Cargo.toml's package version needs to be a string."
))?;
let full_config_file_path = path_into.join(&config_file_path_into);
let i18n_config = if full_config_file_path.exists() {
Some(
I18nConfig::from_file(&full_config_file_path).with_context(|| {
format!(
"Cannot load i18n config file: {0}.",
full_config_file_path.to_string_lossy()
)
})?,
)
} else {
None
};
Ok(Crate {
name: String::from(name),
version: String::from(version),
path: path_into,
parent,
config_file_path: config_file_path_into,
i18n_config,
})
}
pub fn module_name(&self) -> String {
self.name.replace("-", "_")
}
pub fn parent_active_config(&'a self) -> Option<(&'a Crate, &'a I18nConfig)> {
match self.parent {
Some(parent) => parent.active_config(),
None => None,
}
}
pub fn active_config(&'a self) -> Option<(&'a Crate, &'a I18nConfig)> {
match &self.i18n_config {
Some(config) => {
match &config.gettext {
Some(gettext_config) => {
if gettext_config.extract_to_parent {
return self.parent_active_config();
}
}
None => {}
}
return Some((self, &config));
}
None => {
return self.parent_active_config();
}
};
}
pub fn config_or_err(&self) -> Result<&I18nConfig> {
match &self.i18n_config {
Some(config) => Ok(config),
None => Err(anyhow!(format!(
"There is no i18n config file \"{0}\" present in this crate \"{1}\".",
self.config_file_path.to_string_lossy(),
self.name
))),
}
}
pub fn gettext_config_or_err(&self) -> Result<&GettextConfig> {
match &self.config_or_err()?.gettext {
Some(gettext_config) => Ok(gettext_config),
None => Err(anyhow!(format!(
"There is no gettext config available the i18n config \"{0}\".",
self.config_file_path.to_string_lossy(),
))),
}
}
pub fn collated_subcrate(&self) -> bool {
let parent_extract_to_subcrate = self
.parent
.map(|parent_crate| {
parent_crate
.gettext_config_or_err()
.map(|parent_gettext_config| parent_gettext_config.collate_extracted_subcrates)
.unwrap_or(false)
})
.unwrap_or(false);
let extract_to_parent = self
.gettext_config_or_err()
.map(|gettext_config| gettext_config.extract_to_parent)
.unwrap_or(false);
return parent_extract_to_subcrate && extract_to_parent;
}
}
#[derive(Deserialize, Debug)]
pub struct I18nConfig {
pub src_locale: String,
pub target_locales: Vec<String>,
pub subcrates: Option<Vec<PathBuf>>,
pub gettext: Option<GettextConfig>,
}
impl I18nConfig {
pub fn from_file<P: AsRef<Path>>(toml_path: P) -> Result<I18nConfig> {
let toml_path_final: &Path = toml_path.as_ref();
let toml_str = read_to_string(toml_path_final).with_context(|| {
format!(
"Trouble reading file \"{0}\".",
toml_path_final.to_string_lossy()
)
})?;
let config: I18nConfig = toml::from_str(toml_str.as_ref()).with_context(|| {
format!(
"There was an error while parsing an i18n config file \"{0}\".",
toml_path_final.to_string_lossy()
)
})?;
Ok(config)
}
}
#[derive(Deserialize, Debug)]
pub struct GettextConfig {
pub output_dir: PathBuf,
#[serde(default)]
pub extract_to_parent: bool,
#[serde(default)]
pub collate_extracted_subcrates: bool,
pub copyright_holder: Option<String>,
pub msgid_bugs_address: Option<String>,
pub xtr: Option<bool>,
pot_dir: Option<PathBuf>,
po_dir: Option<PathBuf>,
mo_dir: Option<PathBuf>,
}
impl GettextConfig {
pub fn pot_dir(&self) -> PathBuf {
self.pot_dir.clone().unwrap_or(self.output_dir.join("pot"))
}
pub fn po_dir(&self) -> PathBuf {
self.po_dir.clone().unwrap_or(self.output_dir.join("po"))
}
pub fn mo_dir(&self) -> PathBuf {
self.mo_dir.clone().unwrap_or(self.output_dir.join("mo"))
}
}