From 7b8f351b21f4b667dab6ec90d2322b7733fe438c Mon Sep 17 00:00:00 2001 From: rowan Date: Wed, 5 Mar 2025 18:43:09 -0600 Subject: [PATCH] delimited list --- src/fs/mount.rs | 140 ++++++++++++++++++++++++++---------------------- 1 file changed, 76 insertions(+), 64 deletions(-) diff --git a/src/fs/mount.rs b/src/fs/mount.rs index c9ead8b..7644f4e 100644 --- a/src/fs/mount.rs +++ b/src/fs/mount.rs @@ -376,53 +376,54 @@ impl From for GidRange { } } -// TODO: finish implementation to solve options delimiter problem -#[derive(Clone, Debug)] -pub struct DelimitedString { - inner: Vec, - delimiter: char, -} +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DelimitedList(Vec); -impl DelimitedString { - pub fn new(inner: I, delimiter: char) -> Self - where - T: Into, - I: IntoIterator, - { - Self { - inner: inner.into_iter().map(Into::into).collect(), - delimiter, - } - } +impl FromStr for DelimitedList { + type Err = T::Err; - pub fn from_str(s: &str, delimiter: char) -> Self { - Self::from_iter(s.split(delimiter), delimiter) - } - - pub fn from_iter(iter: I, delimiter: char) -> Self - where - T: Into, - I: IntoIterator, - { - Self::new(iter, delimiter) + fn from_str(s: &str) -> Result { + Ok(Self( + s.split(D) + .map(T::from_str) + .collect::, Self::Err>>()?, + )) } } -impl AsRef for DelimitedString { - fn as_ref(&self) -> &OsStr { - &self.inner.join(self.delimiter) +impl> FromIterator for DelimitedList { + fn from_iter>(iter: I) -> Self { + Self(iter.into_iter().collect()) } } -impl Display for DelimitedString { +impl<'a, const D: char, T> IntoIterator for &'a DelimitedList { + type Item = &'a T; + type IntoIter = Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl IntoIterator for DelimitedList { + type Item = T; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Display for DelimitedList { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut iter = self.inner.iter(); + let mut iter = self.0.iter(); if let Some(head) = iter.next() { - write!(f, "{}", head.display())?; + write!(f, "{head}")?; for item in iter { - write!(f, "{}", item.display())?; + write!(f, "{D}{item}")?; } } @@ -537,7 +538,7 @@ impl<'a, T> IntoIterator for &'a Options { type IntoIter = Iter<'a, T>; fn into_iter(self) -> Self::IntoIter { - self.options.as_slice().into_iter() + self.options.as_slice().iter() } } @@ -554,13 +555,13 @@ impl From for Options { } #[derive(Clone, Debug, PartialEq, Eq)] -pub struct Sources(Options); +pub struct Sources(DelimitedList<',', OptionsSource>); impl FromStr for Sources { type Err = ParseOptionsSourceError; fn from_str(s: &str) -> Result { - Ok(Self(Options::from_str(s)?)) + Ok(Self(DelimitedList::<',', OptionsSource>::from_str(s)?)) } } @@ -582,13 +583,13 @@ impl From for ParseUncheckedOptionsError { } #[derive(Clone, Debug, PartialEq, Eq)] -pub struct UncheckedOptions(Options); +pub struct UncheckedOptions(DelimitedList<',', DisplayString>); impl FromStr for UncheckedOptions { type Err = ParseUncheckedOptionsError; fn from_str(s: &str) -> Result { - Ok(Self(Options::::from_str(s)?)) + Ok(Self(DelimitedList::<',', DisplayString>::from_str(s)?)) } } @@ -606,24 +607,26 @@ impl From for UncheckedOptions { impl> FromIterator for UncheckedOptions { fn from_iter>(iter: I) -> Self { - Self(Options::from_iter(iter.into_iter().map(Into::into))) + Self(DelimitedList::<',', DisplayString>::from_iter( + iter.into_iter().map(Into::into), + )) } } -impl From for Options { +impl From for DelimitedList<',', DisplayString> { fn from(value: UncheckedOptions) -> Self { value.0 } } -impl AsRef> for UncheckedOptions { - fn as_ref(&self) -> &Options { +impl AsRef> for UncheckedOptions { + fn as_ref(&self) -> &DelimitedList<',', DisplayString> { &self.0 } } -impl From> for UncheckedOptions { - fn from(value: Options) -> Self { +impl From> for UncheckedOptions { + fn from(value: DelimitedList<',', DisplayString>) -> Self { Self(value) } } @@ -721,7 +724,7 @@ pub enum MountOption { MakeDir(Option), NoMtab, OptionsMode(OptionsMode), - OptionsSource(Options), + OptionsSource(DelimitedList<',', OptionsSource>), OptionsSourceForce, OnlyOnce, Options(UncheckedOptions), @@ -742,7 +745,7 @@ impl FromStr for MountOption { fn from_str(s: &str) -> Result { let s = s.trim().trim_start_matches('-'); - let option = match s.split_once(|delim| delim == ' ' || delim == '=') { + let option = match s.split_once([' ', '=']) { Some((option, value)) => (option, Some(value)), None => (s, None), }; @@ -764,7 +767,11 @@ impl FromStr for MountOption { )), ("n" | "no-mtab", None) => Ok(Self::NoMtab), ("options-mode", Some(mode)) => Ok(Self::OptionsMode(OptionsMode::from_str(mode)?)), - ("options-source", Some(source)) => Ok(Self::OptionsSource(Options::from_str(source)?)), + ("options-source", Some(source)) => { + Ok(Self::OptionsSource( + DelimitedList::<',', OptionsSource>::from_str(source)?, + )) + } ("options-source-force", None) => Ok(Self::OptionsSourceForce), ("onlyonce", None) => Ok(Self::OnlyOnce), ("o" | "options", Some(options)) => { @@ -941,7 +948,7 @@ impl Display for MountOperation { pub struct Mount { device_id: DeviceId, mountpoint: PathBuf, - options: Options, + options: DelimitedList<',', MountOption>, } impl Mount { @@ -953,7 +960,9 @@ impl Mount { Self { device_id: source.into(), mountpoint: target.into(), - options: Options::from_iter(options.into_iter().map(Into::into)), + options: DelimitedList::<',', MountOption>::from_iter( + options.into_iter().map(Into::into), + ), } } } @@ -996,10 +1005,13 @@ mod tests { use enumflags2::BitFlags; - use crate::fs::{ - id_mapping::UntypedIdRange, - mount::{ParseDeviceIdTypeError, ParseOptionsSourceError}, - permission::Permissions, + use crate::{ + fs::{ + id_mapping::UntypedIdRange, + mount::{ParseDeviceIdTypeError, ParseOptionsSourceError}, + permission::Permissions, + }, + prelude::DelimitedList, }; use super::{ @@ -1115,18 +1127,18 @@ mod tests { } #[test] - fn options() { - let opts = Options::::from_str("key=value,keyword").unwrap(); + fn delimited_list() { + type Kvp = KeyValuePair; + type List = DelimitedList<',', Kvp>; + + let opts = List::from_str("key=value,keyword").unwrap(); assert_eq!( opts, - Options::new( - vec![ - KeyValuePair::new("key", Some("value")), - KeyValuePair::new("keyword", None::<&str>) - ], - ',' - ) + List::from_iter(vec![ + Kvp::new("key", Some("value")), + Kvp::new("keyword", None::<&str>) + ]) ); assert_eq!(opts.to_string(), "key=value,keyword"); @@ -1289,7 +1301,7 @@ mod tests { .assert(MountOption::OptionsMode(OptionsMode::Ignore)); Opts::from(("--options-source", "fstab")).assert(MountOption::OptionsSource( - vec![OptionsSource::Fstab].into(), + DelimitedList::from_iter(vec![OptionsSource::Fstab]), )); Opts::from("--options-source-force").assert(MountOption::OptionsSourceForce);