use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result, bail};
use clap::{Parser, Subcommand, ValueEnum};
use projd_core::{ProjectScan, render_json, render_markdown, scan_path};
#[derive(Debug, Parser)]
#[command(name = "projd")]
#[command(version, about = projd_core::describe())]
struct Cli {
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Debug, Subcommand)]
enum Command {
Scan {
path: PathBuf,
#[arg(short, long)]
format: Option<OutputFormat>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
overwrite: bool,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum OutputFormat {
Markdown,
Json,
}
impl OutputFormat {
fn detect_path(path: &Path) -> Option<Self> {
let extension = path.extension()?.to_str()?.to_ascii_lowercase();
match extension.as_str() {
"md" | "markdown" => Some(Self::Markdown),
"json" => Some(Self::Json),
_ => None,
}
}
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Some(Command::Scan {
path,
format,
output,
overwrite,
}) => {
let format = choose_output_format(format, output.as_deref());
let scan = scan_path(path)?;
let rendered = render_scan(&scan, format)?;
write_or_print(rendered, output, overwrite)
}
None => {
println!("{} {}", projd_core::NAME, projd_core::VERSION);
println!("{}", projd_core::describe());
Ok(())
}
}
}
fn choose_output_format(format: Option<OutputFormat>, output: Option<&Path>) -> OutputFormat {
format
.or_else(|| output.and_then(OutputFormat::detect_path))
.unwrap_or(OutputFormat::Markdown)
}
fn render_scan(scan: &ProjectScan, format: OutputFormat) -> Result<String> {
match format {
OutputFormat::Markdown => Ok(render_markdown(scan)),
OutputFormat::Json => render_json(scan).map(|json| format!("{json}\n")),
}
}
fn write_or_print(rendered: String, output: Option<PathBuf>, overwrite: bool) -> Result<()> {
let Some(output) = output else {
print!("{rendered}");
return Ok(());
};
if output.exists() && !overwrite {
bail!(
"refusing to overwrite existing output file `{}`",
output.display()
);
}
fs::write(&output, rendered)
.with_context(|| format!("failed to write output file `{}`", output.display()))?;
Ok(())
}