#[derive(Clone, Debug, Default)]
pub struct PackageResolver {
config_group: String,
package_override: Option<String>,
header_package: Option<String>,
}
impl PackageResolver {
pub fn new() -> Self {
Self::default()
}
pub fn with_config_group(mut self, group: &str) -> Self {
self.config_group = group.to_string();
self
}
pub fn with_package_override(mut self, package: &str) -> Self {
self.package_override = Some(package.to_string());
self
}
pub fn with_header_package(mut self, package: &str) -> Self {
self.header_package = Some(package.to_string());
self
}
pub fn resolve(&self) -> String {
if let Some(ref pkg) = self.package_override {
return self.resolve_special_package(pkg);
}
if let Some(ref pkg) = self.header_package {
return self.resolve_special_package(pkg);
}
self.config_group.clone()
}
fn resolve_special_package(&self, package: &str) -> String {
match package {
"_global_" => String::new(),
"_group_" => self.config_group.clone(),
"_name_" => self.get_config_name(),
pkg => {
if pkg.starts_with("_group_.") {
let suffix = &pkg[8..];
if self.config_group.is_empty() {
suffix.to_string()
} else {
format!("{}.{}", self.config_group, suffix)
}
} else {
pkg.to_string()
}
}
}
}
fn get_config_name(&self) -> String {
if let Some(pos) = self.config_group.rfind('/') {
self.config_group[pos + 1..].to_string()
} else {
self.config_group.clone()
}
}
}
pub fn parse_package_header(content: &str) -> Option<String> {
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with('#') {
if let Some(pkg_start) = trimmed.find("@package") {
let rest = &trimmed[pkg_start + 8..].trim();
let pkg_value = rest.split_whitespace().next()?;
return Some(pkg_value.to_string());
}
continue;
}
if !trimmed.is_empty() {
break;
}
}
None
}
pub fn compute_target_path(package: &str, key_path: &str) -> String {
if package.is_empty() {
key_path.to_string()
} else if key_path.is_empty() {
package.to_string()
} else {
format!("{}.{}", package, key_path)
}
}
pub fn split_path(path: &str) -> Vec<String> {
if path.is_empty() {
Vec::new()
} else {
path.split('.').map(String::from).collect()
}
}
pub fn join_path(components: &[String]) -> String {
components.join(".")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_package_resolver_default() {
let resolver = PackageResolver::new().with_config_group("db/mysql");
assert_eq!(resolver.resolve(), "db/mysql");
}
#[test]
fn test_package_resolver_override() {
let resolver = PackageResolver::new()
.with_config_group("db/mysql")
.with_package_override("database");
assert_eq!(resolver.resolve(), "database");
}
#[test]
fn test_package_resolver_global() {
let resolver = PackageResolver::new()
.with_config_group("db/mysql")
.with_package_override("_global_");
assert_eq!(resolver.resolve(), "");
}
#[test]
fn test_package_resolver_group() {
let resolver = PackageResolver::new()
.with_config_group("db/mysql")
.with_package_override("_group_");
assert_eq!(resolver.resolve(), "db/mysql");
}
#[test]
fn test_package_resolver_name() {
let resolver = PackageResolver::new()
.with_config_group("db/mysql")
.with_package_override("_name_");
assert_eq!(resolver.resolve(), "mysql");
}
#[test]
fn test_package_resolver_relative() {
let resolver = PackageResolver::new()
.with_config_group("db")
.with_package_override("_group_.connection");
assert_eq!(resolver.resolve(), "db.connection");
}
#[test]
fn test_parse_package_header() {
let content = "# @package _global_\ndb:\n host: localhost";
assert_eq!(parse_package_header(content), Some("_global_".to_string()));
}
#[test]
fn test_parse_package_header_none() {
let content = "db:\n host: localhost";
assert_eq!(parse_package_header(content), None);
}
#[test]
fn test_compute_target_path() {
assert_eq!(compute_target_path("db", "host"), "db.host");
assert_eq!(compute_target_path("", "host"), "host");
assert_eq!(compute_target_path("db", ""), "db");
}
#[test]
fn test_split_path() {
assert_eq!(split_path("db.host.port"), vec!["db", "host", "port"]);
assert_eq!(split_path(""), Vec::<String>::new());
}
}