systemprompt_loader/
include_resolver.rs1use anyhow::{Context, Result};
2use serde::Deserialize;
3use std::fs;
4use std::path::{Path, PathBuf};
5
6#[derive(Debug, Clone)]
7pub struct IncludeResolver {
8 base_path: PathBuf,
9}
10
11impl IncludeResolver {
12 pub const fn new(base_path: PathBuf) -> Self {
13 Self { base_path }
14 }
15
16 pub fn resolve_string(&self, value: &str) -> Result<String> {
17 value.strip_prefix("!include ").map_or_else(
18 || Ok(value.to_string()),
19 |include_path| {
20 let full_path = self.base_path.join(include_path.trim());
21 fs::read_to_string(&full_path)
22 .with_context(|| format!("Failed to read include: {}", full_path.display()))
23 },
24 )
25 }
26
27 pub fn resolve_yaml_file<T: for<'de> Deserialize<'de>>(&self, path: &str) -> Result<T> {
28 let full_path = self.base_path.join(path);
29 let content = fs::read_to_string(&full_path)
30 .with_context(|| format!("Failed to read YAML: {}", full_path.display()))?;
31
32 serde_yaml::from_str(&content)
33 .with_context(|| format!("Failed to parse YAML: {}", full_path.display()))
34 }
35
36 pub fn with_subpath(&self, subpath: &str) -> Self {
37 Self {
38 base_path: self.base_path.join(subpath),
39 }
40 }
41
42 pub fn base_path(&self) -> &Path {
43 &self.base_path
44 }
45
46 pub fn exists(&self, path: &str) -> bool {
47 self.base_path.join(path).exists()
48 }
49
50 pub fn read_file(&self, path: &str) -> Result<String> {
51 let full_path = self.base_path.join(path);
52 fs::read_to_string(&full_path)
53 .with_context(|| format!("Failed to read file: {}", full_path.display()))
54 }
55}