dirbuf 0.1.2

reusable directory buffers
Documentation
#![cfg_attr(doc, doc = include_str!("../README.md"))]

#![cfg_attr(feature = "nightly-safe-impl", feature(os_string_truncate))]

#![warn(
    clippy::pedantic,
    clippy::undocumented_unsafe_blocks,
    clippy::unnecessary_safety_doc,
)]
#![allow(clippy::inline_always)]

use std::path::{Path, PathBuf};
use std::ffi::OsString;

mod inner;

pub use inner::{DirBuf, DirBufEntry};

macro_rules! impl_common {
    ($($x:tt)+) => {
        impl $($x)+ {
            /// Efficiently join a path with another.
            ///
            /// The returned [`DirBufEntry`] reuses the underlying buffer,
            /// and must be dropped before `join` can be called again.
            /// The `DirBufEntry` holds a mutable reference to ensure
            /// `join` cannot be called before the `DirBufEntry` is dropped.
            ///
            /// If the path segment is coming from untrusted user data and
            /// panics are undesirable, use [`try_join`](Self::try_join) instead.
            ///
            /// # Panics
            /// Panics if `path` is not a [trivial path](crate#trivial-paths).
            ///
            /// # Performance
            /// The overhead of the panic check is not insubstantial.
            /// When multiple `join`s are used, this is still usually
            /// faster than using a `PathBuf`, but if you know
            /// the path is trivial (e.g. if it is a string literal),
            /// consider using [`join_unchecked`](Self::join_unchecked) to remove this overhead.
            ///
            #[inline]
            #[must_use]
            pub fn join(&mut self, path: impl AsRef<Path>) -> DirBufEntry<'_> {
                match self.try_join_inner(path.as_ref()) {
                    Err(err) => panic!("unable to join paths: {err}"),
                    Ok(entry) => entry,
                }
            }

            /// Variant of `join` optimized for string literals.
            ///
            /// Semantic behavior is identical to [`Self::join`],
            /// the only difference is performance.
            ///
            /// For simple cases, this can have performance equivalent to
            /// that of [`Self::join_unchecked`] without the unsafety,
            /// but for more complex cases,
            /// [`Self::join`] may be more performant.
            ///
            /// It achieves this performance gain by leveraging inlining and
            /// constant folding.
            #[inline(always)]
            #[must_use]
            pub fn join_lit(&mut self, path: &str) -> DirBufEntry<'_> {
                #[cfg(unix)]
                {
                    if trivially_trivial(path) {
                        // SAFETY: just checked if it is trivial
                        return unsafe { self.join_inner(path.as_ref()) };
                    }
                }
                self.join(path)
            }

            /// Attempt to join a path with another.
            ///
            /// The returned [`DirBufEntry`] reuses the underlying buffer,
            /// and must be dropped before `join` can be called again.
            /// The `DirBufEntry` holds a mutable reference to ensure
            /// `join` cannot be called before the `DirBufEntry` is dropped.
            ///
            /// # Errors
            ///
            /// An error is returned if the path is not [trivial](crate#trivial-paths).
            #[inline]
            pub fn try_join(&mut self, path: impl AsRef<Path>) -> Result<DirBufEntry<'_>, JoinError> {
                self.try_join_inner(path.as_ref())
            }

            /// Join a path to another without checking that it is trivial.
            ///
            /// # Safety
            /// `path` must be a [trivial path](crate#trivial-paths).
            #[inline]
            #[must_use]
            pub unsafe fn join_unchecked(&mut self, path: impl AsRef<Path>) -> DirBufEntry<'_> {
                // SAFETY: invariant guranteed by caller
                unsafe { self.join_inner(path.as_ref()) }
            }

            #[inline]
            fn try_join_inner(&mut self, path: &Path) -> Result<DirBufEntry<'_>, JoinError> {
                check_trivial(path)?;
                // SAFETY: we just checked if the path is trivial
                unsafe { Ok(self.join_inner(path)) }
            }

            /// # Safety
            /// The path must be a [trivial path](crate#trivial-paths).
            #[must_use]
            unsafe fn join_inner(&mut self, path: &Path) -> DirBufEntry<'_> {
                
                // we call this before each operation instead of when `DirBufEntry` is dropped
                // to make sure it is leak-safe.
                // SAFETY: the path must be trivial by this function's precondition
                unsafe {self.push(path)};
                self.as_entry()
            }

            /// Borrow as a [`Path`].
            pub fn as_path(&self) -> &Path {
                
                self.as_ref()
            }
        }
    }
}

impl DirBuf {
    /// Initialize an empty `DirBuf` with the given capacity.
    #[must_use]
    pub fn with_capacity(capacity: usize) -> Self {
        let buf = PathBuf::with_capacity(capacity);
        DirBuf::new(buf)
    }

    /// Return the internal buffer, resetting it to its initial state first.
    #[must_use]
    pub fn into_inner(mut self) -> PathBuf {
        let mut r = PathBuf::new();
        // SAFETY: we consume the `DirBuf` so no further operations can be performed on it.
        std::mem::swap(unsafe { self.reset() }, &mut r);
        r
    }
}


impl<T: AsRef<Path>> PartialEq<T> for DirBuf {
    fn eq(&self, other: &T) -> bool {
        self.as_ref() == other.as_ref()
    }
}

impl<T: AsRef<Path>> PartialEq<T> for DirBufEntry<'_> {
    fn eq(&self, other: &T) -> bool {
        self.as_ref() == other.as_ref()
    }
}

impl_common!(DirBuf);
impl_common!(<'buf> DirBufEntry<'buf>);

/// An error that occurred while attempting to join two paths.
#[derive(thiserror::Error, Debug, Clone)]
#[error("path {path:?} is not trivial due to {component:?}")]
pub struct JoinError {
    path: PathBuf,
    component: OsString,
}

#[cold]
fn cold<T>(x: T) -> T {
    x
}

#[inline(always)]
const fn trivially_trivial(s: &str) -> bool {
    let mut i = 0;
    let bytes = s.as_bytes();
    if bytes.is_empty() { return true; }
    if bytes[0] == b'/' { return false; }
    while i < bytes.len() {
        if bytes[i] == b'.' {
            return false;
        }
        i += 1;
    }
    true
}

#[inline]
fn check_trivial(path: &Path) -> Result<(), JoinError> {
    use std::path::Component as Comp;
    for comp in path.components() {
        match &comp {
            Comp::CurDir | Comp::Normal(..) => {
                // ok, do nothing
            },
            _ => return cold(Err(JoinError{
                path: path.into(),
                component: comp.as_os_str().to_owned(),
            })),
        }
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let mut d = DirBuf::new("foo");
        assert_eq!(d.join("bar"), Path::new("foo/bar"));
        assert_eq!(d, "foo");
    }
}