superdiff/
types.rs

1use std::collections::{HashMap, HashSet};
2use std::fmt;
3use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6
7/// A structure to easily move parameters from one place to another.
8#[derive(Clone, Debug)]
9pub struct CompFile {
10    pub file: PathBuf,
11    pub lines: Vec<String>,
12    pub start: usize,
13}
14
15/// A matching block.
16///
17/// Points to a single block of lines in some file.
18#[derive(Hash, PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
19pub struct Match {
20    pub file: PathBuf,
21    pub line: usize,
22    pub size: usize,
23}
24
25/// A bunch of Matches.
26///
27/// Consists of an original match that is deemed similar to a list of other matches. The match that
28/// is the key is arbitrarily chosen and is fungible.
29pub struct Matches(pub HashMap<Match, Vec<Match>>);
30
31/// A lookup that points some arbitrary match to the key match.
32///
33/// Used to check which key some match belongs to, in order to insert into `Matches`.
34pub struct MatchesLookup(pub HashMap<Match, Match>);
35
36#[derive(Serialize, Clone, Deserialize, PartialEq, Eq, Debug)]
37pub struct JsonFileInfo {
38    pub count_blocks: usize,
39}
40
41#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
42pub struct JsonBlockInfo {
43    pub starting_line: usize,
44    pub block_length: usize,
45}
46
47#[derive(Serialize, Deserialize, Eq, Debug)]
48pub struct JsonMatch {
49    pub files: HashMap<PathBuf, JsonFileInfo>,
50    pub blocks: HashMap<PathBuf, Vec<JsonBlockInfo>>,
51}
52
53#[derive(Serialize, Deserialize, Eq, Debug)]
54pub struct JsonRoot {
55    pub version: String,
56    pub files: HashMap<PathBuf, JsonFileInfo>,
57    pub matches: Vec<JsonMatch>,
58}
59
60impl From<&Match> for JsonBlockInfo {
61    fn from(m: &Match) -> Self {
62        Self {
63            starting_line: m.line,
64            block_length: m.size,
65        }
66    }
67}
68
69impl PartialEq for JsonMatch {
70    fn eq(&self, other: &Self) -> bool {
71        if self.files != other.files || self.blocks.len() != other.blocks.len() {
72            return false;
73        }
74
75        for (k, v) in &self.blocks {
76            match other.blocks.get(k) {
77                Some(other_v) => {
78                    let (a_info, b_info): (HashSet<_>, HashSet<_>) =
79                        (v.iter().collect(), other_v.iter().collect());
80                    if a_info != b_info {
81                        return false;
82                    }
83                }
84                None => {
85                    return false;
86                }
87            }
88        }
89
90        true
91    }
92}
93
94impl From<(&Match, &Vec<Match>)> for JsonMatch {
95    fn from((initial_match, other_matches): (&Match, &Vec<Match>)) -> Self {
96        let mut blocks = HashMap::new();
97        let mut files = HashMap::new();
98        files.insert(initial_match.file.clone(), JsonFileInfo { count_blocks: 1 });
99        blocks.insert(
100            initial_match.file.clone(),
101            vec![JsonBlockInfo::from(initial_match)],
102        );
103
104        for m in other_matches {
105            let f = m.clone().file;
106            files
107                .entry(f.clone())
108                .and_modify(|info| info.count_blocks += 1)
109                .or_insert(JsonFileInfo { count_blocks: 1 });
110            blocks
111                .entry(f)
112                .and_modify(|v| v.push(JsonBlockInfo::from(m)))
113                .or_insert(vec![JsonBlockInfo::from(m)]);
114        }
115
116        Self { files, blocks }
117    }
118}
119
120impl fmt::Display for JsonMatch {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        write!(
123            f,
124            "=== MATCH ===\n{}\n",
125            self.blocks
126                .iter()
127                .map(|(filename, infos)| format!(
128                    "File: {}\nLines: {:?}\nSize: {}",
129                    filename.display(),
130                    infos
131                        .iter()
132                        .map(|info| info.starting_line)
133                        .collect::<Vec<usize>>(),
134                    infos[0].block_length,
135                ))
136                .collect::<Vec<String>>()
137                .join("\n---\n")
138        )
139    }
140}
141
142impl From<&Matches> for JsonRoot {
143    fn from(m: &Matches) -> Self {
144        let version = clap::crate_version!().to_owned();
145        let matches: Vec<JsonMatch> = m.0.iter().map(JsonMatch::from).collect();
146        let jm_files = matches.iter().map(|jm| jm.files.clone());
147        let mut files: HashMap<PathBuf, JsonFileInfo> = HashMap::new();
148
149        for jmf in jm_files {
150            for (filename, info) in jmf {
151                files
152                    .entry(filename)
153                    .and_modify(|v| v.count_blocks += info.count_blocks)
154                    .or_insert(info);
155            }
156        }
157
158        Self {
159            version,
160            files,
161            matches,
162        }
163    }
164}
165
166impl JsonRoot {
167    pub fn unique_matches(&self) -> usize {
168        self.matches.len()
169    }
170
171    pub fn json(&self) -> String {
172        serde_json::to_string(&self).unwrap_or("{}".to_owned())
173    }
174}
175
176impl PartialEq for JsonRoot {
177    fn eq(&self, other: &Self) -> bool {
178        if self.matches.len() != other.matches.len() {
179            return false;
180        }
181
182        for item in &self.matches {
183            if !other.matches.contains(item) {
184                return false;
185            }
186        }
187
188        true
189    }
190}
191
192impl fmt::Display for JsonRoot {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        write!(
195            f,
196            "{}",
197            self.matches
198                .iter()
199                .map(|x| x.to_string())
200                .collect::<Vec<String>>()
201                .join("\n")
202        )
203    }
204}
205
206pub type ComparisonFn = Box<dyn Fn(&String, &String) -> bool>;
207pub type FileCache = HashMap<PathBuf, Vec<String>>;
208
209impl Match {
210    pub fn from_compfiles(f1: &CompFile, f2: &CompFile, block_length: usize) -> (Self, Self) {
211        (
212            Self {
213                file: f1.file.clone(),
214                line: f1.start + 1,
215                size: block_length,
216            },
217            Self {
218                file: f2.file.clone(),
219                line: f2.start + 1,
220                size: block_length,
221            },
222        )
223    }
224}
225
226fn get_lines_from_file(file: &PathBuf) -> std::io::Result<Vec<String>> {
227    Ok(std::fs::read_to_string(file)?
228        .split('\n')
229        .map(|line| line.trim().to_owned())
230        .collect::<Vec<String>>())
231}
232
233impl CompFile {
234    pub fn current_line(&self) -> &String {
235        &self.lines[self.start]
236    }
237
238    pub fn from_files(f1: &PathBuf, f2: &PathBuf) -> Option<(Self, Self)> {
239        match (get_lines_from_file(f1), get_lines_from_file(f2)) {
240            (Ok(lines1), Ok(lines2)) => Some((
241                Self {
242                    file: f1.clone(),
243                    lines: lines1,
244                    start: 0,
245                },
246                Self {
247                    file: f2.clone(),
248                    lines: lines2,
249                    start: 0,
250                },
251            )),
252            _ => None,
253        }
254    }
255}