pre_commit_sort/
pre_commit_config.rs

1/// ref. <https://pre-commit.com/#pre-commit-configyaml---top-level>
2use std::collections::BTreeMap;
3
4use crate::{ConfigHook, Local, Meta, PreCommit, Remote, Repo, CI};
5
6#[serde_with::skip_serializing_none]
7#[derive(serde::Serialize, serde::Deserialize, Debug, Eq, Ord, PartialEq, PartialOrd, Clone)]
8pub struct PreCommitConfig {
9    ci: Option<CI>,
10    repos: Vec<Repo>,
11    default_install_hook_types: Option<Vec<String>>,
12    default_language_version: Option<BTreeMap<String, String>>,
13    default_stages: Option<Vec<String>>,
14    files: Option<String>,
15    exclude: Option<String>,
16    fail_fast: Option<bool>,
17    minimum_pre_commit_version: Option<String>,
18}
19
20impl PreCommitConfig {
21    #[must_use]
22    pub const fn new() -> Self {
23        Self {
24            ci: None,
25            repos: Vec::new(),
26            default_install_hook_types: None,
27            default_language_version: None,
28            default_stages: None,
29            files: None,
30            exclude: None,
31            fail_fast: None,
32            minimum_pre_commit_version: None,
33        }
34    }
35
36    pub fn add_remote(&mut self, remote: Remote) {
37        self.repos.push(Repo::Remote(remote));
38    }
39
40    pub fn add_local(&mut self, local: Local) {
41        self.repos.push(Repo::Local(local));
42    }
43
44    pub fn add_meta(&mut self, meta: Meta) {
45        self.repos.push(Repo::Meta(meta));
46    }
47
48    /// Sort and deduplicate repos and their hooks
49    pub fn sort(&mut self) {
50        for repo in &mut self.repos {
51            repo.sort();
52        }
53        self.repos.sort();
54        self.repos.dedup();
55        self.dedup_rev();
56    }
57
58    /// If two repos differ only by their rev, keep only the latest
59    fn dedup_rev(&mut self) {
60        while let Some(i) = self.find_first_rev_dup() {
61            self.repos.remove(i);
62        }
63    }
64
65    fn find_first_rev_dup(&self) -> Option<usize> {
66        // Todo: work only on Remote
67        for (i, w) in self.repos.windows(2).enumerate() {
68            if w[0].equal_but_rev(&w[1]) {
69                return Some(i);
70            }
71        }
72        None
73    }
74
75    /// Install pre-commit-sort in this .pre-commit-config.yaml
76    pub fn install(&mut self) {
77        const VERSION: &str = env!("CARGO_PKG_VERSION");
78        let mut remote = Remote::new(
79            env!("CARGO_PKG_REPOSITORY").to_string(),
80            format!("v{VERSION}"),
81        );
82        let hook = ConfigHook::new(env!("CARGO_PKG_NAME").to_string());
83        remote.add_hook(hook);
84        self.add_remote(remote);
85    }
86}
87
88impl PreCommit for PreCommitConfig {
89    const PATH: &'static str = ".pre-commit-config.yaml";
90
91    fn process(&mut self, install: bool) {
92        if install {
93            self.install();
94        }
95        self.sort();
96    }
97}
98
99impl Default for PreCommitConfig {
100    fn default() -> Self {
101        Self::new()
102    }
103}