mit_commit_message_lints/lints/cmd/
read_lint_config.rs

1use std::{
2    collections::{BTreeMap, BTreeSet},
3    convert::TryInto,
4};
5
6use miette::{IntoDiagnostic, Result, SourceSpan};
7use mit_lint::{Lint, Lints, CONFIG_KEY_PREFIX};
8
9use crate::{external::Vcs, lints::cmd::errors::SerialiseLintError};
10
11/// # Errors
12///
13/// If we fail to parse the toml
14///
15/// # Panics
16///
17/// Will panic if the lint prefix isn't delimited by dots. This should never
18/// happen as it's a constant
19pub fn read_from_toml_or_else_vcs(config: &str, vcs: &dyn Vcs) -> Result<Lints> {
20    let vcs_lints = try_from_vcs(vcs)?;
21    // contains PB  // contains lint // contains config
22    let config: BTreeMap<String, BTreeMap<String, BTreeMap<String, bool>>> = toml::from_str(config)
23        .map_err(|x| SerialiseLintError {
24            src: config.to_string(),
25            message: x.to_string(),
26            span: x
27                .span()
28                .map_or_else(|| SourceSpan::new(0.into(), 0), Into::into),
29        })?;
30
31    let lint_prefix = CONFIG_KEY_PREFIX.split('.').collect::<Vec<_>>();
32    let namespace = (*lint_prefix.first().unwrap()).to_string();
33
34    let Some(config) = config.get(&namespace) else {
35        return Ok(vcs_lints);
36    };
37
38    let group = (*lint_prefix.get(1).unwrap()).to_string();
39
40    let Some(lint_names) = config.get(&group) else {
41        return Ok(vcs_lints);
42    };
43
44    let to_add: Lints = lint_names
45        .iter()
46        .filter_map(|(key, value)| if *value { Some(key as &str) } else { None })
47        .collect::<Vec<&str>>()
48        .try_into()
49        .into_diagnostic()?;
50
51    let to_remove: Lints = lint_names
52        .iter()
53        .filter_map(|(key, value)| if *value { None } else { Some(key as &str) })
54        .collect::<Vec<&str>>()
55        .try_into()
56        .into_diagnostic()?;
57
58    Ok(vcs_lints.subtract(&to_remove).merge(&to_add))
59}
60
61/// Create lints from the VCS configuration
62///
63/// # Errors
64/// If reading from the VCS fails
65fn try_from_vcs(config: &dyn Vcs) -> Result<Lints> {
66    Ok(Lints::new(
67        Lint::all_lints()
68            .filter_map(|lint| {
69                get_config_or_default(config, lint, lint.enabled_by_default()).transpose()
70            })
71            .collect::<Result<BTreeSet<Lint>>>()?,
72    ))
73}
74
75fn get_config_or_default(config: &dyn Vcs, lint: Lint, default: bool) -> Result<Option<Lint>> {
76    Ok(config
77        .get_bool(&lint.config_key())?
78        .or(Some(default))
79        .filter(|lint_value| lint_value == &true)
80        .map(|_| lint))
81}