use anyhow::Context;
use blockwatch::blocks;
use blockwatch::blocks::BlockSeverity;
use blockwatch::diff_parser;
use blockwatch::flags;
use blockwatch::language_parsers;
use blockwatch::validators;
use blockwatch::validators::Violation;
use clap::Parser;
use globset::GlobSet;
use std::collections::HashMap;
use std::io::{IsTerminal, Read, Write};
use std::path::PathBuf;
use std::sync::Arc;
use std::{env, fs, process};
fn main() -> anyhow::Result<()> {
let args = flags::Args::parse();
let languages = language_parsers::language_parsers()?;
let supported_extensions = languages.keys().collect();
args.validate(&supported_extensions)?;
let mut glob_set = args.globs()?;
let is_terminal =
std::io::stdin().is_terminal() || env::var("BLOCKWATCH_TERMINAL_MODE").is_ok();
if glob_set.is_empty() && is_terminal {
glob_set = GlobSet::new([globset::Glob::new("**")?])?
}
let should_scan_files = !glob_set.is_empty();
let path_checker = blocks::PathCheckerImpl::new(glob_set, args.ignored_globs()?);
let root_path = repository_root_path(fs::canonicalize(env::current_dir()?)?)?;
let file_system = blocks::FileSystemImpl::new(root_path);
let modified_lines_by_file = if !is_terminal {
let mut diff = String::new();
std::io::stdin().read_to_string(&mut diff)?;
diff_parser::line_changes_from_diff(diff.as_str())?
} else {
HashMap::new()
};
let blocks = blocks::parse_blocks(
modified_lines_by_file,
should_scan_files,
&file_system,
&path_checker,
languages,
args.extensions(),
)?;
let context = validators::ValidationContext::new(blocks);
if matches!(args.command, Some(flags::SubCommand::List { .. })) {
let report = context.to_serializable_report();
return serde_json::to_writer_pretty(std::io::stdout(), &report)
.context("Failed to list blocks");
}
let (sync_validators, async_validators) = validators::detect_validators(
&context,
validators::DETECTOR_FACTORIES,
&args.disabled_validators(),
&args.enabled_validators(),
)?;
let violations = validators::run(Arc::new(context), sync_validators, async_validators)?;
if !violations.is_empty() {
process_violations(violations)?;
}
Ok(())
}
fn process_violations(violations: HashMap<PathBuf, Vec<Violation>>) -> anyhow::Result<()> {
let mut has_error_severity = false;
let mut diagnostics: HashMap<PathBuf, Vec<serde_json::Value>> =
HashMap::with_capacity(violations.len());
for (file_path, file_violations) in violations {
let mut file_diagnostics = Vec::with_capacity(file_violations.len());
for violation in file_violations {
let diagnostic = violation.as_simple_diagnostic();
if diagnostic.severity() == BlockSeverity::Error {
has_error_severity = true;
}
file_diagnostics.push(serde_json::to_value(diagnostic)?);
}
diagnostics.insert(file_path, file_diagnostics);
}
let mut stderr = std::io::stderr().lock();
serde_json::to_writer_pretty(&mut stderr, &diagnostics)?;
writeln!(&mut stderr)?;
if has_error_severity {
process::exit(1);
}
Ok(())
}
fn repository_root_path(current_path: PathBuf) -> anyhow::Result<PathBuf> {
current_path
.ancestors()
.find(|path| path.join(".git").is_dir() || path.join(".hg").is_dir())
.map(|path| path.to_path_buf())
.ok_or_else(|| anyhow::anyhow!("Could not find the repository root directory"))
}