use std::path::Path;
use cuenv_core::Result;
use cuenv_core::manifest::DirectoryRules;
pub fn evaluate_rules_file(file_path: &Path) -> Result<DirectoryRules> {
let original = std::fs::read_to_string(file_path).map_err(|e| cuenv_core::Error::Io {
source: e,
path: Some(file_path.to_path_buf().into_boxed_path()),
operation: "read .rules.cue".to_string(),
})?;
let patched = prepare_isolated_cue(&original);
let tempdir = tempfile::tempdir().map_err(|e| cuenv_core::Error::Io {
source: e,
path: None,
operation: "create tempdir for .rules.cue eval".to_string(),
})?;
let cue_mod_dir = tempdir.path().join("cue.mod");
std::fs::create_dir_all(&cue_mod_dir).map_err(|e| cuenv_core::Error::Io {
source: e,
path: Some(cue_mod_dir.clone().into_boxed_path()),
operation: "create cue.mod dir".to_string(),
})?;
std::fs::write(
cue_mod_dir.join("module.cue"),
"module: \"cuenv.dev/rules-eval\"\nlanguage: version: \"v0.12.0\"\n",
)
.map_err(|e| cuenv_core::Error::Io {
source: e,
path: Some(cue_mod_dir.join("module.cue").into_boxed_path()),
operation: "write cue.mod/module.cue".to_string(),
})?;
let temp_path = tempdir.path().join("rules_eval.cue");
std::fs::write(&temp_path, patched).map_err(|e| cuenv_core::Error::Io {
source: e,
path: Some(temp_path.clone().into_boxed_path()),
operation: "write temp rules file".to_string(),
})?;
cuengine::evaluate_cue_package_typed(tempdir.path(), "rules").map_err(|e| {
cuenv_core::Error::configuration(format!(
"Failed to parse .rules.cue [isolated-eval] at {}: {}",
file_path.display(),
e
))
})
}
fn prepare_isolated_cue(source: &str) -> String {
let mut result = String::with_capacity(source.len());
result.push_str("package rules\n");
let mut in_import_block = false;
let mut skip_package = true;
for line in source.lines() {
let trimmed = line.trim();
if skip_package && trimmed.starts_with("package ") {
skip_package = false;
continue;
}
if trimmed.starts_with("import \"") {
continue;
}
if trimmed.starts_with("import (") {
in_import_block = true;
continue;
}
if in_import_block {
if trimmed == ")" {
in_import_block = false;
}
continue;
}
if trimmed.starts_with("rules.#") {
let without_prefix =
trimmed.trim_start_matches(|c: char| c.is_alphanumeric() || c == '.' || c == '#');
let remainder = without_prefix.trim();
if remainder.is_empty() || remainder == "&" {
continue;
}
}
if trimmed.starts_with("rules.#")
&& let Some(pos) = trimmed.find('&')
{
let after_amp = trimmed[pos + 1..].trim();
if !after_amp.is_empty() {
result.push_str(after_amp);
result.push('\n');
continue;
}
}
result.push_str(line);
result.push('\n');
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strips_import_and_constraint() {
let input = r#"package cuenv
import "github.com/cuenv/cuenv/schema/rules"
rules.#DirectoryRules & {
ignore: {
git: [".cache"]
}
}
"#;
let result = prepare_isolated_cue(input);
assert!(!result.contains("import"));
assert!(!result.contains("rules.#DirectoryRules"));
assert!(result.starts_with("package rules\n"));
assert!(result.contains("ignore:"));
assert!(result.contains('{'));
}
#[test]
fn strips_standalone_constraint() {
let input = r#"package examples
import "github.com/cuenv/cuenv/schema/rules"
rules.#DirectoryRules
ignore: {
git: ["node_modules/"]
}
"#;
let result = prepare_isolated_cue(input);
assert!(!result.contains("import"));
assert!(!result.contains("rules.#DirectoryRules"));
assert!(result.contains("ignore:"));
}
#[test]
fn handles_multi_line_import_block() {
let input = r#"package test
import (
"github.com/cuenv/cuenv/schema/rules"
"strings"
)
ignore: {
git: ["target/"]
}
"#;
let result = prepare_isolated_cue(input);
assert!(!result.contains("import"));
assert!(!result.contains("strings"));
assert!(result.contains("ignore:"));
}
}