From b459d08a746b89fd04b443c2fd634b45ba6a7775 Mon Sep 17 00:00:00 2001
From: rowan <rowan@kitsu.cafe>
Date: Wed, 5 Mar 2025 04:30:44 -0600
Subject: [PATCH] wip: options delimter

---
 src/fs/mount.rs                  | 150 +++++++++++++++++++++++++------
 src/fs/stackable/fuse_overlay.rs |  53 +++++++----
 src/fs/stackable/mod.rs          |   2 +-
 src/fs/stackable/overlay.rs      |   3 +-
 src/main.rs                      |   2 +-
 5 files changed, 161 insertions(+), 49 deletions(-)

diff --git a/src/fs/mount.rs b/src/fs/mount.rs
index b4f2a46..c9ead8b 100644
--- a/src/fs/mount.rs
+++ b/src/fs/mount.rs
@@ -7,13 +7,15 @@ use std::{
     ops::{Deref, DerefMut},
     path::PathBuf,
     process::Command,
+    slice::Iter,
     str::FromStr,
+    vec::IntoIter,
 };
 
 use crate::utils::DisplayString;
 
 use super::{
-    id_mapping::{ParseUntypedIdRangeError, UntypedIdRange},
+    id_mapping::{Delimiter, ParseUntypedIdRangeError, UntypedIdRange},
     permission::Permissions,
     FileSystem,
 };
@@ -374,18 +376,69 @@ impl From<UntypedIdRange> for GidRange {
     }
 }
 
-#[derive(Debug)]
-pub struct Options<T>(Vec<T>);
+// TODO: finish implementation to solve options delimiter problem
+#[derive(Clone, Debug)]
+pub struct DelimitedString {
+    inner: Vec<OsString>,
+    delimiter: char,
+}
 
-impl<T> Default for Options<T> {
-    fn default() -> Self {
-        Self(Vec::new())
+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,
+        }
+    }
+
+    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)
     }
 }
 
-impl<T> AsRef<Vec<T>> for Options<T> {
-    fn as_ref(&self) -> &Vec<T> {
-        &self.0
+impl AsRef<OsStr> for DelimitedString {
+    fn as_ref(&self) -> &OsStr {
+        &self.inner.join(self.delimiter)
+    }
+}
+
+impl Display for DelimitedString {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let mut iter = self.inner.iter();
+
+        if let Some(head) = iter.next() {
+            write!(f, "{}", head.display())?;
+
+            for item in iter {
+                write!(f, "{}", item.display())?;
+            }
+        }
+
+        Ok(())
+    }
+}
+
+#[derive(Debug)]
+pub struct Options<T> {
+    options: Vec<T>,
+    delimiter: char,
+}
+
+impl<T> Options<T> {
+    pub fn new(options: Vec<T>, delimiter: char) -> Self {
+        Self { options, delimiter }
     }
 }
 
@@ -393,25 +446,43 @@ impl<T> Deref for Options<T> {
     type Target = Vec<T>;
 
     fn deref(&self) -> &Self::Target {
-        &self.0
+        &self.options
     }
 }
 
 impl<T> DerefMut for Options<T> {
     fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
+        &mut self.options
+    }
+}
+
+impl<T> Default for Options<T> {
+    fn default() -> Self {
+        Self {
+            options: Default::default(),
+            delimiter: ' ',
+        }
+    }
+}
+
+impl<T> AsRef<Vec<T>> for Options<T> {
+    fn as_ref(&self) -> &Vec<T> {
+        &self.options
     }
 }
 
 impl<T: Clone> Clone for Options<T> {
     fn clone(&self) -> Self {
-        Self(self.0.clone())
+        Self {
+            options: self.options.clone(),
+            delimiter: self.delimiter,
+        }
     }
 }
 
 impl<T: PartialEq> PartialEq for Options<T> {
     fn eq(&self, other: &Self) -> bool {
-        self.0.eq(&other.0)
+        self.options.eq(&other.options) && self.delimiter.eq(&other.delimiter)
     }
 }
 
