use std::{
any::type_name,
fs,
path::{Path, PathBuf},
};
use crate::normalize_lexical;
use crate::config::ConfigResult;
pub(crate) fn write_template(path: &Path, content: &str) -> ConfigResult<()> {
if let Some(parent) = path
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
{
fs::create_dir_all(parent)?;
}
fs::write(path, content)?;
Ok(())
}
pub(crate) fn resolve_config_template_output<S>(output: Option<PathBuf>) -> ConfigResult<PathBuf> {
let current_dir = std::env::current_dir()?;
let output = output
.as_deref()
.and_then(Path::file_name)
.map(PathBuf::from)
.map(|file_name| default_config_output_dir::<S>().join(file_name))
.unwrap_or_else(default_config_template_output::<S>);
let output = current_dir.join(output);
Ok(normalize_lexical(output))
}
pub(crate) fn default_config_template_output<S>() -> PathBuf {
let target_name = root_config_target_name::<S>();
default_config_output_dir::<S>().join(format!("{target_name}.example.yaml"))
}
pub(crate) fn default_config_schema_output<S>() -> PathBuf {
let target_name = root_config_target_name::<S>();
default_config_output_dir::<S>().join(format!("{target_name}.schema.json"))
}
fn default_config_output_dir<S>() -> PathBuf {
PathBuf::from("config").join(root_config_target_name::<S>())
}
pub(crate) fn root_config_target_name<S>() -> String {
type_segment_to_snake_case(last_type_segment(type_name::<S>()))
}
fn last_type_segment(name: &str) -> &str {
name.rsplit("::")
.next()
.unwrap_or(name)
.split('<')
.next()
.unwrap_or("config")
}
fn type_segment_to_snake_case(name: &str) -> String {
let chars = name.chars().collect::<Vec<_>>();
let mut output = String::new();
for (index, ch) in chars.iter().copied().enumerate() {
if ch.is_ascii_uppercase() {
let previous = index.checked_sub(1).and_then(|index| chars.get(index));
let next = chars.get(index + 1);
let starts_word = previous.is_some_and(|previous| {
previous.is_ascii_lowercase()
|| previous.is_ascii_digit()
|| (previous.is_ascii_uppercase()
&& next.is_some_and(|next| next.is_ascii_lowercase()))
});
if starts_word && !output.ends_with('_') {
output.push('_');
}
output.push(ch.to_ascii_lowercase());
} else if ch.is_ascii_alphanumeric() {
output.push(ch.to_ascii_lowercase());
} else if !output.ends_with('_') {
output.push('_');
}
}
let output = output.trim_matches('_');
if output.is_empty() {
"config".to_owned()
} else {
output.to_owned()
}
}