use regex::Regex;
use super::pack::BundledLayout;
pub fn bundle_sections(layout: &BundledLayout, rendered_sections: Vec<(&str, String)>) -> String {
let strip_regexes: Vec<Regex> = layout
.strip_patterns
.iter()
.filter_map(|p| Regex::new(p).ok())
.collect();
let import_patterns: Vec<String> = layout.strip_import_patterns.clone();
let mut output = String::new();
output.push_str("// Auto-generated by oag — do not edit (bundled)\n\n");
for (label, content) in rendered_sections {
output.push_str(&format!("// === {label} ===\n\n"));
let mut stripped = content.clone();
for regex in &strip_regexes {
stripped = stripped
.lines()
.filter(|line| !regex.is_match(line))
.collect::<Vec<_>>()
.join("\n");
}
stripped = strip_relative_imports(&stripped, &import_patterns);
output.push_str(&stripped);
output.push('\n');
}
output
}
fn strip_relative_imports(content: &str, patterns: &[String]) -> String {
let lines: Vec<&str> = content.lines().collect();
let mut result = Vec::new();
let mut i = 0;
let is_match = |s: &str| -> bool { patterns.iter().any(|p| s.contains(p.as_str())) };
while i < lines.len() {
let trimmed = lines[i].trim();
if trimmed.starts_with("import ") || trimmed.starts_with("import type ") {
if trimmed.ends_with(';') {
if is_match(trimmed) {
i += 1;
continue;
}
result.push(lines[i]);
i += 1;
continue;
}
let start = i;
i += 1;
let mut found_close = false;
while i < lines.len() {
let t = lines[i].trim();
if t.starts_with("} from ") {
found_close = true;
if is_match(t) {
i += 1;
} else {
result.extend_from_slice(&lines[start..=i]);
i += 1;
}
break;
}
i += 1;
}
if !found_close {
result.extend_from_slice(&lines[start..i]);
}
continue;
}
result.push(lines[i]);
i += 1;
}
result.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strip_relative_imports() {
let content = r#"import type {
Pet,
NewPet,
} from "./types";
import { streamSse, type SSEOptions } from "./sse";
export class ApiClient {"#;
let patterns = vec!["\"./types\"".to_string(), "\"./sse\"".to_string()];
let stripped = strip_relative_imports(content, &patterns);
assert!(!stripped.contains("from \"./types\""));
assert!(!stripped.contains("from \"./sse\""));
assert!(stripped.contains("export class ApiClient {"));
}
#[test]
fn test_strip_auto_generated_header() {
let layout = super::BundledLayout {
output_path: "index.ts".to_string(),
sections: vec![],
strip_patterns: vec!["^// Auto-generated by oag.*$".to_string()],
strip_import_patterns: vec![],
};
let content = "// Auto-generated by oag — do not edit\nexport interface Foo {}".to_string();
let rendered = vec![("Types", content)];
let result = bundle_sections(&layout, rendered);
assert!(result.contains("export interface Foo {}"));
assert_eq!(
result.matches("Auto-generated").count(),
1,
"only the bundled header, not the section header"
);
}
}