path-0.9.0: src/Path/Internal/Include.hs
-- This template expects CPP definitions for:
-- PLATFORM_NAME = Posix | Windows
-- IS_WINDOWS = False | True
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
-- | Internal types and functions.
module Path.Internal.PLATFORM_NAME
( Path(..)
, relRootFP
, toFilePath
, hasParentDir
)
where
import Control.DeepSeq (NFData (..))
import Data.Aeson (ToJSON (..), ToJSONKey(..))
import Data.Aeson.Types (toJSONKeyText)
import qualified Data.Text as T (pack)
import GHC.Generics (Generic)
import Data.Data
import Data.Hashable
import qualified Data.List as L
import qualified Language.Haskell.TH.Syntax as TH
import qualified System.FilePath.PLATFORM_NAME as FilePath
-- | Path of some base and type.
--
-- The type variables are:
--
-- * @b@ — base, the base location of the path; absolute or relative.
-- * @t@ — type, whether file or directory.
--
-- Internally is a string. The string can be of two formats only:
--
-- 1. File format: @file.txt@, @foo\/bar.txt@, @\/foo\/bar.txt@
-- 2. Directory format: @foo\/@, @\/foo\/bar\/@
--
-- All directories end in a trailing separator. There are no duplicate
-- path separators @\/\/@, no @..@, no @.\/@, no @~\/@, etc.
newtype Path b t = Path FilePath
deriving (Data, Typeable, Generic)
-- | String equality.
--
-- The following property holds:
--
-- @show x == show y ≡ x == y@
instance Eq (Path b t) where
(==) (Path x) (Path y) = x == y
-- | String ordering.
--
-- The following property holds:
--
-- @show x \`compare\` show y ≡ x \`compare\` y@
instance Ord (Path b t) where
compare (Path x) (Path y) = compare x y
-- | Normalized file path representation for the relative path root
relRootFP :: FilePath
relRootFP = '.' : [FilePath.pathSeparator]
-- | Convert to a 'FilePath' type.
--
-- All directories have a trailing slash, so if you want no trailing
-- slash, you can use 'System.FilePath.dropTrailingPathSeparator' from
-- the filepath package.
toFilePath :: Path b t -> FilePath
toFilePath (Path []) = relRootFP
toFilePath (Path x) = x
-- | Helper function: check if the filepath has any parent directories in it.
-- This handles the logic of checking for different path separators on Windows.
hasParentDir :: FilePath -> Bool
hasParentDir filepath' =
(filepath' == "..") ||
("/.." `L.isSuffixOf` filepath) ||
("/../" `L.isInfixOf` filepath) ||
("../" `L.isPrefixOf` filepath)
where
filepath =
case FilePath.pathSeparator of
'/' -> filepath'
x -> map (\y -> if x == y then '/' else y) filepath'
-- | Same as 'show . Path.toFilePath'.
--
-- The following property holds:
--
-- @x == y ≡ show x == show y@
instance Show (Path b t) where
show = show . toFilePath
instance NFData (Path b t) where
rnf (Path x) = rnf x
instance ToJSON (Path b t) where
toJSON = toJSON . toFilePath
{-# INLINE toJSON #-}
#if MIN_VERSION_aeson(0,10,0)
toEncoding = toEncoding . toFilePath
{-# INLINE toEncoding #-}
#endif
instance ToJSONKey (Path b t) where
toJSONKey = toJSONKeyText $ T.pack . toFilePath
instance Hashable (Path b t) where
-- A "." is represented as an empty string ("") internally. Hashing ""
-- results in a hash that is the same as the salt. To produce a more
-- reasonable hash we use "toFilePath" before hashing so that a "" gets
-- converted back to a ".".
hashWithSalt n path = hashWithSalt n (toFilePath path)
instance forall b t. (Typeable b, Typeable t) => TH.Lift (Path b t) where
lift (Path str) = do
let b = TH.ConT $ getTCName (Proxy :: Proxy b)
t = TH.ConT $ getTCName (Proxy :: Proxy t)
[|Path $(pure (TH.LitE (TH.StringL str))) :: Path $(pure b) $(pure t) |]
where
getTCName :: Typeable a => proxy a -> TH.Name
getTCName a = TH.Name occ flav
where
tc = typeRepTyCon (typeRep a)
occ = TH.OccName (tyConName tc)
flav = TH.NameG TH.TcClsName (TH.PkgName (tyConPackage tc)) (TH.ModName (tyConModule tc))
#if MIN_VERSION_template_haskell(2,17,0)
liftTyped = TH.unsafeCodeCoerce . TH.lift
#elif MIN_VERSION_template_haskell(2,16,0)
liftTyped = TH.unsafeTExpCoerce . TH.lift
#endif