#[path = "support.rs"]
mod support;
use noyalib::{from_str, Value};
use std::collections::HashSet;
use std::fs;
use std::path::{Path, PathBuf};
const ROOT_YAML: &str = "\
name: noyalib-stack
services:
api:
$include: partials/api.yaml
worker:
$include: partials/worker.yaml
metadata:
owner: platform-team
";
const API_YAML: &str = "\
image: registry.example.com/api:1.0
port: 8080
replicas: 3
";
const WORKER_YAML: &str = "\
image: registry.example.com/worker:1.0
queue: jobs
concurrency: 8
";
fn main() -> noyalib::Result<()> {
support::header("$include — modular YAML configuration");
let tmp = std::env::temp_dir().join(format!("noyalib-include-example-{}", std::process::id()));
let partials = tmp.join("partials");
fs::create_dir_all(&partials).map_err(io_err)?;
fs::write(partials.join("api.yaml"), API_YAML).map_err(io_err)?;
fs::write(partials.join("worker.yaml"), WORKER_YAML).map_err(io_err)?;
let mut value: Value = from_str(ROOT_YAML)?;
let mut visiting = HashSet::<PathBuf>::new();
resolve_includes(&mut value, &tmp, &mut visiting)?;
println!(" Root document after $include resolution:\n");
let formatted = noyalib::to_string(&value)?;
for line in formatted.lines() {
println!(" {line}");
}
let _ = fs::remove_dir_all(&tmp);
println!();
println!(" Each `{{ $include: <path> }}` placeholder was replaced by");
println!(" the parsed contents of the referenced file — single");
println!(" document, multiple human-friendly source files.");
support::footer();
Ok(())
}
fn resolve_includes(
value: &mut Value,
base: &Path,
visiting: &mut HashSet<PathBuf>,
) -> noyalib::Result<()> {
if let Some(rel) = include_target(value) {
let path = base.join(&rel);
let canonical = path.canonicalize().unwrap_or_else(|_| path.clone());
if !visiting.insert(canonical.clone()) {
return Err(noyalib::Error::Custom(format!(
"include cycle detected: {}",
canonical.display()
)));
}
let content = fs::read_to_string(&path).map_err(io_err)?;
let mut nested: Value = from_str(&content)?;
let nested_base = path.parent().unwrap_or(base).to_path_buf();
resolve_includes(&mut nested, &nested_base, visiting)?;
let _ = visiting.remove(&canonical);
*value = nested;
return Ok(());
}
match value {
Value::Sequence(seq) => {
for item in seq.iter_mut() {
resolve_includes(item, base, visiting)?;
}
}
Value::Mapping(map) => {
for (_, v) in map.iter_mut() {
resolve_includes(v, base, visiting)?;
}
}
_ => {}
}
Ok(())
}
fn include_target(value: &Value) -> Option<String> {
let map = value.as_mapping()?;
if map.len() != 1 {
return None;
}
let (k, v) = map.iter().next()?;
if k != "$include" {
return None;
}
Some(v.as_str()?.to_owned())
}
fn io_err(e: std::io::Error) -> noyalib::Error {
noyalib::Error::Custom(format!("I/O error: {e}"))
}