atomic_lib/
mapping.rs

1//! Because writing full URLs is error prone and time consuming, we map URLs to shortnames.
2//! These are often user-specific.
3//! This section provides tools to store, share and resolve these Mappings.
4
5use crate::errors::AtomicResult;
6use std::collections::hash_map::IntoIter;
7use std::{collections::HashMap, fs, path::Path};
8/// Maps shortanmes (bookmarks) to URLs
9#[derive(Clone)]
10pub struct Mapping {
11    hashmap: HashMap<String, String>,
12}
13
14impl Mapping {
15    pub fn init() -> Mapping {
16        let hashmap: HashMap<String, String> = HashMap::new();
17
18        Mapping { hashmap }
19    }
20
21    /// Checks if the input string is a Mapping or a valid URL.
22    /// Returns Some if it is valid.
23    /// If it is neither, a None is returned.
24    pub fn try_mapping_or_url(&self, mapping_or_url: &str) -> Option<String> {
25        match self.get(mapping_or_url) {
26            Some(hit) => Some(hit.into()),
27            None => {
28                // Currently only accept HTTP(S) protocol
29                if is_url(mapping_or_url) {
30                    return Some(mapping_or_url.into());
31                }
32                None
33            }
34        }
35    }
36
37    /// Add a new bookmark to the store
38    pub fn insert(&mut self, shortname: String, url: String) {
39        self.hashmap.insert(shortname, url);
40    }
41
42    /// Checks if the bookmark exists, returns it
43    pub fn get(&self, bookmark: &str) -> Option<&String> {
44        self.hashmap.get(bookmark)
45    }
46
47    /// Reads an .amp (atomic mapping) file from your disk.
48    pub fn read_mapping_from_file(&mut self, path: &Path) -> AtomicResult<()> {
49        let mapping_string = std::fs::read_to_string(path)?;
50        self.parse_mapping(&mapping_string)?;
51        Ok(())
52    }
53
54    /// Reads an .amp (atomic mapping) file.
55    /// This is a simple .ini-like text file that maps shortnames to URLs.
56    /// The left-hand should contain the shortname, the right-hand the URL.
57    /// Ignores # comments and empty lines.
58    /// Stores after parsing to the Mapping struct.
59    pub fn parse_mapping(&mut self, mapping_string: &str) -> AtomicResult<()> {
60        for line in mapping_string.lines() {
61            match line.chars().next() {
62                Some('#') => {}
63                Some(' ') => {}
64                Some(_) => {
65                    let split: Vec<&str> = line.split('=').collect();
66                    if split.len() == 2 {
67                        self.hashmap
68                            .insert(String::from(split[0]), String::from(split[1]));
69                    } else {
70                        return Err(format!("Error reading line {:?}", line).into());
71                    };
72                }
73                None => {}
74            };
75        }
76        Ok(())
77    }
78
79    /// Check if the bookmark shortname is present
80    pub fn contains_key(&self, key: &str) -> bool {
81        self.hashmap.contains_key(key)
82    }
83
84    /// Serializes the mapping and stores it to the path
85    pub fn write_mapping_to_disk(&self, path: &Path) {
86        let mut file_string: String = String::new();
87        for (key, url) in self.hashmap.clone().iter() {
88            let map = format!("{}={}\n", key, url);
89            file_string.push_str(&map);
90        }
91        fs::create_dir_all(path.parent().expect("Cannot create above root"))
92            .expect("Unable to create dirs");
93        fs::write(path, file_string).expect("Unable to write file");
94    }
95
96    pub fn populate(&mut self) -> AtomicResult<()> {
97        let mapping = include_str!("../defaults/default_mapping.amp");
98        self.parse_mapping(mapping)?;
99        Ok(())
100    }
101}
102
103/// Check if something is a URL
104pub fn is_url(string: &str) -> bool {
105    // TODO: Probably delete this second one, might break some tests though.
106    string.starts_with("http") || string.starts_with("_:")
107}
108
109impl IntoIterator for Mapping {
110    type Item = (String, String);
111    type IntoIter = IntoIter<String, String>;
112
113    fn into_iter(self) -> Self::IntoIter {
114        self.hashmap.into_iter()
115    }
116}