use crate::{
StoreConfig, StoreResolver, platform_config_dir, platform_data_dir,
resolver::{
ChainResolver, EmbeddedResolver, FileLocaleResolver, FileResolver, GitResolver,
HttpResolver, RegistryResolver, ResolverError, StyleResolver,
},
};
use citum_schema::{Locale, Style};
use std::{error::Error, fs, path::Path, path::PathBuf};
type DynStyleResolver = dyn StyleResolver<Style = Style, Locale = Locale>;
pub fn build_standard_chain() -> Result<ChainResolver, Box<dyn Error + Send + Sync>> {
Ok(ChainResolver::new(standard_resolvers()?))
}
pub fn build_chain_with_file_locale_dir(
locales_dir: PathBuf,
) -> Result<ChainResolver, Box<dyn Error + Send + Sync>> {
let mut resolvers: Vec<Box<DynStyleResolver>> =
vec![Box::new(FileLocaleResolver::new(locales_dir))];
resolvers.extend(standard_resolvers()?);
Ok(ChainResolver::new(resolvers))
}
fn standard_resolvers() -> Result<Vec<Box<DynStyleResolver>>, Box<dyn Error + Send + Sync>> {
let mut resolvers: Vec<Box<DynStyleResolver>> = vec![Box::new(FileResolver)];
if let Some(data_dir) = platform_data_dir()
&& data_dir.exists()
{
let config = StoreConfig::load().unwrap_or_default();
resolvers.push(Box::new(StoreResolver::new(
data_dir,
config.store_format(),
)));
}
if let Some(http) = HttpResolver::from_platform_cache_dir() {
resolvers.push(Box::new(http));
}
if let Some(git) = GitResolver::from_platform_cache_dir() {
resolvers.push(Box::new(git));
}
resolvers.extend(registry_resolvers()?);
resolvers.push(Box::new(EmbeddedResolver));
Ok(resolvers)
}
#[must_use]
pub fn load_locale_or_default(
chain: &dyn StyleResolver<Style = Style, Locale = Locale>,
id: &str,
) -> Locale {
match chain.resolve_locale(id) {
Ok(locale) => locale.resolved_for(id),
Err(ResolverError::LocaleNotFound(_)) => Locale::en_us().resolved_for(id),
Err(_) => Locale::en_us().resolved_for(id),
}
}
#[derive(serde::Deserialize)]
struct RegistrySourceRecord {
name: String,
}
fn registry_sources_path() -> Option<std::path::PathBuf> {
platform_config_dir().map(|dir| dir.join("registry-sources.json"))
}
fn configured_registry_path(name: &str) -> Option<std::path::PathBuf> {
platform_config_dir().map(|dir| dir.join("registries").join(format!("{name}.yaml")))
}
fn configured_registry_names() -> Vec<String> {
let Some(path) = registry_sources_path() else {
return Vec::new();
};
let Ok(bytes) = fs::read(path) else {
return Vec::new();
};
let Ok(records) = serde_json::from_slice::<Vec<RegistrySourceRecord>>(&bytes) else {
return Vec::new();
};
records.into_iter().map(|record| record.name).collect()
}
fn registry_resolvers() -> Result<Vec<Box<DynStyleResolver>>, Box<dyn Error + Send + Sync>> {
let mut resolvers: Vec<Box<DynStyleResolver>> = Vec::new();
let local_path = Path::new("citum-registry.yaml");
if local_path.exists()
&& let Ok(registry) = citum_schema::StyleRegistry::load_from_file(local_path)
{
resolvers.push(Box::new(
RegistryResolver::new(registry).with_base_dir(std::env::current_dir()?),
));
}
for name in configured_registry_names() {
let Some(path) = configured_registry_path(&name) else {
continue;
};
if !path.exists() {
continue;
}
let Ok(registry) = citum_schema::StyleRegistry::load_from_file(&path) else {
continue;
};
let base_dir = path.parent().unwrap_or(Path::new(".")).to_path_buf();
let resolver = RegistryResolver::new(registry).with_base_dir(base_dir);
let resolver = if let Some(http) = HttpResolver::from_platform_cache_dir() {
resolver.with_http(http)
} else {
resolver
};
let resolver = if let Some(git) = GitResolver::from_platform_cache_dir() {
resolver.with_git(git)
} else {
resolver
};
resolvers.push(Box::new(resolver));
}
if let Ok(config) = StoreConfig::load() {
let tracked_names = configured_registry_names();
for registry_config in &config.registries {
if tracked_names.contains(®istry_config.name) {
continue;
}
if let Some(path) = configured_registry_path(®istry_config.name)
&& path.exists()
&& let Ok(registry) = citum_schema::StyleRegistry::load_from_file(&path)
{
let base_dir = path.parent().unwrap_or(Path::new(".")).to_path_buf();
let resolver = RegistryResolver::new(registry).with_base_dir(base_dir);
let resolver = if let Some(http) = HttpResolver::from_platform_cache_dir() {
resolver.with_http(http)
} else {
resolver
};
let resolver = if let Some(git) = GitResolver::from_platform_cache_dir() {
resolver.with_git(git)
} else {
resolver
};
resolvers.push(Box::new(resolver));
continue;
}
if let Some(http) = HttpResolver::from_platform_cache_dir()
&& let Ok(bytes) = http.fetch_bytes(®istry_config.url)
&& let Ok(registry) = serde_yaml::from_slice::<citum_schema::StyleRegistry>(&bytes)
{
if let Some(path) = configured_registry_path(®istry_config.name)
&& let Some(parent) = path.parent()
{
let _ = fs::create_dir_all(parent);
if let Ok(mut tmp) = tempfile::NamedTempFile::new_in(parent) {
let _ = std::io::Write::write_all(&mut tmp, &bytes);
let _ = tmp.persist(&path);
}
}
let base_dir = configured_registry_path(®istry_config.name)
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
.unwrap_or_else(|| Path::new(".").to_path_buf());
let resolver = RegistryResolver::new(registry).with_base_dir(base_dir);
let resolver = resolver.with_http(http);
let resolver = if let Some(git) = GitResolver::from_platform_cache_dir() {
resolver.with_git(git)
} else {
resolver
};
resolvers.push(Box::new(resolver));
}
}
}
let resolver = RegistryResolver::new(citum_schema::embedded::default_registry())
.with_base_dir(std::env::current_dir()?);
let resolver = if let Some(http) = HttpResolver::from_platform_cache_dir() {
resolver.with_http(http)
} else {
resolver
};
let resolver = if let Some(git) = GitResolver::from_platform_cache_dir() {
resolver.with_git(git)
} else {
resolver
};
resolvers.push(Box::new(resolver));
Ok(resolvers)
}