use std::io::{self, Write};
use std::path::PathBuf;
use clap::{Parser, Subcommand};
use crate::diagnostics::{inspect_contract, DiagnosticReport};
use crate::parser::parse_file;
#[derive(Debug, Parser)]
#[command(
name = "dtcs",
version,
about = "Validate DTCS transformation contracts"
)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Validate {
path: PathBuf,
#[arg(long)]
json: bool,
},
Inspect {
path: PathBuf,
#[arg(long)]
json: bool,
},
Diagnostics {
path: PathBuf,
#[arg(long)]
json: bool,
},
Version {
#[arg(long)]
json: bool,
},
}
pub fn run(cli: Cli) -> miette::Result<i32> {
match cli.command {
Command::Validate { path, json } => {
let result = parse_file(&path)?;
let report = result.validate();
render_report(&report, json, ReportMode::Validate)
.map_err(|e| miette::miette!("{e}"))?;
Ok(if report.is_valid() { 0 } else { 1 })
}
Command::Inspect { path, json } => {
let result = parse_file(&path)?;
let mut report = result.report;
if let Some(ref contract) = result.contract {
report.merge(crate::validate(contract));
}
if !report.is_valid() {
render_report(&report, json, ReportMode::Diagnostics)
.map_err(|e| miette::miette!("{e}"))?;
return Ok(1);
}
let Some(contract) = result.contract else {
render_report(&report, json, ReportMode::Diagnostics)
.map_err(|e| miette::miette!("{e}"))?;
return Ok(1);
};
if json {
let summary = InspectSummary::from_contract(&contract);
println!(
"{}",
serde_json::to_string_pretty(&summary).map_err(|e| miette::miette!("{e}"))?
);
} else {
print!("{}", inspect_contract(&contract));
}
Ok(0)
}
Command::Diagnostics { path, json } => {
let result = parse_file(&path)?;
let report = result.validate();
render_report(&report, json, ReportMode::Diagnostics)
.map_err(|e| miette::miette!("{e}"))?;
Ok(if report.is_valid() { 0 } else { 1 })
}
Command::Version { json } => {
if json {
println!(
"{}",
serde_json::json!({
"crateVersion": env!("CARGO_PKG_VERSION"),
"specVersion": crate::SPEC_VERSION,
})
);
} else {
println!("dtcs {}", env!("CARGO_PKG_VERSION"));
println!("spec {}", crate::SPEC_VERSION);
}
Ok(0)
}
}
}
#[derive(Debug)]
enum ReportMode {
Validate,
Diagnostics,
}
#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct InspectSummary {
id: String,
name: String,
version: String,
dtcs_version: String,
inputs: usize,
outputs: usize,
semantic_actions: usize,
rules: usize,
expressions: usize,
functions: usize,
}
impl InspectSummary {
fn from_contract(contract: &crate::TransformationContract) -> Self {
Self {
id: contract.id.clone(),
name: contract.name.clone(),
version: contract.version.clone(),
dtcs_version: contract.dtcs_version.clone(),
inputs: contract.inputs.len(),
outputs: contract.outputs.len(),
semantic_actions: contract.semantic_actions.len(),
rules: contract.rules.len(),
expressions: contract.expressions.len(),
functions: contract.functions.len(),
}
}
}
fn render_report(report: &DiagnosticReport, json: bool, mode: ReportMode) -> std::io::Result<()> {
let mut stdout = io::stdout().lock();
if json {
let payload = match mode {
ReportMode::Validate => serde_json::json!({
"valid": report.is_valid(),
"diagnostics": report.diagnostics,
}),
ReportMode::Diagnostics => serde_json::json!({
"diagnostics": report.diagnostics,
}),
};
writeln!(
stdout,
"{}",
serde_json::to_string_pretty(&payload)
.map_err(|e| std::io::Error::other(e.to_string()))?
)?;
return Ok(());
}
if report.diagnostics.is_empty() {
match mode {
ReportMode::Validate => writeln!(stdout, "valid")?,
ReportMode::Diagnostics => writeln!(stdout, "no diagnostics")?,
}
return Ok(());
}
for diagnostic in &report.diagnostics {
writeln!(
stdout,
"[{}] {} ({}) - {}",
format!("{:?}", diagnostic.severity).to_lowercase(),
diagnostic.id,
format!("{:?}", diagnostic.category).to_lowercase(),
diagnostic.message,
)?;
if let Some(object_ref) = &diagnostic.object_ref {
writeln!(stdout, " at: {object_ref}")?;
}
if let Some(remediation) = &diagnostic.remediation {
writeln!(stdout, " hint: {remediation}")?;
}
}
if matches!(mode, ReportMode::Validate) && report.is_valid() {
writeln!(stdout, "valid")?;
}
Ok(())
}