@@ -429,12 +500,12 @@ impl<T: FromStr> FromStr for Options<T> {
 
 impl<T: Display> Display for Options<T> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let mut iter = self.0.iter();
+        let mut iter = self.options.iter();
         if let Some(head) = iter.next() {
             write!(f, "{head}")?;
 
             for item in iter {
-                write!(f, ",{item}")?;
+                write!(f, "{}{item}", self.delimiter)?;
             }
         }
 
@@ -444,19 +515,41 @@ impl<T: Display> Display for Options<T> {
 
 impl<T> FromIterator<T> for Options<T> {
     fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
-        Self(iter.into_iter().collect())
+        Self {
+            options: iter.into_iter().collect(),
+            ..Default::default()
+        }
+    }
+}
+
+impl<T> IntoIterator for Options<T> {
+    type Item = T;
+
+    type IntoIter = IntoIter<T>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.options.into_iter()
+    }
+}
+impl<'a, T> IntoIterator for &'a Options<T> {
+    type Item = &'a T;
+
+    type IntoIter = Iter<'a, T>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.options.as_slice().into_iter()
     }
 }
 
 impl<T> From<Vec<T>> for Options<T> {
     fn from(value: Vec<T>) -> Self {
-        Self(value)
+        Self::from_iter(value)
     }
 }
 
 impl<T> From<T> for Options<T> {
     fn from(value: T) -> Self {
-        Self(vec![value])
+        Self::from_iter([value])
     }
 }
 
