use std::{
collections::BTreeMap,
fmt::{self, Display, Formatter},
fs,
path::PathBuf,
};
use crate::{
decoder::{format_value, parse_line, Line},
DecodeError,
};
#[derive(Debug, Clone, Default)]
pub struct Group(pub BTreeMap<Key, Value>);
pub type Key = String;
pub type Value = String;
impl Group {
#[inline]
pub fn entry(&self, key: &str) -> Option<&str> {
self.0.get(key).map(|key| key.as_ref())
}
}
#[derive(Debug, Clone, Default)]
pub struct Groups(pub BTreeMap<GroupName, Group>);
pub type GroupName = String;
impl Groups {
#[inline]
pub fn group(&self, key: &str) -> Option<&Group> {
self.0.get(key)
}
}
#[derive(Debug, Clone)]
pub struct GenericEntry {
pub path: PathBuf,
pub groups: Groups,
}
impl GenericEntry {
pub fn from_str(path: impl Into<PathBuf>, input: &str) -> Result<GenericEntry, DecodeError> {
#[inline(never)]
fn inner<'a>(path: PathBuf, input: &'a str) -> Result<GenericEntry, DecodeError> {
let path: PathBuf = path.into();
let mut groups = Groups::default();
let mut active_group: Option<(&str, Group)> = None;
for line in input.lines() {
match parse_line(line)? {
Line::Group(key) => {
if let Some((prev_key, prev_group)) =
active_group.replace((key, Group::default()))
{
groups.0.insert(prev_key.to_string(), prev_group);
}
}
Line::Entry(key, value) => {
if let Some((_, group)) = active_group.as_mut() {
group.0.insert(key.to_string(), format_value(value)?);
}
}
_ => (),
}
}
if let Some((prev_key, prev_group)) = active_group.take() {
groups.0.insert(prev_key.to_string(), prev_group);
}
Ok(GenericEntry { groups, path })
}
inner(path.into(), input)
}
#[inline]
pub fn from_path(path: impl Into<PathBuf>) -> Result<GenericEntry, DecodeError> {
let path: PathBuf = path.into();
let input = fs::read_to_string(&path)?;
Self::from_str(path, &input)
}
#[inline]
pub fn group(&self, key: &str) -> Option<&Group> {
self.groups.group(key)
}
}
impl Display for GenericEntry {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
for (group_name, group) in &self.groups.0 {
let _ = writeln!(formatter, "[{}]", group_name);
for (key, value) in &group.0 {
let _ = writeln!(formatter, "{}={}", key, value);
}
writeln!(formatter)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
const GENERIC_ENTRY_PATH: &str = "tests_entries/generic.entry";
#[test]
fn can_load_file() {
let path = PathBuf::from(GENERIC_ENTRY_PATH);
let entry = GenericEntry::from_path(&path).expect("failed to parse file");
assert_eq!(entry.groups.0.len(), 1);
assert_eq!(entry.path, path)
}
#[test]
fn can_get_entries() {
let path = PathBuf::from(GENERIC_ENTRY_PATH);
let entry = GenericEntry::from_path(&path).expect("failed to parse file");
let group = entry.group("Thumbnailer Entry").unwrap();
assert_eq!(
group.entry("Exec"),
Some("cosmic-player --thumbnail %o --size %s %u")
);
assert_eq!(group.entry("TryExec"), Some("cosmic-player"));
assert_eq!(
group.entry("MimeType"),
Some("application/mxf;application/ram")
);
}
}