{-# OPTIONS_GHC -fno-warn-orphans #-}

-- |
-- Module: Filesystem.Path
-- Copyright: 2010 John Millikin
-- License: MIT
--
-- Maintainer:  jmillikin@gmail.com
-- Portability:  portable
--
-- High‐level, byte‐based file and directory path
-- manipulations. You probably want to import "Filesystem.Path.CurrentOS"
-- instead, since it handles detecting which rules to use in the current
-- compilation.
--
module Filesystem.Path
  ( FilePath
  , empty

  -- * Basic properties
  , null
  , root
  , directory
  , parent
  , filename
  , dirname
  , basename
  , absolute
  , relative

  -- * Basic operations
  , append
  , (</>)
  , concat
  , commonPrefix
  , stripPrefix
  , collapse
  , splitDirectories

  -- * Extensions
  , extension
  , extensions
  , hasExtension

  , addExtension
  , (<.>)
  , dropExtension
  , replaceExtension

  , addExtensions
  , dropExtensions
  , replaceExtensions

  , splitExtension
  , splitExtensions
  ) where

import           Prelude hiding (FilePath, concat, null)
import qualified Prelude as Prelude

import           Data.List (foldl')
import           Data.Maybe (isJust, isNothing)
import qualified Data.Semigroup as Sem
import qualified Data.Monoid as M
import qualified Data.Text as T

import           Filesystem.Path.Internal

instance Sem.Semigroup FilePath where
  <> :: FilePath -> FilePath -> FilePath
(<>) = FilePath -> FilePath -> FilePath
append

instance M.Monoid FilePath where
  mempty :: FilePath
mempty = FilePath
empty
  mappend :: FilePath -> FilePath -> FilePath
mappend = FilePath -> FilePath -> FilePath
append
  mconcat :: [FilePath] -> FilePath
mconcat = [FilePath] -> FilePath
concat

-------------------------------------------------------------------------------
-- Basic properties
-------------------------------------------------------------------------------

-- | @null p = (p == 'empty')@
null :: FilePath -> Bool
null :: FilePath -> Bool
null = (FilePath -> FilePath -> Bool
forall a. Eq a => a -> a -> Bool
== FilePath
empty)

-- | Retrieves the 'FilePath'&#x2019;s root.
root :: FilePath -> FilePath
root :: FilePath -> FilePath
root FilePath
p = FilePath
empty { pathRoot = pathRoot p }

-- | Retrieves the 'FilePath'&#x2019;s directory. If the path is already a
-- directory, it is returned unchanged.
directory :: FilePath -> FilePath
directory :: FilePath -> FilePath
directory FilePath
p = FilePath
empty
  { pathRoot = pathRoot p
  , pathDirectories = let
    dot' | Maybe Root -> Bool
forall a. Maybe a -> Bool
isJust (FilePath -> Maybe Root
pathRoot FilePath
p) = []
         | [Directory] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
Prelude.null (FilePath -> [Directory]
pathDirectories FilePath
p) = [Directory
dot]
         | Bool
otherwise = []
    in dot' ++ pathDirectories p
  }

-- | Retrieves the 'FilePath'&#x2019;s parent directory.
parent :: FilePath -> FilePath
parent :: FilePath -> FilePath
parent FilePath
p = FilePath
empty
  { pathRoot = pathRoot p
  , pathDirectories = let
    starts = (Directory -> Maybe Directory) -> [Directory] -> [Maybe Directory]
forall a b. (a -> b) -> [a] -> [b]
map Directory -> Maybe Directory
forall a. a -> Maybe a
Just [Directory
dot, Directory
dots]
    directories = if FilePath -> Bool
null (FilePath -> FilePath
filename FilePath
p)
      then [Directory] -> [Directory]
forall a. [a] -> [a]
safeInit (FilePath -> [Directory]
pathDirectories FilePath
p)
      else FilePath -> [Directory]
pathDirectories FilePath
p

    dot' | [Directory] -> Maybe Directory
forall a. [a] -> Maybe a
safeHead [Directory]
directories Maybe Directory -> [Maybe Directory] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Maybe Directory]
starts = []
         | Maybe Root -> Bool
forall a. Maybe a -> Bool
isNothing (FilePath -> Maybe Root
pathRoot FilePath
p) = [Directory
dot]
         | Bool
otherwise = []
    in dot' ++ directories
  }

-- | Retrieve a 'FilePath'&#x2019;s filename component.
--
-- @
-- filename \"foo\/bar.txt\" == \"bar.txt\"
-- @
filename :: FilePath -> FilePath
filename :: FilePath -> FilePath
filename FilePath
p = FilePath
empty
  { pathBasename = pathBasename p
  , pathExtensions = pathExtensions p
  }

-- | Retrieve a 'FilePath'&#x2019;s directory name. This is only the
-- /file name/ of the directory, not its full path.
--
-- @
-- dirname \"foo\/bar\/baz.txt\" == \"bar\"
-- dirname \"/\" == \"\"
-- @
--
-- Since: 0.4.1
dirname :: FilePath -> FilePath
dirname :: FilePath -> FilePath
dirname FilePath
p = case [Directory] -> [Directory]
forall a. [a] -> [a]
reverse (FilePath -> [Directory]
pathDirectories FilePath
p) of
  [] -> Maybe Root
-> [Directory] -> Maybe Directory -> [Directory] -> FilePath
FilePath Maybe Root
forall a. Maybe a
Nothing [] Maybe Directory
forall a. Maybe a
Nothing []
  (Directory
d:[Directory]
_) -> case Directory -> (Maybe Directory, [Directory])
parseFilename Directory
d of
    (Maybe Directory
base, [Directory]
exts) -> Maybe Root
-> [Directory] -> Maybe Directory -> [Directory] -> FilePath
FilePath Maybe Root
forall a. Maybe a
Nothing [] Maybe Directory
base [Directory]
exts

-- | Retrieve a 'FilePath'&#x2019;s basename component.
--
-- @
-- basename \"foo/bar.txt\" == \"bar\"
-- @
basename :: FilePath -> FilePath
basename :: FilePath -> FilePath
basename FilePath
p = FilePath
empty
  { pathBasename = pathBasename p
  }

-- | Test whether a path is absolute.
absolute :: FilePath -> Bool
absolute :: FilePath -> Bool
absolute FilePath
p = case FilePath -> Maybe Root
pathRoot FilePath
p of
  Just Root
RootPosix -> Bool
True
  Just RootWindowsVolume{} -> Bool
True
  Just Root
RootWindowsCurrentVolume -> Bool
False
  Just RootWindowsUnc{} -> Bool
True
  Just Root
RootWindowsDoubleQMark -> Bool
True
  Maybe Root
Nothing -> Bool
False

-- | Test whether a path is relative.
relative :: FilePath -> Bool
relative :: FilePath -> Bool
relative FilePath
p = case FilePath -> Maybe Root
pathRoot FilePath
p of
  Just Root
_ -> Bool
False
  Maybe Root
_ -> Bool
True

-------------------------------------------------------------------------------
-- Basic operations
-------------------------------------------------------------------------------

-- | Appends two 'FilePath's. If the second path is absolute, it is returned
-- unchanged.
append :: FilePath -> FilePath -> FilePath
append :: FilePath -> FilePath -> FilePath
append FilePath
x FilePath
y = FilePath
cased where
  cased :: FilePath
cased = case FilePath -> Maybe Root
pathRoot FilePath
y of
    Just Root
RootPosix -> FilePath
y
    Just RootWindowsVolume{} -> FilePath
y
    Just Root
RootWindowsCurrentVolume -> case FilePath -> Maybe Root
pathRoot FilePath
x of
      Just RootWindowsVolume{} -> FilePath
y { pathRoot = pathRoot x }
      Maybe Root
_ -> FilePath
y
    Just RootWindowsUnc{} -> FilePath
y
    Just Root
RootWindowsDoubleQMark -> FilePath
y
    Maybe Root
Nothing -> FilePath
xy
  xy :: FilePath
xy = FilePath
y
    { pathRoot = pathRoot x
    , pathDirectories = directories
    }
  directories :: [Directory]
directories = [Directory]
xDirectories [Directory] -> [Directory] -> [Directory]
forall a. [a] -> [a] -> [a]
++ FilePath -> [Directory]
pathDirectories FilePath
y
  xDirectories :: [Directory]
xDirectories = (FilePath -> [Directory]
pathDirectories FilePath
x [Directory] -> [Directory] -> [Directory]
forall a. [a] -> [a] -> [a]
++) ([Directory] -> [Directory]) -> [Directory] -> [Directory]
forall a b. (a -> b) -> a -> b
$ if FilePath -> Bool
null (FilePath -> FilePath
filename FilePath
x)
    then []
    else [FilePath -> Directory
filenameChunk FilePath
x]

-- | An alias for 'append'.
(</>) :: FilePath -> FilePath -> FilePath
</> :: FilePath -> FilePath -> FilePath
(</>) = FilePath -> FilePath -> FilePath
append

-- | A fold over 'append'.
concat :: [FilePath] -> FilePath
concat :: [FilePath] -> FilePath
concat [] = FilePath
empty
concat [FilePath]
ps = (FilePath -> FilePath -> FilePath) -> [FilePath] -> FilePath
forall a. (a -> a -> a) -> [a] -> a
forall (t :: * -> *) a. Foldable t => (a -> a -> a) -> t a -> a
foldr1 FilePath -> FilePath -> FilePath
append [FilePath]
ps

-- | Find the greatest common prefix between a list of 'FilePath's.
commonPrefix :: [FilePath] -> FilePath
commonPrefix :: [FilePath] -> FilePath
commonPrefix [] = FilePath
empty
commonPrefix [FilePath]
ps = (FilePath -> FilePath -> FilePath) -> [FilePath] -> FilePath
forall a. (a -> a -> a) -> [a] -> a
forall (t :: * -> *) a. Foldable t => (a -> a -> a) -> t a -> a
foldr1 FilePath -> FilePath -> FilePath
step [FilePath]
ps where
  step :: FilePath -> FilePath -> FilePath
step FilePath
x FilePath
y = if FilePath -> Maybe Root
pathRoot FilePath
x Maybe Root -> Maybe Root -> Bool
forall a. Eq a => a -> a -> Bool
/= FilePath -> Maybe Root
pathRoot FilePath
y
    then FilePath
empty
    else let cs :: [Directory]
cs = FilePath -> FilePath -> [Directory]
commonDirectories FilePath
x FilePath
y in
      if [Directory]
cs [Directory] -> [Directory] -> Bool
forall a. Eq a => a -> a -> Bool
/= FilePath -> [Directory]
pathDirectories FilePath
x Bool -> Bool -> Bool
|| FilePath -> Maybe Directory
pathBasename FilePath
x Maybe Directory -> Maybe Directory -> Bool
forall a. Eq a => a -> a -> Bool
/= FilePath -> Maybe Directory
pathBasename FilePath
y
        then FilePath
empty { pathRoot = pathRoot x, pathDirectories = cs }
        else let exts :: [Directory]
exts = FilePath -> FilePath -> [Directory]
commonExtensions FilePath
x FilePath
y in
          FilePath
x { pathExtensions = exts }

  commonDirectories :: FilePath -> FilePath -> [Directory]
commonDirectories FilePath
x FilePath
y = [Directory] -> [Directory] -> [Directory]
forall {a}. Eq a => [a] -> [a] -> [a]
common (FilePath -> [Directory]
pathDirectories FilePath
x) (FilePath -> [Directory]
pathDirectories FilePath
y)
  commonExtensions :: FilePath -> FilePath -> [Directory]
commonExtensions FilePath
x FilePath
y = [Directory] -> [Directory] -> [Directory]
forall {a}. Eq a => [a] -> [a] -> [a]
common (FilePath -> [Directory]
pathExtensions FilePath
x) (FilePath -> [Directory]
pathExtensions FilePath
y)

  common :: [a] -> [a] -> [a]
common [] [a]
_ = []
  common [a]
_ [] = []
  common (a
x:[a]
xs) (a
y:[a]
ys) = if a
x a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
y
    then a
x a -> [a] -> [a]
forall a. a -> [a] -> [a]
: [a] -> [a] -> [a]
common [a]
xs [a]
ys
    else []

-- | Remove a prefix from a path.
--
-- @
-- 'stripPrefix' \"\/foo\/\" \"\/foo\/bar\/baz.txt\" == Just \"bar\/baz.txt\"
-- 'stripPrefix' \"\/foo\/\" \"\/bar\/baz.txt\" == Nothing
-- @
--
-- This function operates on logical prefixes, rather than by counting
-- characters. The prefix @\"\/foo\/bar\/baz\"@ is interpreted the path
-- @(\"\/foo\/bar\/\", \"baz\")@, and will be stripped accordingly:
--
-- @
-- 'stripPrefix' \"\/foo\/bar\/baz\" \"\/foo\/bar\/baz\/qux\" == Nothing
-- 'stripPrefix' \"\/foo\/bar\/baz\" \"\/foo\/bar\/baz.txt\" == Just \".txt\"
-- @
--
-- Since: 0.4.1
stripPrefix :: FilePath -> FilePath -> Maybe FilePath
stripPrefix :: FilePath -> FilePath -> Maybe FilePath
stripPrefix FilePath
x FilePath
y = if FilePath -> Maybe Root
pathRoot FilePath
x Maybe Root -> Maybe Root -> Bool
forall a. Eq a => a -> a -> Bool
/= FilePath -> Maybe Root
pathRoot FilePath
y
  then case FilePath -> Maybe Root
pathRoot FilePath
x of
    Maybe Root
Nothing -> FilePath -> Maybe FilePath
forall a. a -> Maybe a
Just FilePath
y
    Just Root
_ -> Maybe FilePath
forall a. Maybe a
Nothing
  else do
    dirs <- [Directory] -> [Directory] -> Maybe [Directory]
forall a. Eq a => [a] -> [a] -> Maybe [a]
strip (FilePath -> [Directory]
pathDirectories FilePath
x) (FilePath -> [Directory]
pathDirectories FilePath
y)
    case dirs of
      [] -> case (FilePath -> Maybe Directory
pathBasename FilePath
x, FilePath -> Maybe Directory
pathBasename FilePath
y) of
        (Maybe Directory
Nothing, Maybe Directory
Nothing) -> do
          exts <- [Directory] -> [Directory] -> Maybe [Directory]
forall a. Eq a => [a] -> [a] -> Maybe [a]
strip (FilePath -> [Directory]
pathExtensions FilePath
x) (FilePath -> [Directory]
pathExtensions FilePath
y)
          return (y { pathRoot = Nothing, pathDirectories = dirs, pathExtensions = exts })
        (Maybe Directory
Nothing, Just Directory
_) -> case FilePath -> [Directory]
pathExtensions FilePath
x of
          [] -> FilePath -> Maybe FilePath
forall a. a -> Maybe a
Just (FilePath
y { pathRoot = Nothing, pathDirectories = dirs })
          [Directory]
_ -> Maybe FilePath
forall a. Maybe a
Nothing
        (Just Directory
x_b, Just Directory
y_b) | Directory
x_b Directory -> Directory -> Bool
forall a. Eq a => a -> a -> Bool
== Directory
y_b -> do
          exts <- [Directory] -> [Directory] -> Maybe [Directory]
forall a. Eq a => [a] -> [a] -> Maybe [a]
strip (FilePath -> [Directory]
pathExtensions FilePath
x) (FilePath -> [Directory]
pathExtensions FilePath
y)
          return (empty { pathExtensions = exts })
        (Maybe Directory, Maybe Directory)
_ -> Maybe FilePath
forall a. Maybe a
Nothing
      [Directory]
_ -> case (FilePath -> Maybe Directory
pathBasename FilePath
x, FilePath -> [Directory]
pathExtensions FilePath
x) of
        (Maybe Directory
Nothing, []) -> FilePath -> Maybe FilePath
forall a. a -> Maybe a
Just (FilePath
y { pathRoot = Nothing, pathDirectories = dirs })
        (Maybe Directory, [Directory])
_ -> Maybe FilePath
forall a. Maybe a
Nothing

strip :: Eq a => [a] -> [a] -> Maybe [a]
strip :: forall a. Eq a => [a] -> [a] -> Maybe [a]
strip [] [a]
ys = [a] -> Maybe [a]
forall a. a -> Maybe a
Just [a]
ys
strip [a]
_ [] = Maybe [a]
forall a. Maybe a
Nothing
strip (a
x:[a]
xs) (a
y:[a]
ys) = if a
x a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
y
  then [a] -> [a] -> Maybe [a]
forall a. Eq a => [a] -> [a] -> Maybe [a]
strip [a]
xs [a]
ys
  else Maybe [a]
forall a. Maybe a
Nothing

-- | Remove intermediate @\".\"@ and @\"..\"@ directories from a path.
--
-- @
-- 'collapse' \"\/foo\/.\/bar\" == \"\/foo\/bar\"
-- 'collapse' \"\/foo\/bar\/..\/baz\" == \"\/foo\/baz\"
-- 'collapse' \"\/foo\/..\/..\/bar\" == \"\/bar\"
-- 'collapse' \".\/foo\/bar\" == \".\/foo\/baz\"
-- @
--
-- Note that if any of the elements are symbolic links, 'collapse' may change
-- which file the path resolves to.
--
-- Since: 0.2
collapse :: FilePath -> FilePath
collapse :: FilePath -> FilePath
collapse FilePath
p = FilePath
p { pathDirectories = newDirs } where
  newDirs :: [Directory]
newDirs = case FilePath -> Maybe Root
pathRoot FilePath
p of
    Maybe Root
Nothing -> [Directory] -> [Directory]
forall a. [a] -> [a]
reverse [Directory]
revNewDirs
    Just Root
_ -> (Directory -> Bool) -> [Directory] -> [Directory]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (\Directory
x -> Directory
x Directory -> Directory -> Bool
forall a. Eq a => a -> a -> Bool
== Directory
dot Bool -> Bool -> Bool
|| Directory
x Directory -> Directory -> Bool
forall a. Eq a => a -> a -> Bool
== Directory
dots) ([Directory] -> [Directory]
forall a. [a] -> [a]
reverse [Directory]
revNewDirs)
  (Bool
_, [Directory]
revNewDirs) = ((Bool, [Directory]) -> Directory -> (Bool, [Directory]))
-> (Bool, [Directory]) -> [Directory] -> (Bool, [Directory])
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' (Bool, [Directory]) -> Directory -> (Bool, [Directory])
step (Bool
True, []) (FilePath -> [Directory]
pathDirectories FilePath
p)

  step :: (Bool, [Directory]) -> Directory -> (Bool, [Directory])
step (Bool
True, [Directory]
acc) Directory
c = (Bool
False, Directory
cDirectory -> [Directory] -> [Directory]
forall a. a -> [a] -> [a]
:[Directory]
acc)
  step (Bool
_, [Directory]
acc) Directory
c | Directory
c Directory -> Directory -> Bool
forall a. Eq a => a -> a -> Bool
== Directory
dot = (Bool
False, [Directory]
acc)
  step (Bool
_, [Directory]
acc) Directory
c | Directory
c Directory -> Directory -> Bool
forall a. Eq a => a -> a -> Bool
== Directory
dots = case [Directory]
acc of
    [] -> (Bool
False, Directory
cDirectory -> [Directory] -> [Directory]
forall a. a -> [a] -> [a]
:[Directory]
acc)
    (Directory
h:[Directory]
ts) | Directory
h Directory -> Directory -> Bool
forall a. Eq a => a -> a -> Bool
== Directory
dot -> (Bool
False, Directory
cDirectory -> [Directory] -> [Directory]
forall a. a -> [a] -> [a]
:[Directory]
ts)
           | Directory
h Directory -> Directory -> Bool
forall a. Eq a => a -> a -> Bool
== Directory
dots -> (Bool
False, Directory
cDirectory -> [Directory] -> [Directory]
forall a. a -> [a] -> [a]
:[Directory]
acc)
           | Bool
otherwise -> (Bool
False, [Directory]
ts)
  step (Bool
_, [Directory]
acc) Directory
c = (Bool
False, Directory
cDirectory -> [Directory] -> [Directory]
forall a. a -> [a] -> [a]
:[Directory]
acc)

-- | expand a FilePath into a list of the root name, directories, and file name
--
-- Since: 0.4.7
splitDirectories :: FilePath -> [FilePath]
splitDirectories :: FilePath -> [FilePath]
splitDirectories FilePath
p = [FilePath]
rootName [FilePath] -> [FilePath] -> [FilePath]
forall a. [a] -> [a] -> [a]
++ [FilePath]
dirNames [FilePath] -> [FilePath] -> [FilePath]
forall a. [a] -> [a] -> [a]
++ [FilePath]
fileName where
  rootName :: [FilePath]
rootName = case FilePath -> Maybe Root
pathRoot FilePath
p of
    Maybe Root
Nothing -> []
    Maybe Root
r -> [FilePath
empty { pathRoot = r }]
  dirNames :: [FilePath]
dirNames = (Directory -> FilePath) -> [Directory] -> [FilePath]
forall a b. (a -> b) -> [a] -> [b]
map (\Directory
d -> FilePath
empty { pathDirectories = [d] }) (FilePath -> [Directory]
pathDirectories FilePath
p)
  fileName :: [FilePath]
fileName = case (FilePath -> Maybe Directory
pathBasename FilePath
p, FilePath -> [Directory]
pathExtensions FilePath
p) of
    (Maybe Directory
Nothing, []) -> []
    (Maybe Directory, [Directory])
_ -> [FilePath -> FilePath
filename FilePath
p]

-------------------------------------------------------------------------------
-- Extensions
-------------------------------------------------------------------------------

-- | Get a 'FilePath'&#x2019;s last extension, or 'Nothing' if it has no
-- extensions.
extension :: FilePath -> Maybe T.Text
extension :: FilePath -> Maybe Text
extension FilePath
p = case FilePath -> [Text]
extensions FilePath
p of
  [] -> Maybe Text
forall a. Maybe a
Nothing
  [Text]
es -> Text -> Maybe Text
forall a. a -> Maybe a
Just ([Text] -> Text
forall a. HasCallStack => [a] -> a
last [Text]
es)

-- | Get a 'FilePath'&#x2019;s full extension list.
extensions :: FilePath -> [T.Text]
extensions :: FilePath -> [Text]
extensions = (Directory -> Text) -> [Directory] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Directory -> Text
unescape' ([Directory] -> [Text])
-> (FilePath -> [Directory]) -> FilePath -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> [Directory]
pathExtensions


-- | Get whether a 'FilePath'&#x2019;s last extension is the predicate.
hasExtension :: FilePath -> T.Text -> Bool
hasExtension :: FilePath -> Text -> Bool
hasExtension FilePath
p Text
e = FilePath -> Maybe Text
extension FilePath
p Maybe Text -> Maybe Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text -> Maybe Text
forall a. a -> Maybe a
Just Text
e

-- | Append an extension to the end of a 'FilePath'.
addExtension :: FilePath -> T.Text -> FilePath
addExtension :: FilePath -> Text -> FilePath
addExtension FilePath
p Text
ext = FilePath -> [Text] -> FilePath
addExtensions FilePath
p [Text
ext]

-- | Append many extensions to the end of a 'FilePath'.
addExtensions :: FilePath -> [T.Text] -> FilePath
addExtensions :: FilePath -> [Text] -> FilePath
addExtensions FilePath
p [Text]
exts = FilePath
p { pathExtensions = newExtensions } where
  newExtensions :: [Directory]
newExtensions = FilePath -> [Directory]
pathExtensions FilePath
p [Directory] -> [Directory] -> [Directory]
forall a. [a] -> [a] -> [a]
++ (Text -> Directory) -> [Text] -> [Directory]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Directory
escape [Text]
exts

-- | An alias for 'addExtension'.
(<.>) :: FilePath -> T.Text -> FilePath
<.> :: FilePath -> Text -> FilePath
(<.>) = FilePath -> Text -> FilePath
addExtension

-- | Remove a 'FilePath'&#x2019;s last extension.
dropExtension :: FilePath -> FilePath
dropExtension :: FilePath -> FilePath
dropExtension FilePath
p = FilePath
p { pathExtensions = safeInit (pathExtensions p) }

-- | Remove all extensions from a 'FilePath'.
dropExtensions :: FilePath -> FilePath
dropExtensions :: FilePath -> FilePath
dropExtensions FilePath
p = FilePath
p { pathExtensions = [] }

-- | Replace a 'FilePath'&#x2019;s last extension.
replaceExtension :: FilePath -> T.Text -> FilePath
replaceExtension :: FilePath -> Text -> FilePath
replaceExtension = FilePath -> Text -> FilePath
addExtension (FilePath -> Text -> FilePath)
-> (FilePath -> FilePath) -> FilePath -> Text -> FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
dropExtension

-- | Remove all extensions from a 'FilePath', and replace them with a new
-- list.
replaceExtensions :: FilePath -> [T.Text] -> FilePath
replaceExtensions :: FilePath -> [Text] -> FilePath
replaceExtensions = FilePath -> [Text] -> FilePath
addExtensions (FilePath -> [Text] -> FilePath)
-> (FilePath -> FilePath) -> FilePath -> [Text] -> FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
dropExtensions

-- | @splitExtension p = ('dropExtension' p, 'extension' p)@
splitExtension :: FilePath -> (FilePath, Maybe T.Text)
splitExtension :: FilePath -> (FilePath, Maybe Text)
splitExtension FilePath
p = (FilePath -> FilePath
dropExtension FilePath
p, FilePath -> Maybe Text
extension FilePath
p)

-- | @splitExtensions p = ('dropExtensions' p, 'extensions' p)@
splitExtensions :: FilePath -> (FilePath, [T.Text])
splitExtensions :: FilePath -> (FilePath, [Text])
splitExtensions FilePath
p = (FilePath -> FilePath
dropExtensions FilePath
p, FilePath -> [Text]
extensions FilePath
p)

-------------------------------------------------------------------------------
-- Utils
-------------------------------------------------------------------------------

safeInit :: [a] -> [a]
safeInit :: forall a. [a] -> [a]
safeInit [a]
xs = case [a]
xs of
  [] -> []
  [a]
_ -> [a] -> [a]
forall a. HasCallStack => [a] -> [a]
init [a]
xs

safeHead :: [a] -> Maybe a
safeHead :: forall a. [a] -> Maybe a
safeHead [] = Maybe a
forall a. Maybe a
Nothing
safeHead (a
x:[a]
_) = a -> Maybe a
forall a. a -> Maybe a
Just a
x