use std::io;
use anyhow::bail;
use glossa_shared::{
ToCompactString, fmt_compact,
tap::{Pipe, Tap},
};
use itertools::Itertools;
use lang_id::{LangID, RawID};
use crate::{
AnyResult, Generator, MiniStr,
generator::{MapType, to_lower_snake_case},
};
impl Generator<'_> {
pub fn output_locales_fn(
&self,
map_type: MapType,
const_lang_id: bool,
) -> AnyResult<String> {
let raw_locales = self.collect_raw_locales(map_type)?;
let locales_len = raw_locales.len();
let new_header = || self.new_locales_fn_header(&locales_len, &const_lang_id);
if !const_lang_id {
return new_header()
.tap_mut(|buf| {
let push_str = |s| buf.push_str(s);
[&format!("{raw_locales:#?}"), "}\n"]
.into_iter()
.for_each(push_str);
})
.pipe(Ok);
}
raw_locales
.iter()
.map(try_conv_const_id)
.try_fold(
new_header(), |mut acc, fn_name| {
let push_str = |s| acc.push_str(s);
["\n ", &fn_name?, ","]
.into_iter()
.for_each(push_str);
Ok::<_, anyhow::Error>(acc)
},
)?
.tap_mut(|buf| buf.push_str(" ]\n}"))
.pipe(Ok)
}
pub fn collect_raw_locales(&self, map_type: MapType) -> io::Result<Vec<MiniStr>> {
match map_type.is_dsl() {
true => match self.get_or_init_dsl_maps() {
x if x.is_empty() => "// Error: Empty DSL Map"
.pipe(io::Error::other)
.pipe(Err),
data => data
.keys()
.map(|id| id.to_compact_string())
.collect_vec()
.pipe(Ok),
},
_ => map_type
.get_non_dsl_maps(self)?
.keys()
.map(|id| id.to_compact_string())
.collect_vec()
.pipe(Ok),
}
}
pub(crate) fn new_locales_fn_header(
&self,
locales_len: &usize,
const_lang_id: &bool,
) -> String {
let ret_type = {
match *const_lang_id {
true => fmt_compact!(
r#"[super::lang_id::LangID; {locales_len}] {{
#[allow(unused_imports)]
use super::lang_id::RawID;
use super::lang_id::consts::*;
["#
),
_ => fmt_compact!("[&'static str; {locales_len}] {{\n "),
}
};
let s_header = format!("const fn all_locales() -> {ret_type}",);
self.new_fn_header(&s_header)
}
pub fn output_mod_rs(&self, map_type: MapType) -> io::Result<String> {
let mod_vis = self.get_mod_visibility();
let feature_names = self.collect_cargo_feature_names(map_type)?;
let module_names = self.collect_rs_mod_names(map_type)?;
ensure_length_equal(feature_names.len(), module_names.len())?;
feature_names
.iter()
.zip(module_names)
.map(|(feat_name, mod_name)| {
fmt_compact!(
r###"
#[cfg(feature = "{feat_name}")]
{mod_vis}mod {mod_name};
"###
)
})
.collect::<String>()
.pipe(Ok)
}
pub fn collect_cargo_feature_names(
&self,
map_type: MapType,
) -> io::Result<Vec<MiniStr>> {
let feat_prefix = self.get_feature_prefix();
self
.collect_raw_locales(map_type)?
.into_iter()
.map(|x| fmt_compact!("{feat_prefix}{x}"))
.collect_vec()
.pipe(Ok)
}
pub fn collect_rs_mod_names(&self, map_type: MapType) -> io::Result<Vec<MiniStr>> {
let mod_prefix = self.get_mod_prefix();
self
.collect_raw_locales(map_type)?
.into_iter()
.map(|x| fmt_compact!("{mod_prefix}{id}", id = to_lower_snake_case(x)))
.collect_vec()
.pipe(Ok)
}
#[cfg(feature = "json")]
pub fn output_cargo_features(&self, map_type: MapType) -> io::Result<String> {
let feat_prefix = self.get_feature_prefix();
let all_feat_locales = self.collect_cargo_feature_names(map_type)?;
let all_arr_json = serde_json::to_string(&all_feat_locales)?;
format!(
"{} = []\n{feat_prefix}all = {all_arr_json}",
all_feat_locales.join(" = []\n")
)
.pipe(Ok)
}
}
fn try_conv_const_id(id: &MiniStr) -> AnyResult<MiniStr> {
use lang_id::matches::{get_fn_name, match_id};
match match_id(id.as_bytes())
.to_compact_string()
.as_str()
{
x if x == id => id
.as_bytes()
.pipe(get_fn_name)
.to_compact_string()
.pipe(Ok),
_ => {
let id = id.parse::<LangID>()?;
if id.variants().count() >= 1 {
bail!("This ID ({id}) contains variants and cannot be converted to const.")
}
RawID::try_from_str(
id.language.as_str(),
id.script
.map(|x| x.to_compact_string())
.unwrap_or_default()
.as_str(),
id.region
.map(|x| x.to_compact_string())
.unwrap_or_default()
.as_str(),
)?
.to_compact_string()
.pipe(Ok)
}
}
}
pub(crate) fn ensure_length_equal(
feat_names_len: usize,
mod_names_len: usize,
) -> io::Result<()> {
(mod_names_len == feat_names_len)
.then_some(())
.ok_or_else(|| {
io::Error::other("feature_names and module_names are not equal in length")
})
}
#[cfg(test)]
mod tests {
use glossa_shared::display::puts;
use super::*;
use crate::generator::dbg_generator::new_generator;
#[ignore]
#[test]
fn test_list_all_locales() -> AnyResult<()> {
new_generator()
.with_visibility(crate::Visibility::Pub)
.output_locales_fn(
MapType::Regular,
true,
)?
.pipe_ref(puts)
.pipe(Ok)
}
#[ignore]
#[test]
fn test_gen_mod_rs_str() -> AnyResult<()> {
let generator = new_generator().with_mod_visibility(crate::Visibility::Pub);
let raw_locales = generator.collect_raw_locales(MapType::Regular)?;
let mod_prefix = generator.get_mod_prefix();
let feature_prefix = generator.get_feature_prefix();
let mod_vis = generator.get_mod_visibility();
let fn_content = raw_locales
.iter()
.map(|id| {
let mod_name = to_lower_snake_case(id);
fmt_compact!(
r###"
#[cfg(feature = "{feature_prefix}{id}")]
{mod_vis}mod {mod_prefix}{mod_name};
"###
)
})
.collect::<String>();
println!("{fn_content}");
Ok(())
}
#[ignore]
#[test]
fn test_output_mod_rs() -> AnyResult<()> {
new_generator()
.with_mod_visibility(crate::Visibility::PubCrate)
.output_mod_rs(MapType::Regular)?
.pipe_ref(puts)
.pipe(Ok)
}
#[test]
#[ignore]
#[cfg(feature = "json")]
fn test_gen_cargo_features() -> AnyResult<()> {
use glossa_shared::display::puts;
let generator = new_generator();
let feat_prefix = generator.get_feature_prefix();
let all_locales = generator
.collect_raw_locales(MapType::Regular)?
.into_iter()
.map(|x| fmt_compact!("{feat_prefix}{x}"))
.collect_vec();
let all = serde_json::to_string(&all_locales)?;
format!(
"{} = []\n{feat_prefix}all = {all}",
all_locales.join(" = []\n")
)
.pipe_ref(puts)
.pipe(Ok)
}
#[ignore]
#[test]
fn test_output_cargo_features() -> AnyResult<()> {
new_generator()
.output_cargo_features(MapType::DSL)?
.pipe_ref(puts)
.pipe(Ok)
}
}