use crate::error::ResolveError;
use crate::value::HoconValue;
use std::fs;
use super::structure_builder::StructureBuilder;
use super::types::{IncludeKey, InternalResolveOptions, ResObj, ResolverValue};
use super::utils::deep_merge_res_obj_into;
pub(crate) fn load_include(
include_path: &str,
required: bool,
is_file: bool,
line: usize,
col: usize,
opts: &InternalResolveOptions,
_path_prefix: &[String],
) -> Result<ResObj, ResolveError> {
let base = if is_file {
std::env::current_dir().unwrap_or_default()
} else {
match &opts.base_dir {
Some(dir) => dir.clone(),
None => std::env::current_dir().unwrap_or_default(),
}
};
let abs_path = base.join(include_path);
let has_extension = abs_path.extension().is_some();
if has_extension {
return match load_single_include(&abs_path, opts) {
Ok(obj) => Ok(obj),
Err(_) if !abs_path.exists() => {
if required {
return Err(ResolveError {
message: format!("required include file not found: {}", abs_path.display()),
path: abs_path.display().to_string(),
line,
col,
});
}
Ok(ResObj::new())
}
Err(e) => Err(e),
};
}
let extensions = ["properties", "json", "conf"];
let mut merged = ResObj::new();
let mut found_any = false;
for ext in &extensions {
let candidate = abs_path.with_extension(ext);
match load_single_include(&candidate, opts) {
Ok(obj) => {
found_any = true;
deep_merge_res_obj_into(&mut merged, obj, &[]);
}
Err(e) => {
if candidate.exists() {
return Err(e);
}
}
}
}
if found_any {
Ok(merged)
} else if required {
Err(ResolveError {
message: format!("required include file not found: {}", abs_path.display()),
path: abs_path.display().to_string(),
line,
col,
})
} else {
Ok(ResObj::new())
}
}
fn load_single_include(
candidate: &std::path::Path,
opts: &InternalResolveOptions,
) -> Result<ResObj, ResolveError> {
let candidate_key = IncludeKey::Path(candidate.to_path_buf());
if opts.include_stack.contains(&candidate_key) {
return Err(ResolveError {
message: format!("circular include: {}", candidate.display()),
path: candidate.display().to_string(),
line: 0,
col: 0,
});
}
let content = fs::read_to_string(candidate).map_err(|e| ResolveError {
message: format!("failed to read {}: {}", candidate.display(), e),
path: candidate.display().to_string(),
line: 0,
col: 0,
})?;
if candidate.extension().and_then(|e| e.to_str()) == Some("properties") {
let hv = crate::properties::properties_to_hocon(&content);
if let HoconValue::Object(fields) = hv {
let mut obj = ResObj::new();
for (k, v) in fields {
obj.fields.insert(k, ResolverValue::Resolved(v));
}
return Ok(obj);
}
return Ok(ResObj::new());
}
let tokens = crate::lexer::tokenize(&content).map_err(|e| ResolveError {
message: e.message,
path: candidate.display().to_string(),
line: e.line,
col: e.col,
})?;
let has_content = tokens.iter().any(|t| {
!matches!(
t.kind,
crate::lexer::TokenKind::Newline | crate::lexer::TokenKind::Eof
)
});
if !has_content {
return Ok(ResObj::new());
}
let ast = crate::parser::parse_tokens(&tokens).map_err(|e| ResolveError {
message: e.message,
path: candidate.display().to_string(),
line: e.line,
col: e.col,
})?;
let mut child_opts = InternalResolveOptions::new(opts.env.clone());
if let Some(parent) = candidate.parent() {
child_opts = child_opts.with_base_dir(parent.to_path_buf());
}
child_opts.include_stack = opts.include_stack.clone();
child_opts.include_stack.push(candidate_key);
#[cfg(feature = "include-package")]
{
child_opts.package_registry = opts.package_registry.clone();
}
StructureBuilder::new(&child_opts).build(ast, &[])
}
#[cfg(feature = "include-package")]
pub(crate) fn load_package_include(
identifier: &str,
file: &str,
required: bool,
line: usize,
col: usize,
opts: &InternalResolveOptions,
) -> Result<ResObj, ResolveError> {
let key = IncludeKey::Package {
identifier: identifier.to_string(),
file: file.to_string(),
};
if opts.include_stack.contains(&key) {
return Err(ResolveError {
message: format!("circular package include: ({:?}, {:?})", identifier, file),
path: format!("package({:?}, {:?})", identifier, file),
line,
col,
});
}
let content: &str = match opts
.package_registry
.get(&(identifier.to_string(), file.to_string()))
{
Some(c) => c.as_str(),
None => {
let _ = required; return Err(ResolveError {
message: format!(
"include package not found: ({:?}, {:?}) — was Parser::register_package called?",
identifier, file
),
path: format!("package({:?}, {:?})", identifier, file),
line,
col,
});
}
};
let tokens = crate::lexer::tokenize(content).map_err(|e| ResolveError {
message: e.message,
path: format!("package({:?}, {:?})", identifier, file),
line: e.line,
col: e.col,
})?;
let has_content = tokens.iter().any(|t| {
!matches!(
t.kind,
crate::lexer::TokenKind::Newline | crate::lexer::TokenKind::Eof
)
});
if !has_content {
return Ok(ResObj::new());
}
let ast = crate::parser::parse_tokens(&tokens).map_err(|e| ResolveError {
message: e.message,
path: format!("package({:?}, {:?})", identifier, file),
line: e.line,
col: e.col,
})?;
let mut child_opts = InternalResolveOptions::new(opts.env.clone());
child_opts.package_registry = opts.package_registry.clone();
child_opts.include_stack = opts.include_stack.clone();
child_opts.include_stack.push(key);
StructureBuilder::new(&child_opts).build(ast, &[])
}