use std::collections::HashMap;
use serde::{Deserialize, Serialize};
const SEPERATOR: char = '/';
const NAME_PLACEHOLDER: char = '*';
#[derive(Deserialize, Serialize, Debug)]
pub struct Describer {
descriptions: HashMap<String, String>,
patterns: HashMap<String, String>,
}
impl Describer {
pub fn new() -> Describer {
Describer {
descriptions: HashMap::new(),
patterns: HashMap::new(),
}
}
pub fn new_with(d: HashMap<String, String>, p: HashMap<String, String>) -> Describer {
Describer {
descriptions: d,
patterns: p,
}
}
pub fn new_from_json(json: &str) -> Result<Describer, serde_json::Error> {
serde_json::from_str::<Describer>(json)
}
pub fn describe(&self, path: &str) -> Option<String> {
match self.descriptions.get(path) {
Some(d) => Some(d.clone()),
None => self.describe_using_pattern(path),
}
}
fn describe_using_pattern(&self, path: &str) -> Option<String> {
let parent: Vec<&str> = path.rsplitn(2, SEPERATOR).collect();
if parent.len() != 2 {
None
} else {
match self.patterns.get(parent[1]) {
Some(p) => Some(p.replace(NAME_PLACEHOLDER, parent[0])),
None => None,
}
}
}
pub fn add_description(&mut self, path: &str, desc: &str) {
self.descriptions.insert(path.to_string(), desc.to_string());
}
pub fn add_pattern(&mut self, path: &str, desc: &str) {
self.patterns.insert(path.to_string(), desc.to_string());
}
pub fn to_json(&self, pretty: bool) -> Result<String, serde_json::Error> {
if pretty {
serde_json::to_string_pretty(self)
} else {
serde_json::to_string(self)
}
}
}
impl Default for Describer {
fn default() -> Describer {
Describer::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_describe_test() {
let mut descriptions: HashMap<String, String> = HashMap::new();
let mut patterns: HashMap<String, String> = HashMap::new();
for (path, desc, is_pattern) in [
("/path/to/dir", "This is /path/to/dir.", false),
("/another/dir", "This is /another/dir.", false),
("/yet/another/path", "This is /yet/another/path.", false),
("/path/to/dir", "* is in /path/to/dir.", true),
("/yet/another/path", "* is in /yet/another/path.", true),
("/obvious", "* is *", true),
("/yet/another", "* is in /yet/another/path.", true),
]
.iter()
{
if *is_pattern {
patterns.insert(path.to_string(), desc.to_string());
} else {
descriptions.insert(path.to_string(), desc.to_string());
}
}
describe_tester(&Describer::new_with(descriptions, patterns));
}
#[test]
fn new_from_json_describe_test() {
match Describer::new_from_json(
"
{
\"descriptions\": {
\"/path/to/dir\": \"This is /path/to/dir.\",
\"/another/dir\": \"This is /another/dir.\",
\"/yet/another/path\": \"This is /yet/another/path.\"
},
\"patterns\": {
\"/path/to/dir\": \"* is in /path/to/dir.\",
\"/yet/another/path\": \"* is in /yet/another/path.\",
\"/obvious\": \"* is *\",
\"/yet/another\": \"* is in /yet/another/path.\"
}
}",
) {
Ok(d) => describe_tester(&d),
Err(e) => panic!(e),
};
}
#[test]
fn add_test() {
let mut d = Describer::new();
d.add_description("path/to/directory", "This is an empty directory.");
d.add_pattern("parent/directory", "* is a child of parent/directory.");
assert_eq!(
d.to_json(false).unwrap(),
format!(
"{}{}{}{}",
"{\"descriptions\":",
"{\"path/to/directory\":\"This is an empty directory.\"},",
"\"patterns\":",
"{\"parent/directory\":\"* is a child of parent/directory.\"}}"
)
);
}
fn describe_tester(describer: &Describer) {
for (path, desc, is_none) in [
("/path/to/dir", "This is /path/to/dir.", false),
("/another/dir", "This is /another/dir.", false),
("/yet/another/path", "This is /yet/another/path.", false),
("/path/to/dir/1", "1 is in /path/to/dir.", false),
("/path/to/dir/things", "things is in /path/to/dir.", false),
("/yet/another/path/1", "1 is in /yet/another/path.", false),
("/yet/another/path/$", "$ is in /yet/another/path.", false),
("/obvious/obviously", "obviously is obviously", false),
("/doesn't/exist", "", true),
]
.iter()
{
assert_eq!(
describer.describe(path),
if *is_none {
None
} else {
Some(desc.to_string())
}
);
}
}
}