summaryrefslogtreecommitdiff
path: root/src/parse/de.rs
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2025-01-16 14:45:14 -0500
committerFreya Murphy <freya@freyacat.org>2025-01-16 14:45:14 -0500
commit60985236c7070425c28aa0c5ce675306d06ab123 (patch)
tree7448aef5dadf19d4504151ebc370f9e20757ef10 /src/parse/de.rs
downloadiris-60985236c7070425c28aa0c5ce675306d06ab123.tar.gz
iris-60985236c7070425c28aa0c5ce675306d06ab123.tar.bz2
iris-60985236c7070425c28aa0c5ce675306d06ab123.zip
initialHEADmain
Diffstat (limited to 'src/parse/de.rs')
-rw-r--r--src/parse/de.rs291
1 files changed, 291 insertions, 0 deletions
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<String>;
+
+ fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "single or list of plugin ids")
+ }
+
+ fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ self.visit_string(v.to_string())
+ }
+
+ fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(vec![v])
+ }
+
+ fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
+ 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::<String>()? {
+ values.push(value);
+ }
+ Ok(values)
+ }
+}
+
+struct PluginIDsSeed;
+impl<'de> de::DeserializeSeed<'de> for PluginIDsSeed {
+ type Value = HashSet<String>;
+
+ fn deserialize<D>(self, d: D) -> Result<Self::Value, D::Error>
+ 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<String>);
+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<E>(self, v: &str) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ self.visit_string(v.to_string())
+ }
+
+ fn visit_borrowed_str<E>(self, v: &'_ str) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ self.visit_string(v.to_string())
+ }
+
+ fn visit_string<E>(self, url: String) -> Result<Self::Value, E>
+ 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<A>(self, mut map: A) -> Result<Self::Value, A::Error>
+ 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::<String>()? {
+ match key.as_str() {
+ // plugin id
+ "id" => {
+ id = Some(map.next_value::<String>()?);
+ }
+ // repo url
+ "url" => {
+ url = Some(map.next_value::<String>()?);
+ }
+ // locked commit
+ "commit" => {
+ commit = Some(map.next_value::<String>()?);
+ }
+ // locked branch
+ "branch" => {
+ branch = Some(map.next_value::<String>()?);
+ }
+ // vim command to run on launch
+ "run" => {
+ run = Some(map.next_value::<String>()?);
+ }
+ // 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<String>);
+impl<'de> de::DeserializeSeed<'de> for PluginSeed {
+ type Value = Plugin;
+
+ fn deserialize<D>(self, d: D) -> Result<Self::Value, D::Error>
+ 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<Plugin>;
+
+ fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str("map of plugin id's to their arguments")
+ }
+
+ fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
+ 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<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
+ 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<Plugin>;
+
+ fn deserialize<D>(self, d: D) -> Result<Self::Value, D::Error>
+ 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<A>(self, mut map: A) -> Result<Self::Value, A::Error>
+ where
+ A: de::MapAccess<'de>,
+ {
+ let mut plugins = None; // list of plugins (required)
+ while let Some(key) = map.next_key::<String>()? {
+ 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: D) -> Result<Self, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ d.deserialize_map(ConfigVisitor)
+ }
+}
+
+impl Config {
+ pub fn parse(s: &str) -> crate::Result<Self> {
+ 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, Self::Err> {
+ Self::parse(s)
+ }
+}