hash40/
label_map.rs

1use crate::errors::ParseHashError;
2use crate::{hash40, Hash40};
3use bimap::BiHashMap;
4
5use std::fs::File;
6use std::io::{self, BufRead, BufReader};
7use std::path::Path;
8
9#[derive(Debug, Default, Clone)]
10pub struct LabelMap {
11    /// A bidirectional map to associate hashes and their labels
12    pub map: BiHashMap<Hash40, String>,
13
14    /// Controls whether the default hash40 method is used instead of returning None
15    /// when you try to find the hash of a label which is not present in the map.
16    ///
17    /// By default, set to false
18    pub strict: bool,
19}
20
21/// The type of error returned when reading from custom label files
22#[derive(Debug)]
23pub enum CustomLabelError {
24    Io(io::Error),
25    MisingColumn,
26    ParseHashError(ParseHashError),
27}
28
29impl LabelMap {
30    /// Convenience method to clear the labels within the map
31    pub fn clear(&mut self) {
32        self.map.clear();
33    }
34
35    /// Inserts labels into the map, using the default hash40 method for the hash
36    pub fn add_labels<I: IntoIterator<Item = String>>(&mut self, labels: I) {
37        for l in labels {
38            self.map.insert(Hash40::new(&l), l);
39        }
40    }
41
42    /// Inserts labels into the map, providing both the hash and the associated label.
43    ///
44    /// Users can insert a label for a hash, even if the hash of the label inserted doesn't
45    /// match the paired hash. This allows custom descriptive labels when the true label is
46    /// not known for the hash.
47    pub fn add_custom_labels<I: Iterator<Item = (Hash40, String)>>(&mut self, labels: I) {
48        for (hash, label) in labels {
49            self.map.insert(hash, label);
50        }
51    }
52
53    /// Opens a file and returns a list of newline-separated labels
54    pub fn read_labels<P: AsRef<Path>>(path: P) -> Result<Vec<String>, io::Error> {
55        let reader = BufReader::new(File::open(path)?);
56        reader.lines().collect()
57    }
58
59    /// Opens a file and returns a list of line-separated pairs of hashes and labels.
60    /// Each hash-label pair is separated by a comma, and the hash must be formatted
61    /// in hexadecimal, beginning with "0x"
62    pub fn read_custom_labels<P: AsRef<Path>>(
63        path: P,
64    ) -> Result<Vec<(Hash40, String)>, CustomLabelError> {
65        let reader = BufReader::new(File::open(path)?);
66        reader
67            .lines()
68            .map(|line_result| {
69                let line = line_result?;
70                let mut split = line.split(',');
71                split
72                    .next()
73                    .zip(split.next())
74                    .ok_or(CustomLabelError::MisingColumn)
75                    .and_then(|(hash, label)| {
76                        Ok((Hash40::from_hex_str(hash)?, String::from(label)))
77                    })
78            })
79            .collect()
80    }
81
82    /// A combination of the two functions [`Self::add_labels`] and [`Self::read_labels`]
83    pub fn add_labels_from_path<P: AsRef<Path>>(&mut self, path: P) -> Result<(), io::Error> {
84        self.add_labels(Self::read_labels(path)?);
85        Ok(())
86    }
87
88    /// A combination of the two functions [`Self::add_custom_labels`] and
89    /// [`Self::read_custom_labels`]
90    pub fn add_custom_labels_from_path<P: AsRef<Path>>(
91        &mut self,
92        path: P,
93    ) -> Result<(), CustomLabelError> {
94        self.add_custom_labels(Self::read_custom_labels(path)?.into_iter());
95        Ok(())
96    }
97
98    pub fn label_of(&self, hash: Hash40) -> Option<String> {
99        self.map.get_by_left(&hash).map(Into::into)
100    }
101
102    pub fn hash_of(&self, label: &str) -> Option<Hash40> {
103        self.map
104            .get_by_right(label)
105            .copied()
106            .or_else(|| (!self.strict).then(|| hash40(label)))
107    }
108}
109
110impl From<io::Error> for CustomLabelError {
111    fn from(err: io::Error) -> Self {
112        Self::Io(err)
113    }
114}
115
116impl From<ParseHashError> for CustomLabelError {
117    fn from(err: ParseHashError) -> Self {
118        Self::ParseHashError(err)
119    }
120}