notabene 0.2.0

A fast linter for changelogs in Keep a Changelog format
Documentation
use std::collections::{HashMap, HashSet};
use std::io::{self, Write};
use std::path::PathBuf;

use clap::ArgMatches;

use crate::rule::Rule;
use crate::ruleset::RuleSet;
use crate::span::Locator;
use crate::{Linter, parse};

use super::config::{Config, Lint};
use super::error::{Error, Result};
use super::report::{Format, report};

pub fn lint(matches: &ArgMatches) -> Result<()> {
    let mut config = Config::load(None).unwrap();
    if let Some(path) = matches.get_one::<PathBuf>("config_file") {
        config = config.merge(&Config::from_file(path).unwrap());
    };
    let path = matches
        .get_one::<PathBuf>("FILE")
        .unwrap_or(&PathBuf::from("CHANGELOG.md"))
        .clone();
    let select: Option<HashSet<Rule>> = matches
        .get_many::<Rule>("select")
        .map(|values| values.copied().collect());
    let ignore: Option<HashSet<Rule>> = matches
        .get_many::<Rule>("ignore")
        .map(|values| values.copied().collect());
    let output_format = matches.get_one::<Format>("output_format").copied();
    let cli_config = Config {
        lint: Lint {
            select,
            ignore,
            output_format,
        },
    };
    config = config.merge(&cli_config);
    let rules: HashSet<Rule> = config
        .lint
        .select
        .unwrap()
        .difference(&config.lint.ignore.unwrap())
        .copied()
        .collect();
    let ruleset = RuleSet::new(rules);
    let content = std::fs::read_to_string(&path)?;
    let ir = parse(&content);
    let linter = Linter::new(&ruleset).with_filename(Some(&path));
    let diagnostics = linter.lint(&ir);
    if diagnostics.is_empty() {
        Ok(())
    } else {
        let locator = Locator::new(&content);
        let mut diagnostics: Vec<_> = diagnostics
            .into_iter()
            .map(|d| d.locate(&locator))
            .collect();
        let mut output = io::stdout();
        diagnostics.sort_by_key(|d| d.location);
        report(
            &mut output,
            diagnostics.as_slice(),
            &content,
            Some(&path),
            &locator,
            // TODO: Build final config.
            config.lint.output_format.unwrap(),
        )?;
        Err(Error::Check)
    }
}

pub fn rule(matches: &ArgMatches) -> Result<()> {
    let mut rules_by_code = HashMap::new();
    for rule in Rule::ALL {
        rules_by_code.insert(rule.code().to_string(), rule);
    }
    let mut output = io::stdout();
    if matches.get_flag("all") {
        for (i, rule) in Rule::ALL.iter().enumerate() {
            if i > 0 {
                writeln!(output)?;
            }
            write!(
                output,
                "# {}

{}
",
                rule.code(),
                rule.doc()
            )?;
        }
    } else if let Some(rule) = matches.get_one::<Rule>("RULE") {
        write!(
            output,
            "# {}

{}
",
            rule.code(),
            rule.doc()
        )?
    } else {
        unreachable!()
    }
    Ok(())
}