use proc_macro::TokenStream;
use quote::quote;
use std::path::PathBuf;
use syn::{parse_macro_input, LitStr};
#[proc_macro]
pub fn validate_template(input: TokenStream) -> TokenStream {
let path_lit = parse_macro_input!(input as LitStr);
let path_str = path_lit.value();
let manifest_dir = match std::env::var("CARGO_MANIFEST_DIR") {
Ok(dir) => PathBuf::from(dir),
Err(_) => {
return syn::Error::new_spanned(
&path_lit,
"CARGO_MANIFEST_DIR not set; cannot resolve template path",
)
.to_compile_error()
.into();
}
};
let full_path = manifest_dir.join(&path_str);
let content = match std::fs::read_to_string(&full_path) {
Ok(c) => c,
Err(e) => {
let msg = format!(
"Failed to read template file '{}': {}",
full_path.display(),
e
);
return syn::Error::new_spanned(&path_lit, msg)
.to_compile_error()
.into();
}
};
if let Err(e) = textfsm_core::Template::parse_str(&content) {
let msg = format!(
"Template validation failed for '{}':\n {}",
path_str, e
);
return syn::Error::new_spanned(&path_lit, msg)
.to_compile_error()
.into();
}
TokenStream::from(quote! {})
}
#[proc_macro]
pub fn validate_templates(input: TokenStream) -> TokenStream {
let path_lit = parse_macro_input!(input as LitStr);
let path_str = path_lit.value();
let manifest_dir = match std::env::var("CARGO_MANIFEST_DIR") {
Ok(dir) => PathBuf::from(dir),
Err(_) => {
return syn::Error::new_spanned(
&path_lit,
"CARGO_MANIFEST_DIR not set; cannot resolve template path",
)
.to_compile_error()
.into();
}
};
let full_path = manifest_dir.join(&path_str);
if !full_path.is_dir() {
let msg = format!(
"Template directory not found: '{}'",
full_path.display()
);
return syn::Error::new_spanned(&path_lit, msg)
.to_compile_error()
.into();
}
let pattern = full_path.join("**/*.textfsm");
let pattern_str = pattern.to_string_lossy();
let entries = match glob::glob(&pattern_str) {
Ok(paths) => paths,
Err(e) => {
let msg = format!("Invalid glob pattern: {}", e);
return syn::Error::new_spanned(&path_lit, msg)
.to_compile_error()
.into();
}
};
let mut errors = Vec::new();
let mut count = 0;
for entry in entries {
let file_path = match entry {
Ok(p) => p,
Err(e) => {
errors.push(format!("Glob error: {}", e));
continue;
}
};
count += 1;
let content = match std::fs::read_to_string(&file_path) {
Ok(c) => c,
Err(e) => {
errors.push(format!(
"Failed to read '{}': {}",
file_path.display(),
e
));
continue;
}
};
if let Err(e) = textfsm_core::Template::parse_str(&content) {
let relative = file_path
.strip_prefix(&manifest_dir)
.unwrap_or(&file_path);
errors.push(format!(
"Validation failed for '{}':\n {}",
relative.display(),
e
));
}
}
if !errors.is_empty() {
let msg = format!(
"Template validation errors:\n\n{}",
errors.join("\n\n")
);
return syn::Error::new_spanned(&path_lit, msg)
.to_compile_error()
.into();
}
if count == 0 {
let msg = format!(
"No .textfsm files found in '{}'",
full_path.display()
);
return syn::Error::new_spanned(&path_lit, msg)
.to_compile_error()
.into();
}
TokenStream::from(quote! {})
}
#[proc_macro]
pub fn validate_index(input: TokenStream) -> TokenStream {
let path_lit = parse_macro_input!(input as LitStr);
let path_str = path_lit.value();
let manifest_dir = match std::env::var("CARGO_MANIFEST_DIR") {
Ok(dir) => PathBuf::from(dir),
Err(_) => {
return syn::Error::new_spanned(
&path_lit,
"CARGO_MANIFEST_DIR not set; cannot resolve index path",
)
.to_compile_error()
.into();
}
};
let full_path = manifest_dir.join(&path_str);
let content = match std::fs::read_to_string(&full_path) {
Ok(c) => c,
Err(e) => {
let msg = format!(
"Failed to read index file '{}': {}",
full_path.display(),
e
);
return syn::Error::new_spanned(&path_lit, msg)
.to_compile_error()
.into();
}
};
let index = match textfsm_core::Index::parse_str(&content) {
Ok(idx) => idx,
Err(e) => {
let msg = format!(
"Index validation failed for '{}':\n {}",
path_str, e
);
return syn::Error::new_spanned(&path_lit, msg)
.to_compile_error()
.into();
}
};
let template_dir = full_path.parent().unwrap_or(&manifest_dir);
let mut template_lines: std::collections::HashMap<String, usize> =
std::collections::HashMap::new();
for entry in index.entries() {
for template in entry.templates() {
template_lines
.entry(template.to_string())
.or_insert(entry.line_num());
}
}
let mut errors = Vec::new();
for (template_name, line_num) in &template_lines {
let template_path = template_dir.join(template_name);
let template_content = match std::fs::read_to_string(&template_path) {
Ok(c) => c,
Err(e) => {
errors.push(format!(
"Template '{}' (index line {}): failed to read: {}",
template_name, line_num, e
));
continue;
}
};
if let Err(e) = textfsm_core::Template::parse_str(&template_content) {
errors.push(format!(
"Template '{}' (index line {}): {}",
template_name, line_num, e
));
}
}
if !errors.is_empty() {
let msg = format!(
"Index validation failed for '{}':\n\n{}",
path_str,
errors.join("\n\n")
);
return syn::Error::new_spanned(&path_lit, msg)
.to_compile_error()
.into();
}
TokenStream::from(quote! {})
}