@@ -868,9 +961,9 @@ impl Mount {
 impl FileSystem for Mount {
     fn mount(&mut self) -> std::io::Result<()> {
         mount(
-            self.device_id,
+            self.device_id.clone(),
             &self.mountpoint,
-            self.options.map(Into::into),
+            self.options.clone(),
         )?;
         Ok(())
     }
@@ -881,13 +974,13 @@ impl FileSystem for Mount {
     }
 }
 
-pub fn mount<T: AsRef<MountOption>, I: IntoIterator<Item = T>>(
+pub fn mount<T: Into<MountOption>, I: IntoIterator<Item = T>>(
     source: impl Into<DeviceId>,
     target: impl Into<PathBuf>,
     options: I,
 ) -> std::io::Result<std::process::Output> {
     Command::new("mount")
-        .args(options.into_iter().map(|s| s.as_ref().to_string()))
+        .args(options.into_iter().map(|v| v.into().to_string()))
         .arg(source.into().to_string())
         .arg(target.into())
         .output()
@@ -1027,10 +1120,13 @@ mod tests {
 
         assert_eq!(
             opts,
-            Options(vec![
-                KeyValuePair::new("key", Some("value")),
-                KeyValuePair::new("keyword", None::<&str>)
-            ])
+            Options::new(
+                vec![
+                    KeyValuePair::new("key", Some("value")),
+                    KeyValuePair::new("keyword", None::<&str>)
+                ],
+                ','
+            )
         );
 
         assert_eq!(opts.to_string(), "key=value,keyword");
@@ -1153,7 +1249,7 @@ mod tests {
     #[test]
     fn mount_options() {
         Opts::from((["-L", "--label"].as_slice(), "LABEL=label"))
-            .assert(MountOption::Label(DeviceId::Label("label".to_string())));
+            .assert(MountOption::Label(DeviceId::Label("label".into())));
 
         Opts::from((["-t", "--types"].as_slice(), "nomsdos,smbs")).assert(MountOption::FsType(
             UncheckedOptions::from_iter(vec!["nomsdos", "smbs"]),
diff --git a/src/fs/stackable/fuse_overlay.rs b/src/fs/stackable/fuse_overlay.rs
index a04ffcd..d2bce01 100644
--- a/src/fs/stackable/fuse_overlay.rs
+++ b/src/fs/stackable/fuse_overlay.rs
@@ -278,7 +278,11 @@ impl_deref!(FuseOverlay(StackFs<FuseOverlayOption>));
 impl_deref_mut!(FuseOverlay(StackFs<FuseOverlayOption>));
 
 impl FuseOverlay {
-    pub fn new(
+    pub fn new(target: impl Into<PathBuf>) -> Self {
+        Self(StackFs::new(target))
+    }
+
+    pub fn readonly(
         target: impl Into<PathBuf>,
         lowerdir: impl IntoIterator<Item = impl Into<PathBuf>>,
     ) -> Self {
@@ -286,6 +290,26 @@ impl FuseOverlay {
         fs.push_option(FuseOverlayOption::LowerDir(LowerDirs::from_iter(lowerdir)));
         Self(fs)
     }
+
+    pub fn writable(
+        target: impl Into<PathBuf>,
+        lowerdir: impl IntoIterator<Item = impl Into<PathBuf>>,
+        upperdir: impl Into<PathBuf>,
+    ) -> Self {
+        let mut fs = StackFs::new(target);
+
+        fs.extend_options([
+            FuseOverlayOption::LowerDir(LowerDirs::from_iter(lowerdir)),
+            FuseOverlayOption::UpperDir(upperdir.into()),
+        ]);
+
+        Self(fs)
+    }
+
+    pub fn set_workdir(&mut self, workdir: impl Into<PathBuf>) -> &mut Self {
+        self.push_option(FuseOverlayOption::WorkDir(workdir.into()));
+        self
+    }
 }
 
 impl Stack for FuseOverlay {
@@ -362,7 +386,7 @@ mod tests {
         assert_eq!(one.to_string(), "/lower");
 
         let two = LowerDirs::from_iter(["/first", "/second"]);
-        assert_eq!(two.to_string(), "/first,/second");
+        assert_eq!(two.to_string(), "/first:/second");
 
         let from_str_none = LowerDirs::from_str("").unwrap();
         assert_eq!(from_str_none, empty);
@@ -370,7 +394,7 @@ mod tests {
         let from_str_one = LowerDirs::from_str("/lower").unwrap();
         assert_eq!(from_str_one, one);
 
-        let from_str_two = LowerDirs::from_str("/first,/second").unwrap();
+        let from_str_two = LowerDirs::from_str("/first:/second").unwrap();
         assert_eq!(from_str_two, two);
     }
 
@@ -441,36 +465,29 @@ mod tests {
         let basic = FuseOverlay::new("/mnt");
         assert_eq!(basic.to_string(), "fuse-overlayfs /mnt");
 
-        let mut with_options = FuseOverlay::new("~/.config");
-        with_options.extend_options([
-            FuseOverlayOption::NoAcl,
-            LowerDirs::from_iter(["~/.themes", "~/.colors"]).into(),
-        ]);
+        let mut with_options = FuseOverlay::readonly("~/.config", ["~/.themes", "~/.colors"]);
+        with_options.push_option(FuseOverlayOption::NoAcl);
 
         assert_eq!(
             with_options.to_string(),
-            "fuse-overlayfs -o noacl -o lowerdir=~/.themes,~/.colors ~/.config"
+            "fuse-overlayfs -olowerdir=~/.themes:~/.colors -onoacl ~/.config"
         );
     }
 
     #[test]
     fn mountpoint_trait_impl() {
-        let mut all = FuseOverlay::new("/mnt");
+        let mut all = FuseOverlay::writable("/mnt", ["/doesnt", "/matter"], "/upper");
 
-        all.extend_options([
-            LowerDirs::from_iter(["/doesnt", "/matter"]).into(),
-            FuseOverlayOption::UpperDir("/upper".into()),
-            FuseOverlayOption::WorkDir("/work".into()),
-        ]);
+        all.set_workdir("/work");
 
         let opts: Vec<String> = all.options().map(|x| x.to_string()).collect();
 
         assert_eq!(
             opts,
             [
-                "-o lowerdir=/doesnt,/matter",
-                "-o upperdir=/upper",
-                "-o workdir=/work"
+                "-olowerdir=/doesnt:/matter",
+                "-oupperdir=/upper",
+                "-oworkdir=/work"
             ]
         );
     }
diff --git a/src/fs/stackable/mod.rs b/src/fs/stackable/mod.rs
index 8e435d6..345bd70 100644
--- a/src/fs/stackable/mod.rs
+++ b/src/fs/stackable/mod.rs
@@ -67,7 +67,7 @@ impl FromStr for LowerDirs {
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         Ok(Self(
-            s.split(',')
+            s.split(':')
                 .filter(|s| !s.is_empty())
                 .map(Into::into)
                 .collect(),
diff --git a/src/fs/stackable/overlay.rs b/src/fs/stackable/overlay.rs
index b0ab85b..9527b57 100644
--- a/src/fs/stackable/overlay.rs
+++ b/src/fs/stackable/overlay.rs
@@ -1,4 +1,3 @@
-use std::ffi::OsString;
 use std::fmt::Display;
 use std::path::PathBuf;
 use std::str::FromStr;
@@ -277,7 +276,7 @@ impl Stack for Overlay {
 impl FileSystem for Overlay {
     fn mount(&mut self) -> std::io::Result<()> {
         let opts = self.options.iter().map(ToString::to_string);
-        let options = MountOption::Options(UncheckedOptions::from_iter(opts));
+        let options = [MountOption::Options(UncheckedOptions::from_iter(opts))];
         mount("overlay", &self.mountpoint, options)?;
         Ok(())
     }
diff --git a/src/main.rs b/src/main.rs
index 0423a7a..9a39bc8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,7 +2,7 @@ use vahanafs::fs::FileSystem;
 use vahanafs::prelude::*;
 
 fn main() {
-    let mut fs = FuseOverlay::new("/home/rowan/vfs/mnt", ["/home/rowan/vfs/lower1"]);
+    let mut fs = FuseOverlay::readonly("/home/rowan/vfs/mnt", ["/home/rowan/vfs/lower1"]);
     println!("{fs}");
     fs.mount().expect("could not mount overlayfs");
 }