use std::path::{Path, PathBuf};
use crate::cli::LibraryArgs;
pub fn run(
file: &Path,
query_str: &str,
format: &str,
count: bool,
recursive: bool,
lib_args: &LibraryArgs,
) -> Result<(), String> {
if recursive && file.is_dir() {
let files = collect_wcl_files(file)?;
if files.is_empty() {
return Err(format!("no .wcl files found in {}", file.display()));
}
let mut all_results: Vec<crate::Value> = Vec::new();
for wcl_file in &files {
let source = std::fs::read_to_string(wcl_file)
.map_err(|e| format!("cannot read {}: {}", wcl_file.display(), e))?;
let mut options = crate::ParseOptions {
root_dir: wcl_file.parent().unwrap_or(Path::new(".")).to_path_buf(),
..Default::default()
};
lib_args.apply(&mut options);
let doc = crate::parse(&source, options);
if doc.has_errors() {
for diag in doc.errors() {
eprintln!(
"{}",
super::format_diagnostic(diag, &doc.source_map, wcl_file)
);
}
continue;
}
match doc.query(query_str) {
Ok(crate::Value::List(items)) => {
all_results.extend(items);
}
Ok(val) => {
all_results.push(val);
}
Err(e) => {
eprintln!("query error [{}]: {}", wcl_file.display(), e);
}
}
}
let aggregated = crate::Value::List(all_results);
if count {
match &aggregated {
crate::Value::List(items) => println!("{}", items.len()),
_ => println!("1"),
}
} else {
match format {
"json" => {
let json = value_to_json(&aggregated);
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
}
"csv" => {
print_csv(&aggregated);
}
"wcl" => {
print_wcl(&aggregated, 0);
}
_ => {
println!("{}", aggregated);
}
}
}
return Ok(());
}
let source = std::fs::read_to_string(file)
.map_err(|e| format!("cannot read {}: {}", file.display(), e))?;
let mut options = crate::ParseOptions {
root_dir: file.parent().unwrap_or(Path::new(".")).to_path_buf(),
..Default::default()
};
lib_args.apply(&mut options);
let doc = crate::parse(&source, options);
if doc.has_errors() {
for diag in doc.errors() {
eprintln!("{}", super::format_diagnostic(diag, &doc.source_map, file));
}
return Err("document has errors".to_string());
}
let result = doc.query(query_str)?;
if count {
match &result {
crate::Value::List(items) => println!("{}", items.len()),
_ => println!("1"),
}
} else {
match format {
"json" => {
let json = value_to_json(&result);
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
}
"csv" => {
print_csv(&result);
}
"wcl" => {
print_wcl(&result, 0);
}
_ => {
println!("{}", result);
}
}
}
Ok(())
}
fn collect_wcl_files(dir: &Path) -> Result<Vec<PathBuf>, String> {
let mut files = Vec::new();
collect_wcl_files_recursive(dir, &mut files)?;
files.sort();
Ok(files)
}
fn collect_wcl_files_recursive(dir: &Path, files: &mut Vec<PathBuf>) -> Result<(), String> {
let entries = std::fs::read_dir(dir)
.map_err(|e| format!("cannot read directory {}: {}", dir.display(), e))?;
for entry in entries {
let entry = entry.map_err(|e| format!("directory entry error: {}", e))?;
let path = entry.path();
if path.is_dir() {
collect_wcl_files_recursive(&path, files)?;
} else if path.extension().is_some_and(|ext| ext == "wcl") {
files.push(path);
}
}
Ok(())
}
fn value_to_json(val: &crate::Value) -> serde_json::Value {
match val {
crate::Value::String(s) => serde_json::Value::String(s.clone()),
crate::Value::Int(i) => serde_json::json!(i),
crate::Value::Float(f) => serde_json::json!(f),
crate::Value::Bool(b) => serde_json::Value::Bool(*b),
crate::Value::Null => serde_json::Value::Null,
crate::Value::Identifier(s) => serde_json::Value::String(s.clone()),
crate::Value::Symbol(s) => serde_json::Value::String(s.clone()),
crate::Value::List(items) => {
serde_json::Value::Array(items.iter().map(value_to_json).collect())
}
crate::Value::Map(m) => {
let obj: serde_json::Map<String, serde_json::Value> = m
.iter()
.map(|(k, v)| (k.clone(), value_to_json(v)))
.collect();
serde_json::Value::Object(obj)
}
crate::Value::BlockRef(br) => blockref_to_json(br),
_ => serde_json::Value::String(format!("{}", val)),
}
}
fn blockref_to_json(br: &crate::BlockRef) -> serde_json::Value {
let mut obj = serde_json::Map::new();
for (k, v) in &br.attributes {
obj.insert(k.clone(), value_to_json(v));
}
for child in &br.children {
let key = child.id.as_deref().unwrap_or(&child.kind);
obj.insert(key.to_string(), blockref_to_json(child));
}
serde_json::Value::Object(obj)
}
fn print_csv(val: &crate::Value) {
match val {
crate::Value::List(items) => {
let mut headers: Vec<String> = Vec::new();
let rows: Vec<_> = items
.iter()
.filter_map(|item| match item {
crate::Value::Map(m) => Some(m),
crate::Value::BlockRef(br) => Some(&br.attributes),
_ => None,
})
.collect();
for row in &rows {
for key in row.keys() {
if !headers.contains(key) {
headers.push(key.clone());
}
}
}
if headers.is_empty() {
for item in items {
println!("{}", item);
}
} else {
println!("{}", headers.join(","));
for row in &rows {
let cells: Vec<String> = headers
.iter()
.map(|h| {
row.get(h)
.map(|v| csv_escape(&format!("{}", v)))
.unwrap_or_default()
})
.collect();
println!("{}", cells.join(","));
}
}
}
_ => println!("{}", val),
}
}
fn csv_escape(s: &str) -> String {
if s.contains(',') || s.contains('"') || s.contains('\n') {
format!("\"{}\"", s.replace('"', "\"\""))
} else {
s.to_string()
}
}
fn print_wcl(val: &crate::Value, indent: usize) {
let pad = " ".repeat(indent);
match val {
crate::Value::List(items) => {
for item in items {
print_wcl(item, indent);
}
}
crate::Value::BlockRef(br) => {
print!("{}{}", pad, br.kind);
if let Some(id) = &br.id {
print!(" {}", id);
}
println!(" {{");
for (key, value) in &br.attributes {
print!("{} {} = ", pad, key);
print_wcl_value(value);
println!();
}
for child in &br.children {
print_wcl(&crate::Value::BlockRef(child.clone()), indent + 1);
}
println!("{}}}", pad);
}
crate::Value::Map(m) => {
for (key, value) in m {
print!("{}{} = ", pad, key);
print_wcl_value(value);
println!();
}
}
other => {
print_wcl_value(other);
println!();
}
}
}
fn print_wcl_value(val: &crate::Value) {
match val {
crate::Value::String(s) => print!("\"{}\"", s),
crate::Value::Int(n) => print!("{}", n),
crate::Value::Float(f) => print!("{}", f),
crate::Value::Bool(b) => print!("{}", b),
crate::Value::Null => print!("null"),
crate::Value::List(items) => {
print!("[");
for (i, item) in items.iter().enumerate() {
if i > 0 {
print!(", ");
}
print_wcl_value(item);
}
print!("]");
}
other => print!("{}", other),
}
}