From 60985236c7070425c28aa0c5ce675306d06ab123 Mon Sep 17 00:00:00 2001 From: Freya Murphy Date: Thu, 16 Jan 2025 14:45:14 -0500 Subject: initial --- src/parse/de.rs | 291 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 src/parse/de.rs (limited to 'src/parse/de.rs') diff --git a/src/parse/de.rs b/src/parse/de.rs new file mode 100644 index 0000000..4b312a4 --- /dev/null +++ b/src/parse/de.rs @@ -0,0 +1,291 @@ +//! Deserialize an iris structure from a string. + +use crate::{Config, Plugin}; + +use serde::de; +use std::collections::HashSet; +use std::fmt; +use std::str::FromStr; + +/// Errors that can occur when deserializing a type +#[derive(thiserror::Error, Debug)] +pub enum Error { + /// Occurs when toml could not be deserialized + #[error(transparent)] + Toml(#[from] toml::de::Error), +} + +macro_rules! error { + ($($arg:tt)*) => { + de::Error::custom(format!($($arg)*)) + }; +} + +struct PluginIDsVisitor; +impl<'de> de::Visitor<'de> for PluginIDsVisitor { + type Value = Vec; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "single or list of plugin ids") + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + self.visit_string(v.to_string()) + } + + fn visit_string(self, v: String) -> Result + where + E: de::Error, + { + Ok(vec![v]) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: de::SeqAccess<'de>, + { + let mut values = + seq.size_hint().map_or_else(Vec::new, Vec::with_capacity); + while let Some(value) = seq.next_element::()? { + values.push(value); + } + Ok(values) + } +} + +struct PluginIDsSeed; +impl<'de> de::DeserializeSeed<'de> for PluginIDsSeed { + type Value = HashSet; + + fn deserialize(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + let values = d.deserialize_any(PluginIDsVisitor)?; + Ok(HashSet::from_iter(values)) + } +} + +// plugin visitor with seeded plugin id +struct PluginVisitor(Option); +impl<'de> de::Visitor<'de> for PluginVisitor { + type Value = Plugin; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self.0 { + Some(id) => write!(f, "arguments for plugin '{id}'"), + None => write!(f, "plugin id and arguments"), + } + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + self.visit_string(v.to_string()) + } + + fn visit_borrowed_str(self, v: &'_ str) -> Result + where + E: de::Error, + { + self.visit_string(v.to_string()) + } + + fn visit_string(self, url: String) -> Result + where + E: de::Error, + { + let Some(id) = self.0 else { + return Err(de::Error::missing_field("id")); + }; + Self::Value::new(id, &url).map_err(E::custom) + } + + fn visit_map(self, mut map: A) -> Result + where + A: de::MapAccess<'de>, + { + // parse each map value as a possbile field for the plugin + let mut id = self.0; // plugin id (required) + let mut url = None; // repository url (required) + let mut commit = None; // commit to lock to + let mut branch = None; // branch to lock to + let mut run = None; // command to run on launch + let mut before = HashSet::new(); // plugins to load before + let mut after = HashSet::new(); // plugins to load after + + while let Some(key) = map.next_key::()? { + match key.as_str() { + // plugin id + "id" => { + id = Some(map.next_value::()?); + } + // repo url + "url" => { + url = Some(map.next_value::()?); + } + // locked commit + "commit" => { + commit = Some(map.next_value::()?); + } + // locked branch + "branch" => { + branch = Some(map.next_value::()?); + } + // vim command to run on launch + "run" => { + run = Some(map.next_value::()?); + } + // plugins to load before + "before" => { + before = map.next_value_seed(PluginIDsSeed)?; + } + // plugins to load after + "after" => { + after = map.next_value_seed(PluginIDsSeed)?; + } + // invalid key! + key => return Err(error!("unknown plugin field '{key}'")), + }; + } + + // id is a required field + let Some(id) = id else { + return Err(de::Error::missing_field("id")); + }; + + // url is a required field + let Some(url) = url else { + return Err(de::Error::missing_field("url")); + }; + + let mut plugin = + Self::Value::new(id, &url).map_err(de::Error::custom)?; + plugin.commit = commit; + plugin.branch = branch; + plugin.run = run; + plugin.before = before; + plugin.after = after; + Ok(plugin) + } +} + +// deserialize plugin with possible id +struct PluginSeed(Option); +impl<'de> de::DeserializeSeed<'de> for PluginSeed { + type Value = Plugin; + + fn deserialize(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_any(PluginVisitor(self.0)) + } +} + +// plugins visitor +struct PluginsVisitor; +impl<'de> de::Visitor<'de> for PluginsVisitor { + type Value = Vec; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("map of plugin id's to their arguments") + } + + fn visit_map(self, mut map: A) -> Result + where + A: de::MapAccess<'de>, + { + let mut plugins = + map.size_hint().map_or_else(Vec::new, Vec::with_capacity); + while let Some(id) = map.next_key()? { + let plugin = map.next_value_seed(PluginSeed(Some(id)))?; + plugins.push(plugin); + } + Ok(plugins) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: de::SeqAccess<'de>, + { + let mut plugins = + seq.size_hint().map_or_else(Vec::new, Vec::with_capacity); + while let Some(plugin) = seq.next_element_seed(PluginSeed(None))? { + plugins.push(plugin); + } + Ok(plugins) + } +} + +// plugins seed +struct PluginsSeed; +impl<'de> de::DeserializeSeed<'de> for PluginsSeed { + type Value = Vec; + + fn deserialize(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_any(PluginsVisitor) + } +} + +// config visitor +struct ConfigVisitor; +impl<'de> de::Visitor<'de> for ConfigVisitor { + type Value = Config; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("iris config value") + } + + fn visit_map(self, mut map: A) -> Result + where + A: de::MapAccess<'de>, + { + let mut plugins = None; // list of plugins (required) + while let Some(key) = map.next_key::()? { + match key.as_str() { + "plugins" => { + plugins = Some(map.next_value_seed(PluginsSeed)?); + } + key => return Err(error!("unknown config field '{key}'")), + }; + } + // plugins is a required field + let Some(mut plugins) = plugins else { + return Err(de::Error::missing_field("plugins")); + }; + plugins.sort(); + Ok(Config { plugins }) + } +} + +impl<'de> de::Deserialize<'de> for Config { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_map(ConfigVisitor) + } +} + +impl Config { + pub fn parse(s: &str) -> crate::Result { + toml::from_str(s) + .map_err(Error::from) + .map_err(crate::Error::from) + } +} + +impl FromStr for Config { + type Err = crate::Error; + fn from_str(s: &str) -> Result { + Self::parse(s) + } +} -- cgit v1.2.3-freya