use clap::{Arg, Command};
use mdcheck::{check_files, CheckConfig, CheckResult, OutputFormat};
use std::path::PathBuf;
use std::process;
fn main() {
let matches = Command::new("mdcheck")
.version("0.1.0")
.about("Linter/Validator for Markdown files that enforces CommonMark specification")
.arg(
Arg::new("files")
.help("Markdown files or directories to check")
.required(true)
.num_args(1..),
)
.arg(
Arg::new("recursive")
.short('r')
.long("recursive")
.help("Recursively search directories for markdown files")
.action(clap::ArgAction::SetTrue),
)
.arg(
Arg::new("output-format")
.short('o')
.long("output-format")
.help("Output format")
.value_parser(["human", "json"])
.default_value("human"),
)
.arg(
Arg::new("strict")
.short('s')
.long("strict")
.help("Enable strict mode (treat warnings as errors)")
.action(clap::ArgAction::SetTrue),
)
.arg(
Arg::new("ignore-warnings")
.short('w')
.long("ignore-warnings")
.help("Ignore warnings, only show errors")
.action(clap::ArgAction::SetTrue),
)
.get_matches();
let files: Vec<String> = matches
.get_many::<String>("files")
.unwrap_or_default()
.cloned()
.collect();
let recursive = matches.get_flag("recursive");
let output_format = match matches.get_one::<String>("output-format").unwrap().as_str() {
"json" => OutputFormat::Json,
_ => OutputFormat::Human,
};
let strict = matches.get_flag("strict");
let ignore_warnings = matches.get_flag("ignore-warnings");
let config = CheckConfig {
recursive,
output_format: output_format.clone(),
strict,
ignore_warnings,
};
let all_paths: Vec<PathBuf> = files.iter().map(PathBuf::from).collect();
let results = check_files(&all_paths, &config);
if matches!(output_format, OutputFormat::Human) {
print_human_results(&results, &config);
}
let has_errors = results.iter().any(|result| !result.errors.is_empty());
let has_warnings = results.iter().any(|result| !result.warnings.is_empty());
let should_fail = has_errors || (strict && has_warnings);
if should_fail {
process::exit(1);
} else {
process::exit(0);
}
}
fn print_human_results(results: &[CheckResult], config: &CheckConfig) {
use colored::*;
let mut total_errors = 0;
let mut total_warnings = 0;
let mut files_with_issues = 0;
for result in results {
if result.errors.is_empty() && result.warnings.is_empty() {
if !config.ignore_warnings {
println!("{} {}", "✓".green(), result.file_path.display());
}
continue;
}
files_with_issues += 1;
total_errors += result.errors.len();
total_warnings += result.warnings.len();
println!("\n{}:", result.file_path.display());
for error in &result.errors {
println!(" {} {}: {}", "error".red(), error.line, error.message);
if let Some(context) = &error.context {
println!(" {}", context.dimmed());
}
}
if !config.ignore_warnings {
for warning in &result.warnings {
println!(
" {} {}: {}",
"warning".yellow(),
warning.line,
warning.message
);
if let Some(context) = &warning.context {
println!(" {}", context.dimmed());
}
}
}
}
if files_with_issues > 0 {
println!("\nSummary:");
if total_errors > 0 {
print!(
"{} {}",
total_errors.to_string().red(),
if total_errors == 1 { "error" } else { "errors" }
);
}
if total_warnings > 0 && !config.ignore_warnings {
if total_errors > 0 {
print!(", ");
}
print!(
"{} {}",
total_warnings.to_string().yellow(),
if total_warnings == 1 {
"warning"
} else {
"warnings"
}
);
}
println!(
" in {} file{}",
files_with_issues,
if files_with_issues == 1 { "" } else { "s" }
);
} else if !config.ignore_warnings || total_errors == 0 {
println!("\n{} All markdown files are valid!", "✓".green());
}
}