delimited list

This commit is contained in:
Rowan 2025-03-05 18:43:09 -06:00
parent b459d08a74
commit 7b8f351b21

View file

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