{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Description: This module provides a type safe way to work with specific, well formed, components of a POSIX path.
module System.Filesystem.PathComponent
( PathComponent
, pathComponent
, isPathComponent
, getPC
, slashify
, splitPathComponents
) where
import Control.Monad.Fail
import qualified Data.ByteString as B
import Data.Maybe (fromMaybe)
import Data.Semigroup
import Data.String
import Prelude hiding (fail)
import System.Posix.FilePath
import Text.Printf
-- | The restricted subset of 'B.ByteString's that are valid names in a POSIX path.
newtype PathComponent = PC {
getPC :: B.ByteString -- ^ Get the 'B.ByteString' out of a 'PathComponent'
} deriving (Eq, Ord, Show, Semigroup)
instance IsString PathComponent where
fromString = fromMaybe (error "not a PathComponent") . pathComponent . fromString
-- | Safe constructor for 'PathComponent'
pathComponent :: MonadFail m => B.ByteString -> m PathComponent
pathComponent b | isPathComponent b = pure (PC b)
| otherwise = fail (printf "Not a valud PathComponent (%s)" (show b))
-- | True when 'pathComponent' will result in a Just
isPathComponent :: B.ByteString -> Bool
isPathComponent "" = False
isPathComponent b = B.all (`B.notElem` "\x00/") b
-- | Add a trailing @/@ unconditionally.
slashify :: PathComponent -> B.ByteString
slashify (PC p) = B.snoc p 0x2f
-- | A version of 'splitDirectories' for 'PathComponent's.
splitPathComponents :: MonadFail m => RawFilePath -> m [PathComponent]
splitPathComponents =
-- We can only get a Nothing when the future path component:
-- 1) contains a slash, which it can't due to coming from spliDirectories
-- 2) contains a Null byte, which is illegal in a path
-- and thus the path is not a valid RawFilePath
maybe (fail "path contained a null byte") pure . mapM pathComponent . splitDirectories