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 pub map: BiHashMap<Hash40, String>,
13
14 pub strict: bool,
19}
20
21#[derive(Debug)]
23pub enum CustomLabelError {
24 Io(io::Error),
25 MisingColumn,
26 ParseHashError(ParseHashError),
27}
28
29impl LabelMap {
30 pub fn clear(&mut self) {
32 self.map.clear();
33 }
34
35 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 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 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 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 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 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}