use crate::errors::{CliError, Result};
use crate::output::{derive_output_path, OutputWriter};
use clap::Parser;
use quillmark::{Document, Quillmark};
use quillmark_core::OutputFormat;
use std::fs;
use std::path::PathBuf;
#[derive(Parser)]
pub struct RenderArgs {
#[arg(value_name = "QUILL_PATH")]
quill: PathBuf,
#[arg(value_name = "MARKDOWN_FILE")]
markdown_file: Option<PathBuf>,
#[arg(short, long, value_name = "FILE")]
output: Option<PathBuf>,
#[arg(short, long, value_name = "FORMAT", default_value = "pdf")]
format: String,
#[arg(long)]
stdout: bool,
#[arg(short, long)]
verbose: bool,
#[arg(long)]
quiet: bool,
#[arg(long, value_name = "DATA_FILE")]
output_data: Option<PathBuf>,
}
pub fn execute(args: RenderArgs) -> Result<()> {
if !args.quill.exists() {
return Err(CliError::InvalidArgument(format!(
"Quill directory not found: {}",
args.quill.display()
)));
}
if args.verbose {
println!("Loading quill from: {}", args.quill.display());
}
let engine = Quillmark::new();
let quill = engine.quill_from_path(args.quill.clone())?;
if args.verbose {
println!("Quill loaded: {}", quill.source().name);
}
let (parse_output, markdown_path_for_output) =
if let Some(ref markdown_path) = args.markdown_file {
if !markdown_path.exists() {
return Err(CliError::InvalidArgument(format!(
"Markdown file not found: {}",
markdown_path.display()
)));
}
if args.verbose {
println!("Reading markdown from: {}", markdown_path.display());
}
let markdown = fs::read_to_string(markdown_path)?;
let output = Document::from_markdown_with_warnings(&markdown)?;
if args.verbose {
println!("Markdown parsed successfully");
}
(output, Some(markdown_path.clone()))
} else {
let markdown = quill.source().example.clone().ok_or_else(|| {
CliError::InvalidArgument(format!(
"Quill '{}' does not have example content",
quill.source().name
))
})?;
if args.verbose {
println!("Using example content from quill");
}
let output = Document::from_markdown_with_warnings(&markdown)?;
if args.verbose {
println!("Example markdown parsed successfully");
}
(output, None)
};
let (parsed, parse_warnings) = (parse_output.document, parse_output.warnings);
if args.verbose {
println!("Render-ready quill for backend: {}", quill.backend_id());
}
let output_format = match args.format.to_lowercase().as_str() {
"pdf" => OutputFormat::Pdf,
"svg" => OutputFormat::Svg,
"png" => OutputFormat::Png,
"txt" => OutputFormat::Txt,
_ => {
return Err(CliError::InvalidArgument(format!(
"Invalid output format: {}. Must be one of: pdf, svg, png, txt",
args.format
)));
}
};
if args.verbose {
println!("Rendering to format: {:?}", output_format);
}
if let Some(data_path) = args.output_data {
let json_data = quill
.compile_data(&parsed)
.map_err(|e| CliError::Render(e))?;
let f = std::fs::File::create(&data_path).map_err(|e| {
CliError::Io(std::io::Error::new(
e.kind(),
format!(
"Failed to create data output file '{}': {}",
data_path.display(),
e
),
))
})?;
serde_json::to_writer_pretty(f, &json_data).map_err(|e| {
CliError::Io(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Failed to write JSON data: {}", e),
))
})?;
if args.verbose && !args.quiet {
println!("JSON data written to: {}", data_path.display());
}
}
let mut result = quill.render(&parsed, Some(output_format))?;
result.warnings.splice(0..0, parse_warnings);
if !result.warnings.is_empty() && !args.quiet {
crate::errors::print_warnings(&result.warnings);
}
let artifact = result.artifacts.first().ok_or_else(|| {
CliError::InvalidArgument("No artifacts produced from rendering".to_string())
})?;
let output_path = if args.stdout {
None
} else {
Some(args.output.unwrap_or_else(|| {
if let Some(ref path) = markdown_path_for_output {
derive_output_path(path, &args.format)
} else {
PathBuf::from(format!("example.{}", args.format))
}
}))
};
let writer = OutputWriter::new(args.stdout, output_path, args.quiet);
writer.write(&artifact.bytes)?;
if args.verbose && !args.quiet {
println!("Rendering completed successfully");
}
Ok(())
}