{-# LANGUAGE CPP #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -Wno-redundant-constraints #-}

-- | Provides utilities for working with 'Path'.
--
-- @since 0.1
module FileSystem.Path
  ( -- * Pattern Synonym
    pattern MkPath,

    -- * QuasiQuoters
    absdirPathSep,
    absfilePathSep,
    reldirPathSep,
    relfilePathSep,

    -- * Operations
    (<</>>),
    addExtension,
    splitExtension,
    fileExtension,
    replaceExtension,

    -- * Parsing
    parseAbsDir,
    parseAbsFile,
    parseRelDir,
    parseRelFile,

    -- * Elimination
    toOsPath,

    -- * Re-exports
    Path,
    Abs,
    Rel,
    Dir,
    File,
  )
where

import Control.Monad.Catch (MonadThrow)
import Data.Bifunctor (Bifunctor (second))
import GHC.Stack (HasCallStack)
import Language.Haskell.TH.Quote
  ( QuasiQuoter
      ( QuasiQuoter,
        quoteDec,
        quoteExp,
        quotePat,
        quoteType
      ),
  )
import OsPath (Abs, Dir, File, Path, Rel)
import OsPath qualified as Path
import System.OsPath (OsPath)
import System.OsString.Internal.Types (OsString (OsString, getOsString))

-- | Pattern synonym for eliminating 'Path' to 'OsPath'.
--
-- @since 0.1
pattern MkPath :: OsPath -> Path b t
pattern $mMkPath :: forall {r} {b} {t}. Path b t -> (OsPath -> r) -> ((# #) -> r) -> r
MkPath p <- (toOsPath -> p)

{-# COMPLETE MkPath #-}

-- | Like 'Path.absdir', except it runs paths through a "replace function"
-- first. On unix, replaces @\\@ with @/@. On windows, does the opposite.
--
-- @since 0.1
absdirPathSep :: QuasiQuoter
absdirPathSep :: QuasiQuoter
absdirPathSep =
  QuasiQuoter
    { quoteExp :: FilePath -> Q Exp
quoteExp = QuasiQuoter
Path.absdir.quoteExp (FilePath -> Q Exp) -> (FilePath -> FilePath) -> FilePath -> Q Exp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes,
      quotePat :: FilePath -> Q Pat
quotePat = QuasiQuoter
Path.absdir.quotePat (FilePath -> Q Pat) -> (FilePath -> FilePath) -> FilePath -> Q Pat
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes,
      quoteType :: FilePath -> Q Type
quoteType = QuasiQuoter
Path.absdir.quoteType (FilePath -> Q Type)
-> (FilePath -> FilePath) -> FilePath -> Q Type
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes,
      quoteDec :: FilePath -> Q [Dec]
quoteDec = QuasiQuoter
Path.absdir.quoteDec (FilePath -> Q [Dec])
-> (FilePath -> FilePath) -> FilePath -> Q [Dec]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes
    }

-- | Like 'Path.absfile', except it runs paths through a "replace function"
-- first. On unix, replaces @\\@ with @/@. On windows, does the opposite.
--
-- @since 0.1
absfilePathSep :: QuasiQuoter
absfilePathSep :: QuasiQuoter
absfilePathSep =
  QuasiQuoter
    { quoteExp :: FilePath -> Q Exp
quoteExp = QuasiQuoter
Path.absfile.quoteExp (FilePath -> Q Exp) -> (FilePath -> FilePath) -> FilePath -> Q Exp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes,
      quotePat :: FilePath -> Q Pat
quotePat = QuasiQuoter
Path.absfile.quotePat (FilePath -> Q Pat) -> (FilePath -> FilePath) -> FilePath -> Q Pat
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes,
      quoteType :: FilePath -> Q Type
quoteType = QuasiQuoter
Path.absfile.quoteType (FilePath -> Q Type)
-> (FilePath -> FilePath) -> FilePath -> Q Type
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes,
      quoteDec :: FilePath -> Q [Dec]
quoteDec = QuasiQuoter
Path.absfile.quoteDec (FilePath -> Q [Dec])
-> (FilePath -> FilePath) -> FilePath -> Q [Dec]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes
    }

-- | Like 'Path.reldir', except it runs paths through a "replace function"
-- first. On unix, replaces @\\@ with @/@. On windows, does the opposite.
--
-- @since 0.1
reldirPathSep :: QuasiQuoter
reldirPathSep :: QuasiQuoter
reldirPathSep =
  QuasiQuoter
    { quoteExp :: FilePath -> Q Exp
quoteExp = QuasiQuoter
Path.reldir.quoteExp (FilePath -> Q Exp) -> (FilePath -> FilePath) -> FilePath -> Q Exp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes,
      quotePat :: FilePath -> Q Pat
quotePat = QuasiQuoter
Path.reldir.quotePat (FilePath -> Q Pat) -> (FilePath -> FilePath) -> FilePath -> Q Pat
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes,
      quoteType :: FilePath -> Q Type
quoteType = QuasiQuoter
Path.reldir.quoteType (FilePath -> Q Type)
-> (FilePath -> FilePath) -> FilePath -> Q Type
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes,
      quoteDec :: FilePath -> Q [Dec]
quoteDec = QuasiQuoter
Path.reldir.quoteDec (FilePath -> Q [Dec])
-> (FilePath -> FilePath) -> FilePath -> Q [Dec]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes
    }

-- | Like 'Path.relfile', except it runs paths through a "replace function"
-- first. On unix, replaces @\\@ with @/@. On windows, does the opposite.
--
-- @since 0.1
relfilePathSep :: QuasiQuoter
relfilePathSep :: QuasiQuoter
relfilePathSep =
  QuasiQuoter
    { quoteExp :: FilePath -> Q Exp
quoteExp = QuasiQuoter
Path.relfile.quoteExp (FilePath -> Q Exp) -> (FilePath -> FilePath) -> FilePath -> Q Exp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes,
      quotePat :: FilePath -> Q Pat
quotePat = QuasiQuoter
Path.relfile.quotePat (FilePath -> Q Pat) -> (FilePath -> FilePath) -> FilePath -> Q Pat
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes,
      quoteType :: FilePath -> Q Type
quoteType = QuasiQuoter
Path.relfile.quoteType (FilePath -> Q Type)
-> (FilePath -> FilePath) -> FilePath -> Q Type
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes,
      quoteDec :: FilePath -> Q [Dec]
quoteDec = QuasiQuoter
Path.relfile.quoteDec (FilePath -> Q [Dec])
-> (FilePath -> FilePath) -> FilePath -> Q [Dec]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
replaceSlashes
    }

{- ORMOLU_DISABLE -}

replaceSlashes :: FilePath -> FilePath
replaceSlashes :: FilePath -> FilePath
replaceSlashes = (Char -> FilePath -> FilePath) -> FilePath -> FilePath -> FilePath
forall a b. (a -> b -> b) -> b -> [a] -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr Char -> FilePath -> FilePath
go FilePath
""
  where
#if WINDOWS
    go '/' acc = '\\' : acc
#else
    go :: Char -> FilePath -> FilePath
go Char
'\\' FilePath
acc = Char
'/' Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
: FilePath
acc
#endif
    go Char
c FilePath
acc = Char
c Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
: FilePath
acc

{- ORMOLU_ENABLE -}

-- | 'Path' to 'OsPath'.
--
-- @since 0.1
toOsPath :: Path b t -> OsPath
toOsPath :: forall b t. Path b t -> OsPath
toOsPath = PlatformString -> OsPath
OsString (PlatformString -> OsPath)
-> (Path b t -> PlatformString) -> Path b t -> OsPath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Path b t -> PlatformString
forall b t. Path b t -> PlatformString
Path.toOsPath

-- | Like 'Path.parseAbsDir', but in terms of 'OsPath' rather than system
-- specific type.
--
-- @since 0.1
parseAbsDir :: (HasCallStack, MonadThrow m) => OsPath -> m (Path Abs Dir)
parseAbsDir :: forall (m :: * -> *).
(HasCallStack, MonadThrow m) =>
OsPath -> m (Path Abs Dir)
parseAbsDir = PlatformString -> m (Path Abs Dir)
forall (m :: * -> *).
MonadThrow m =>
PlatformString -> m (Path Abs Dir)
Path.parseAbsDir (PlatformString -> m (Path Abs Dir))
-> (OsPath -> PlatformString) -> OsPath -> m (Path Abs Dir)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (.getOsString)

-- | Like 'Path.parseAbsFile', but in terms of 'OsPath' rather than system
-- specific type.
--
-- @since 0.1
parseAbsFile :: (HasCallStack, MonadThrow m) => OsPath -> m (Path Abs File)
parseAbsFile :: forall (m :: * -> *).
(HasCallStack, MonadThrow m) =>
OsPath -> m (Path Abs File)
parseAbsFile = PlatformString -> m (Path Abs File)
forall (m :: * -> *).
MonadThrow m =>
PlatformString -> m (Path Abs File)
Path.parseAbsFile (PlatformString -> m (Path Abs File))
-> (OsPath -> PlatformString) -> OsPath -> m (Path Abs File)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (.getOsString)

-- | Like 'Path.parseRelDir', but in terms of 'OsPath' rather than system
-- specific type.
--
-- @since 0.1
parseRelDir :: (HasCallStack, MonadThrow m) => OsPath -> m (Path Rel Dir)
parseRelDir :: forall (m :: * -> *).
(HasCallStack, MonadThrow m) =>
OsPath -> m (Path Rel Dir)
parseRelDir = PlatformString -> m (Path Rel Dir)
forall (m :: * -> *).
MonadThrow m =>
PlatformString -> m (Path Rel Dir)
Path.parseRelDir (PlatformString -> m (Path Rel Dir))
-> (OsPath -> PlatformString) -> OsPath -> m (Path Rel Dir)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (.getOsString)

-- | Like 'Path.parseRelFile', but in terms of 'OsPath' rather than system
-- specific type.
--
-- @since 0.1
parseRelFile :: (HasCallStack, MonadThrow m) => OsPath -> m (Path Rel File)
parseRelFile :: forall (m :: * -> *).
(HasCallStack, MonadThrow m) =>
OsPath -> m (Path Rel File)
parseRelFile = PlatformString -> m (Path Rel File)
forall (m :: * -> *).
MonadThrow m =>
PlatformString -> m (Path Rel File)
Path.parseRelFile (PlatformString -> m (Path Rel File))
-> (OsPath -> PlatformString) -> OsPath -> m (Path Rel File)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (.getOsString)

-- | Alias for 'Path.</>', intended to allow unqualified usage alongside
-- 'OsPath''s @\</\>@.
--
-- @since 0.1
(<</>>) :: Path b Dir -> Path Rel t -> Path b t
<</>> :: forall b t. Path b Dir -> Path Rel t -> Path b t
(<</>>) = Path b Dir -> Path Rel t -> Path b t
forall b t. Path b Dir -> Path Rel t -> Path b t
(Path.</>)

infixr 5 <</>>

-- | Like 'Path.addExtension', but in terms of 'OsPath' rather than system
-- specific type.
--
-- @since 0.1
addExtension ::
  (HasCallStack, MonadThrow m) =>
  OsPath ->
  Path b File ->
  m (Path b File)
addExtension :: forall (m :: * -> *) b.
(HasCallStack, MonadThrow m) =>
OsPath -> Path b File -> m (Path b File)
addExtension OsPath
p = PlatformString -> Path b File -> m (Path b File)
forall (m :: * -> *) b.
MonadThrow m =>
PlatformString -> Path b File -> m (Path b File)
Path.addExtension OsPath
p.getOsString

-- | Like 'Path.splitExtension', but in terms of 'OsPath' rather than system
-- specific type.
--
-- @since 0.1
splitExtension ::
  (HasCallStack, MonadThrow m) =>
  Path b File ->
  m (Path b File, OsPath)
splitExtension :: forall (m :: * -> *) b.
(HasCallStack, MonadThrow m) =>
Path b File -> m (Path b File, OsPath)
splitExtension = ((Path b File, PlatformString) -> (Path b File, OsPath))
-> m (Path b File, PlatformString) -> m (Path b File, OsPath)
forall a b. (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((PlatformString -> OsPath)
-> (Path b File, PlatformString) -> (Path b File, OsPath)
forall b c a. (b -> c) -> (a, b) -> (a, c)
forall (p :: * -> * -> *) b c a.
Bifunctor p =>
(b -> c) -> p a b -> p a c
second PlatformString -> OsPath
OsString) (m (Path b File, PlatformString) -> m (Path b File, OsPath))
-> (Path b File -> m (Path b File, PlatformString))
-> Path b File
-> m (Path b File, OsPath)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Path b File -> m (Path b File, PlatformString)
forall (m :: * -> *) b.
MonadThrow m =>
Path b File -> m (Path b File, PlatformString)
Path.splitExtension

-- | Like 'Path.fileExtension', but in terms of 'OsPath' rather than system
-- specific type.
--
-- @since 0.1
fileExtension :: (HasCallStack, MonadThrow m) => Path b File -> m OsPath
fileExtension :: forall (m :: * -> *) b.
(HasCallStack, MonadThrow m) =>
Path b File -> m OsPath
fileExtension = (PlatformString -> OsPath) -> m PlatformString -> m OsPath
forall a b. (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap PlatformString -> OsPath
OsString (m PlatformString -> m OsPath)
-> (Path b File -> m PlatformString) -> Path b File -> m OsPath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Path b File -> m PlatformString
forall (m :: * -> *) b.
MonadThrow m =>
Path b File -> m PlatformString
Path.fileExtension

-- | Like 'Path.replaceExtension', but in terms of 'OsPath' rather than system
-- specific type.
--
-- @since 0.1
replaceExtension ::
  (HasCallStack, MonadThrow m) =>
  OsPath ->
  Path b File ->
  m (Path b File)
replaceExtension :: forall (m :: * -> *) b.
(HasCallStack, MonadThrow m) =>
OsPath -> Path b File -> m (Path b File)
replaceExtension OsPath
p = PlatformString -> Path b File -> m (Path b File)
forall (m :: * -> *) b.
MonadThrow m =>
PlatformString -> Path b File -> m (Path b File)
Path.replaceExtension OsPath
p.getOsString