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)]
pub struct DelimitedString {
inner: Vec<OsString>,
delimiter: char,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DelimitedList<const D: char, T>(Vec<T>);
impl DelimitedString {
pub fn new<T, I>(inner: I, delimiter: char) -> Self
where
T: Into<OsString>,
I: IntoIterator<Item = T>,
{
Self {
inner: inner.into_iter().map(Into::into).collect(),
delimiter,
}
}
impl<const D: char, T: FromStr> FromStr for DelimitedList<D, T> {
type Err = T::Err;
pub fn from_str(s: &str, delimiter: char) -> Self {
Self::from_iter(s.split(delimiter), delimiter)
}
pub fn from_iter<I, T>(iter: I, delimiter: char) -> Self
where
T: Into<OsString>,
I: IntoIterator<Item = T>,
{
Self::new(iter, delimiter)
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(
s.split(D)
.map(T::from_str)
.collect::<Result<Vec<T>, Self::Err>>()?,
))
}
}
impl AsRef<OsStr> for DelimitedString {
fn as_ref(&self) -> &OsStr {
&self.inner.join(self.delimiter)
impl<const D: char, T: Into<T>> FromIterator<T> for DelimitedList<D, T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
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 {
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<T> {
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<T> From<T> for Options<T> {
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Sources(Options<OptionsSource>);
pub struct Sources(DelimitedList<',', OptionsSource>);
impl FromStr for Sources {
type Err = ParseOptionsSourceError;
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)]
pub struct UncheckedOptions(Options<DisplayString>);
pub struct UncheckedOptions(DelimitedList<',', DisplayString>);
impl FromStr for UncheckedOptions {
type Err = ParseUncheckedOptionsError;
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 {
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 {
value.0
}
}
impl AsRef<Options<DisplayString>> for UncheckedOptions {
fn as_ref(&self) -> &Options<DisplayString> {
impl AsRef<DelimitedList<',', DisplayString>> for UncheckedOptions {
fn as_ref(&self) -> &DelimitedList<',', DisplayString> {
&self.0
}
}
impl From<Options<DisplayString>> for UncheckedOptions {
fn from(value: Options<DisplayString>) -> Self {
impl From<DelimitedList<',', DisplayString>> for UncheckedOptions {
fn from(value: DelimitedList<',', DisplayString>) -> Self {
Self(value)
}
}
@ -721,7 +724,7 @@ pub enum MountOption {
MakeDir(Option<Permissions>),
NoMtab,
OptionsMode(OptionsMode),
OptionsSource(Options<OptionsSource>),
OptionsSource(DelimitedList<',', OptionsSource>),
OptionsSourceForce,
OnlyOnce,
Options(UncheckedOptions),
@ -742,7 +745,7 @@ impl FromStr for MountOption {
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<MountOption>,
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::{
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::<KeyValuePair>::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);