spacetimedb_paths/
utils.rs

1use std::borrow::BorrowMut;
2use std::path::{Path, PathBuf};
3
4pub(crate) trait PathBufExt: BorrowMut<PathBuf> + Sized {
5    fn joined<P: AsRef<Path>>(mut self, path: P) -> Self {
6        self.borrow_mut().push(path);
7        self
8    }
9    fn with_exe_ext(mut self) -> Self {
10        self.borrow_mut().set_extension(std::env::consts::EXE_EXTENSION);
11        self
12    }
13    fn joined_int<I: itoa::Integer>(self, path_seg: I) -> Self {
14        self.joined(itoa::Buffer::new().format(path_seg))
15    }
16}
17
18impl PathBufExt for PathBuf {}
19
20/// Declares a new strongly-typed path newtype.
21///
22/// ```
23/// # use spacetimedb_paths::path_type;
24/// path_type! {
25///     /// optional docs
26///     // optional. if false, makes the type's constructor public.
27/// #   // TODO: replace cfg(any()) with cfg(false) once stabilized (rust-lang/rust#131204)
28///     #[non_exhaustive(any())]
29///     FooPath: dir // or file. adds extra utility methods for manipulating the file/dir
30/// }
31/// ```
32#[macro_export]
33macro_rules! path_type {
34    ($(#[doc = $doc:literal])* $(#[non_exhaustive($($non_exhaustive:tt)+)])? $name:ident) => {
35        $(#[doc = $doc])*
36        #[derive(Clone, Debug, $crate::__serde::Serialize, $crate::__serde::Deserialize)]
37        #[serde(transparent)]
38        #[cfg_attr(all($($($non_exhaustive)+)?), non_exhaustive)]
39        pub struct $name(pub std::path::PathBuf);
40
41        impl AsRef<std::path::Path> for $name {
42            #[inline]
43            fn as_ref(&self) -> &std::path::Path {
44                &self.0
45            }
46        }
47        impl AsRef<std::ffi::OsStr> for $name {
48            #[inline]
49            fn as_ref(&self) -> &std::ffi::OsStr {
50                self.0.as_ref()
51            }
52        }
53
54        impl From<std::ffi::OsString> for $name {
55            fn from(s: std::ffi::OsString) -> Self {
56                Self(s.into())
57            }
58        }
59
60        impl $crate::FromPathUnchecked for $name {
61            fn from_path_unchecked(path: impl Into<std::path::PathBuf>) -> Self {
62                Self(path.into())
63            }
64        }
65
66        impl $name {
67            #[inline]
68            pub fn display(&self) -> std::path::Display<'_> {
69                self.0.display()
70            }
71
72            #[inline]
73            pub fn metadata(&self) -> std::io::Result<std::fs::Metadata> {
74                self.0.metadata()
75            }
76        }
77    };
78    ($(#[$($attr:tt)+])* $name:ident: dir) => {
79        path_type!($(#[$($attr)+])* $name);
80        impl $name {
81            #[inline]
82            pub fn create(&self) -> std::io::Result<()> {
83                std::fs::create_dir_all(self)
84            }
85            #[inline]
86            pub fn read_dir(&self) -> std::io::Result<std::fs::ReadDir> {
87                self.0.read_dir()
88            }
89            #[inline]
90            pub fn is_dir(&self) -> bool {
91                self.0.is_dir()
92            }
93        }
94    };
95    ($(#[$($attr:tt)+])* $name:ident: file) => {
96        path_type!($(#[$($attr)+])* $name);
97        impl $name {
98            pub fn read(&self) -> std::io::Result<Vec<u8>> {
99                std::fs::read(self)
100            }
101
102            pub fn read_to_string(&self) -> std::io::Result<String> {
103                std::fs::read_to_string(self)
104            }
105
106            pub fn write(&self, contents: impl AsRef<[u8]>) -> std::io::Result<()> {
107                self.create_parent()?;
108                std::fs::write(self, contents)
109            }
110
111            /// Opens a file at this path with the given options, ensuring its parent directory exists.
112            #[inline]
113            pub fn open_file(&self, options: &std::fs::OpenOptions) -> std::io::Result<std::fs::File> {
114                self.create_parent()?;
115                options.open(self)
116            }
117
118            /// Create the parent directory of this path if it doesn't already exist.
119            #[inline]
120            pub fn create_parent(&self) -> std::io::Result<()> {
121                if let Some(parent) = self.0.parent() {
122                    if parent != std::path::Path::new("") {
123                        std::fs::create_dir_all(parent)?;
124                    }
125                }
126                Ok(())
127            }
128        }
129    };
130}
131pub(crate) use path_type;