packages feed

strong-path (empty) → 1.0.0.0

raw patch · 20 files changed

+1664/−0 lines, 20 filesdep +basedep +exceptionsdep +filepathsetup-changed

Dependencies added: base, exceptions, filepath, path, strong-path, tasty, tasty-discover, tasty-hspec, tasty-quickcheck, template-haskell

Files

+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for strong-path++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2020 wasp-lang++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,58 @@+# StrongPath++[![CI](https://github.com/wasp-lang/strong-path/workflows/CI/badge.svg?branch=master)](https://github.com/wasp-lang/strong-path/actions/workflows/ci.yaml?query=branch%3Amaster)+[![Hackage](https://img.shields.io/hackage/v/strong-path.svg)](https://hackage.haskell.org/package/strong-path)+[![Stackage LTS](http://stackage.org/package/strong-path/badge/lts)](http://stackage.org/lts/package/strong-path)+[![Stackage Nightly](http://stackage.org/package/strong-path/badge/nightly)](http://stackage.org/nightly/package/strong-path)++Strongly typed file paths in Haskell.++This library provides a strongly typed representation of file paths, providing more safety during compile time while also making code more readable, compared to the standard solution (`FilePath`, which is really just `String`).++Without `StrongPath`:+```hs+getGitConfigPath :: IO FilePath+generateHtmlFromMarkdown :: FilePath -> IO FilePath+```++With `StrongPath`:+```hs+getGitConfigPath :: IO (Path System (Rel HomeDir) (File GitConfigFile))+generateHtmlFromMarkdown :: Path System (Rel HomeDir) (File MarkdownFile) -> IO (Path System Abs (File HtmlFile))+```++Simple but complete example:+```hs+import StrongPath (Path, System, Abs, Rel, File, Dir, (</>), parseAbsDir)++data HomeDir++getHomeDirPath :: IO (Path System Abs (Dir HomeDir))+getHomeDirPath = getLine >>= fromJust . parseAbsDir+```++Check [documentation](https://hackage.haskell.org/package/strong-path/docs/StrongPath.html) for more details!++## Documentation+Detailed documentation, including rich examples and API is written via Haddock.++Check out the latest documentation on Hackage: [Documentation](https://hackage.haskell.org/package/strong-path/docs/StrongPath.html).++You can also build and view the Haddock documentation yourself if you wish, by running `stack haddock --open`.++## Contributing / development+We are using `ormolu` for code formatting. In order for the PR to pass, it needs to be formatted by `ormolu`.++`strong-path` is `Stack` project, so make sure you have `stack` installed on your machine.++`stack build` to build the project, `stack test` to run the tests.++`stack build --file-watch --haddock` to rebuild documentation as you change it.++### Publishing to Hackage++`stack sdist` to build publishable .tar.gz., and then we need to upload it manually.++Make sure to update the version of package in package.yaml.++We should also tag the commit in git with version tag (e.g. v1.0.0.0) so we know which version of code was used to produce that release.
+ Setup.hs view
@@ -0,0 +1,3 @@+import Distribution.Simple++main = defaultMain
+ src/StrongPath.hs view
@@ -0,0 +1,262 @@+module StrongPath+  ( -- * Overview++    -- | This library provides a strongly typed representation of file paths, providing more safety during compile time while also making code more readable, compared to the standard solution ("System.FilePath").+    --+    -- Example of using "System.FilePath" vs using "StrongPath" to describe the path to git config file (relative to the home directory):+    --+    -- > getGitConfigPath :: IO FilePath+    --+    -- > getGitConfigPath :: IO (Path System (Rel HomeDir) (File GitConfigFile))+    --+    -- Or, imagine stumbling onto this function:+    --+    -- > generateHtmlFromMarkdown :: FilePath -> IO FilePath+    --+    -- What kind of path does it take - relative, absolute? If relative, to what is it relative? What kind of path does it return? Do paths in question follow Posix or Windows standard?+    -- With "StrongPath", same function could look like this:+    --+    -- > generateHtmlFromMarkdown :: Path System (Rel HomeDir) (File MarkdownFile) -> IO (Path System Abs (File HtmlFile))+    --+    -- Basic idea is that working with 'FilePath' (which is just an alias for String+    -- and is a default type for representing file paths in Haskell) is too clumsy+    -- and can easily lead to errors in runtime, while those errors could have been caught+    -- in the compile time if more advanced approach for representing file paths was used.+    --+    -- This is where "StrongPath" with its 'Path' type comes in: by encoding+    -- more information about the file path into the type (e.g. is it relative or+    -- absolute, if it is relative what is it relative to, is it file or dir), we+    -- can achieve that additional safety and catch many potential errors during compile time,+    -- while also making code more readable.+    --+    -- Some examples:+    --+    --  - If you have absolute path to directory on the disk such as @\/home/\john\/Music@,+    --    with "StrongPath" you could represent it as @Path System Abs (Dir MusicDir)@,+    --    capturing its details in the type.+    --+    --  - If you have relative (to home) path to file on the disk such as @john\/.gitconfig@,+    --    you could represent it as @Path System (Rel HomeDir) (File JohnsGitConfigFile)@.+    --+    --  - If you have @..\/index.js@ path, coming from the Javascript import statement+    --    @import Stuff from \"..\/index.js\"@, you could represent it as+    --    @Path Posix (Rel ()) (File IndexFile)@.+    --+    --+    -- Notice that "StrongPath" will not allow you to, for example, represent @\/foo\/bar.txt@, which is an+    -- absolute path, as @Path System (Rel SomeDir) (File BarFile)@, because the parser function (in+    -- this case 'parseRelFile') will detect that path is absolute and not relative+    -- and will throw compile error.+    -- Therefore, due to the checks that parser functions perform,+    -- once you get 'FilePath' converted into 'Path', you can be pretty sure that it+    -- is exactly what the type says it is.+    --+    -- Once you have your file path represented as 'Path', you can perform safe operations like+    -- `</>` (concatenation of two paths) where types really shine.+    -- Specifically, `</>` will allow you to concatenate two paths only if they use the same standard,+    -- right path is relative to the left path and the left path is a directory.+    -- If these conditions are not satisfied, the code will not compile!++    -- ** Function naming++    -- | In "StrongPath" you will find groups of (usually 12) functions that all do the same thing really+    -- but each one of them is specialized for specific type of path.+    --+    -- In such case, we usually name them via following scheme: @\<function_name_prefix\>\<base\>\<type\>\<standard\>@, where+    --+    --  - @\<base\>@ can be @Rel@ or @Abs@.+    --  - @\<type\>@ can be @File@ or @Dir@.+    --  - @\<standard\>@ can be @P@ (Posix), @W@ (Windows) or nothing (System).+    --+    -- This results in 12 functions, for all 12 combinations of path type.+    --+    -- For example, from their name, we can say for the following functions that:+    --+    --  - @parseAbsFile@ does something with @Path System Abs (File f)@+    --  - @parseRelFileP@ does something with @Path Posix (Rel r) (File f)@+    --  - @parseRelDirW@ does something with @Path Windows (Rel r) (Dir d)@++    -- ** Common examples++    -- | Below we will go through most important features of "StrongPath" by going through some simple code examples that build upon each other.++    -- *** Typical import++    -- |+    -- > import StrongPath (Path, System, Abs, Rel, File, Dir, (</>))+    -- > import qualified StrongPath as SP++    -- *** Absolute path to home dir++    -- |+    -- Let's say that you want to ask user for absolute path to their home directory.+    -- With "StrongPath", you could do it like this:+    --+    -- > data HomeDir+    -- >+    -- > getHomeDirPath :: IO (Path System Abs (Dir HomeDir))+    -- > getHomeDirPath = getLine >>= fromJust . SP.parseAbsDir+    --+    -- Notice how you captured all the important information in type, plus+    -- you ensure it is indeed valid path by parsing it (with 'parseAbsDir')!+    --+    -- For the simplicity we didn't handle error properly and just used 'Data.Maybe.fromJust',+    -- but normally you would probably want to do something more fancy.++    -- *** Relative path to .gitconfig++    -- |+    -- Next, let's write a function that asks user for a relative path to .gitconfig file in their home directory.+    --+    -- > data UserGitConfig+    -- >+    -- > getUserGitConfigPath :: IO (Path System (Rel HomeDir) (File UserGitConfig))+    -- > getUserGitConfigPath = getLine >>= fromJust . SP.parseRelFile++    -- *** Absolute path to .gitconfig++    -- |+    -- If user inputed both abs path to home dir and rel path to .gitconfig, we can+    -- compute abs path to .gitconfig:+    --+    -- > absHomeDirPath <- getHomeDirPath+    -- > relGitConfigPath <- getUserGitConfigPath+    -- > let absGitConfigPath = absHomeDirPath </> relGitConfigPath+    --+    -- Cool thing here is that you can be sure that @absGitConfigPath@ makes sense, because '</>' would not allow+    -- you (at compile time) to concatenate @relGitConfigPath@ with anything else than path to home dir, since it knows+    -- that is what it is relative to!++    -- *** Copying .gitconfig++    -- |+    -- Let's say that for some reason, we want to copy this .gitconfig to home dir of another user,+    -- and we want it to have the same relative position in that home dir as it has in the current home dir.+    --+    -- Let's assume we already have+    --+    -- > anotherHomeDir :: IO (Path System Abs (Dir AnotherHomeDir))+    --+    -- then we can do smth like this:+    --+    -- > let absAnotherGitConfigPath = anotherHomeDir </> (SP.castRel relGitConfigPath)+    --+    -- We used 'castRel' to "loosen up" @relGitConfigPath@'s type, so it does not require to be relative+    -- to @HomeDir@ and instead accepts @AnotherHomeDir@.+    --+    -- Similar to 'castRel', there are also 'castFile' and 'castDir'.+    --+    -- Now we could do the copying like this:+    --+    -- > copyFile (fromAbsFile absGitConfigPath) (fromAbsFile absAnotherGitConfigPath)+    --+    -- Notice that while converting 'Path' to 'FilePath', we could have used 'toFilePath' instead of+    -- 'fromAbsFile', but 'fromAbsFile' gives us more type safety by demanding given 'Path' to be+    -- of specific type (absolute file). For example, if somehow variable @absGitConfigPath@ got to be of type+    -- @Path System (Rel ()) (Dir ())@, 'fromAbsFile' would cause compile time error, while 'toFilePath'+    -- would just happily go on.++    -- *** Extracting @from@ path from a JS import statement.++    -- |+    -- What if we wanted to extract @from@ path from a Javascript import statement and return it as a 'Path'?+    --+    -- Example of Javascript import statement:+    --+    -- > import Bar from "../foo/bar"  // We want to extract "../foo/bar" path.+    --+    -- Let's assume that we know that this statement is relative to some @ProjectDir@ (because that is where the+    -- JS file we got the statement from is located), but we don't know upfront the name of the file being imported.+    --+    -- Such function could have the following signature:+    --+    -- > parseJsImportFrom :: String -> Maybe (Path Posix (Rel (ProjectDir)) (File ()))+    --+    -- Notice how we used 'Posix' to specify that the path is following posix standard+    -- no matter on which OS we are running this code, while in examples above we+    -- used 'System', which meant paths follow whatever is the standard of the OS we are running on.+    --+    -- Next, also notice how we used @File ()@ to specify that file is \"unnamed\".+    -- While you could use some other approach to specify this, we found this to be convenient way to do it.+    -- That is why we also introduce @File\'@ and @Dir\'@ aliases, to make this even simpler.++    -- *** Defining a path via string literal during compile time++    -- |+    -- Let's say we want to define default file path from user's home directory to user's VLC config directory, and we already know it while writing our program.+    -- With "StrongPath", we could do it like this:+    --+    -- > defaultUserVlcConfigDir :: Path System (Rel UserHomeDir) (Dir UserVlcConfigDir)+    -- > defaultUserVlcConfigDir = [SP.reldir|.config/vlc|]+    --+    -- where we need QuasiQuotes language extension for 'SP.reldir' quasi quoter to work.+    -- This will parse the path during compile-time, ensuring it is valid.++    -- *** Paths starting with "../"++    -- |+    -- Relative paths in "StrongPath" can start with one or multiple "../".+    -- "../" is taken into account and appropriately managed when performing operations on paths.+    --+    -- > someRelPath :: Path System (Rel SomeDir) (File SomeFle)+    -- > someRelPath = [SP.relfile|../foo/myfile.txt|]++    -- *** Some more examples++    -- |+    -- > -- System path to "foo" directory, relative to "bar" directory.+    -- > dirFooInDirBar :: Path System (Rel BarDir) (Dir FooDir)+    -- > dirFooInDirBar = [reldir|somedir/foo|]+    -- >+    -- > -- Abs system path to "bar" directory.+    -- > dirBarAbsPath :: Path System Abs (Dir BarDir)+    -- > dirBarAbsPath = [absdir|/bar/|]+    -- >+    -- > -- Abs path to "foo" directory.+    -- > dirFooAbsPath :: Path System Abs (Dir FooDir)+    -- > dirFooAbsPath = dirBarAbsPath </> dirFooInDirBar+    -- >+    -- > -- Posix path to "unnamed" file, relative to "foo" directory.+    -- > someFile :: Path Posix (Rel FooDir) File ()+    -- > someFile = [relfileP|some/file.txt|]+    -- >+    -- > dirHome :: Path System Abs (Dir HomeDir)+    -- > dirHome :: [absdir|/home/john/|]+    -- >+    -- > dirFooCopiedToHomeAsInBar :: Path System Abs (Dir FooDir)+    -- > dirFooCopiedToHomeAsInBar = dirHome </> castRel dirFooInDirBar+    -- >+    -- > data BarDir  -- Represents Bar directory.+    -- > data FooDir  -- Represents Foo directory.+    -- > data HomeDir -- Represents Home directory.++    -- ** Inspiration++    -- |+    -- This library is greatly inspired by [path library](https://github.com/commercialhaskell/path)+    -- and is really a layer on top of it, replicating most of its API and using it for implementation+    -- details, while also adding to it, with main additions being:+    --+    -- - Differentiation between path standards (system, posix and windows) at type level, they can't be accidentally mixed.+    -- - \"Naming\" of directories and files at type level.+    -- - Support at type level for describing what are relative paths exactly relative to,+    --   so you e.g. can't concatenate wrong paths.+    -- - Support for @..\/@ at start of relative path.++    -- * API+    module StrongPath.Types,+    module StrongPath.FilePath,+    module StrongPath.Operations,+    module StrongPath.TH,++    -- ** Working with "Path" library++    -- | If you are using "Path" library alongside "StrongPath", you can import module "StrongPath.Path",+    -- which contains functions for converting "StrongPath" 'Path' into 'Path.Path' and vice versa.+  )+where++import StrongPath.FilePath+import StrongPath.Operations+import StrongPath.TH+import StrongPath.Types
+ src/StrongPath/FilePath.hs view
@@ -0,0 +1,212 @@+{-# OPTIONS_HADDOCK hide #-}++module StrongPath.FilePath+  ( -- ** Parsers (from 'FilePath' to 'Path')+    -- $parsersFilepath+    parseRelDir,+    parseRelFile,+    parseAbsDir,+    parseAbsFile,+    parseRelDirW,+    parseRelFileW,+    parseAbsDirW,+    parseAbsFileW,+    parseRelDirP,+    parseRelFileP,+    parseAbsDirP,+    parseAbsFileP,++    -- ** Conversion (from 'Path' to 'FilePath')+    -- $conversionFilepath+    toFilePath,+    fromRelDir,+    fromRelFile,+    fromAbsDir,+    fromAbsFile,+    fromRelDirP,+    fromRelFileP,+    fromAbsDirP,+    fromAbsFileP,+    fromRelDirW,+    fromRelFileW,+    fromAbsDirW,+    fromAbsFileW,+  )+where++import Control.Monad.Catch (MonadThrow)+import Data.List (intercalate)+import qualified Path as P+import qualified Path.Posix as PP+import qualified Path.Windows as PW+import StrongPath.Internal+import StrongPath.Path+import qualified System.FilePath as FP+import qualified System.FilePath.Posix as FPP+import qualified System.FilePath.Windows as FPW++-- $parsersFilepath+-- Path can be constructed from `FilePath`:+--+-- > parse<base><type><standard> :: MonadThrow m => FilePath -> m (<corresponding_path_type>)+--+-- There are 12 parser functions, each of them parsing 'FilePath' into a specific 'Path'+-- type.+-- All of them work in the same fashion and will throw an error (via 'MonadThrow')+-- if given 'FilePath' can't be parsed into the specific 'Path' type.+-- For example, if path is absolute, 'parseRelDir' will throw an error.+--+-- Not all parsers accept all types of separators, for example+-- 'parseRelDirP' parser will fail to parse paths using Windows separators,+-- while 'parseRelDirW' will accept both Windows and Posix separators.+--+-- Below is a table describing, for all the parser functions,+-- which path standard (separators) do they accept as input+-- and to what path standard they parse it.+--+-- +---------------------------+-----------------+----------++-- |          Parsers          |      From       |    To    |+-- +===========================+=================+==========++-- | parse[Abs|Rel][Dir|File]  |  System/Posix   |  System  |+-- +---------------------------+-----------------+----------++-- | parse[Abs|Rel][Dir|File]W |  Win/Posix      |   Win    |+-- +---------------------------+-----------------+----------++-- | parse[Abs|Rel][Dir|File]P |   Posix         |  Posix   |+-- +---------------------------+-----------------+----------++--+-- NOTE: Root of @parseAbs...@ input always has to match its path standard!+--   e.g., 'parseAbsDirW' can parse @\"C:\\foo\/bar\"@ but it can't parse @\"\/foo\/bar\"@.+--+-- Examples:+--+--  - @parseAbsFile \"C:\\foo\\bar.txt\"@ is valid if system is Windows, and gives the same result as @parseAbsFile \"C:\\foo\/bar.txt\"@.+--    On the other hand, both are invalid if system is Linux.+--  - @parseRelFile \"foo\/bar.txt\"@ is valid independent of the system.+--  - @parseRelFile \"foo\\bar.txt\"@ is valid only if system is Windows.+--  - @parseRelDirW \"foo\\bar\\test\"@ is valid, independent of the system, and gives the same result as @parseRelDirW \"foo\\bar\/test\"@ or @parseRelDirW "foo\/bar\/test\"@.+--+-- Basically, all of the parsers accept their \"native\" standard AND Posix,+-- which enables you to hardcode paths as Posix in the code that will compile+-- and work both on Linux and Windows when using `System` as a standard.+-- So Posix becames a kind of \"universal\" language for hardcoding the paths.++parseRelDir :: MonadThrow m => FilePath -> m (Path System (Rel d1) (Dir d2))+parseRelFile :: MonadThrow m => FilePath -> m (Path System (Rel d) (File f))+parseAbsDir :: MonadThrow m => FilePath -> m (Path System Abs (Dir d))+parseAbsFile :: MonadThrow m => FilePath -> m (Path System Abs (File f))+parseRelDirW :: MonadThrow m => FilePath -> m (Path Windows (Rel d1) (Dir d2))+parseRelFileW :: MonadThrow m => FilePath -> m (Path Windows (Rel d) (File f))+parseAbsDirW :: MonadThrow m => FilePath -> m (Path Windows Abs (Dir d))+parseAbsFileW :: MonadThrow m => FilePath -> m (Path Windows Abs (File f))+parseRelDirP :: MonadThrow m => FilePath -> m (Path Posix (Rel d1) (Dir d2))+parseRelFileP :: MonadThrow m => FilePath -> m (Path Posix (Rel d) (File f))+parseAbsDirP :: MonadThrow m => FilePath -> m (Path Posix Abs (Dir d))+parseAbsFileP :: MonadThrow m => FilePath -> m (Path Posix Abs (File f))+---- System+parseRelDir = parseRelFP RelDir [FP.pathSeparator, FPP.pathSeparator] P.parseRelDir++parseRelFile = parseRelFP RelFile [FP.pathSeparator, FPP.pathSeparator] P.parseRelFile++parseAbsDir fp = fromPathAbsDir <$> P.parseAbsDir fp++parseAbsFile fp = fromPathAbsFile <$> P.parseAbsFile fp++---- Windows+parseRelDirW = parseRelFP RelDirW [FPW.pathSeparator, FPP.pathSeparator] PW.parseRelDir++parseRelFileW = parseRelFP RelFileW [FPW.pathSeparator, FPP.pathSeparator] PW.parseRelFile++parseAbsDirW fp = fromPathAbsDirW <$> PW.parseAbsDir fp++parseAbsFileW fp = fromPathAbsFileW <$> PW.parseAbsFile fp++---- Posix+parseRelDirP = parseRelFP RelDirP [FPP.pathSeparator] PP.parseRelDir++parseRelFileP = parseRelFP RelFileP [FPP.pathSeparator] PP.parseRelFile++parseAbsDirP fp = fromPathAbsDirP <$> PP.parseAbsDir fp++parseAbsFileP fp = fromPathAbsFileP <$> PP.parseAbsFile fp++-- $conversionFilepath+-- 'Path' can be converted into 'FilePath' via polymorphic function 'toFilePath'+-- or via any of the 12 functions that accept specific path type.+--+-- We recommend using specific functions instead of 'toFilePath',+-- because that way you are explicit about which path you expect+-- and if that expectancy is not met, type system will catch it.++toFilePath :: Path s b t -> FilePath+toFilePath sp = case sp of+  ---- System+  RelDir p prefix -> relPathToFilePath P.toFilePath FP.pathSeparator prefix p+  RelFile p prefix -> relPathToFilePath P.toFilePath FP.pathSeparator prefix p+  AbsDir p -> P.toFilePath p+  AbsFile p -> P.toFilePath p+  ---- Windows+  RelDirW p prefix -> relPathToFilePath PW.toFilePath FPW.pathSeparator prefix p+  RelFileW p prefix -> relPathToFilePath PW.toFilePath FPW.pathSeparator prefix p+  AbsDirW p -> PW.toFilePath p+  AbsFileW p -> PW.toFilePath p+  ---- Posix+  RelDirP p prefix -> relPathToFilePath PP.toFilePath FPP.pathSeparator prefix p+  RelFileP p prefix -> relPathToFilePath PP.toFilePath FPP.pathSeparator prefix p+  AbsDirP p -> PP.toFilePath p+  AbsFileP p -> PP.toFilePath p+  where+    relPathToFilePath pathToFilePath sep prefix path =+      combinePrefixWithPath sep (relPathPrefixToFilePath sep prefix) (pathToFilePath path)++    relPathPrefixToFilePath :: Char -> RelPathPrefix -> FilePath+    relPathPrefixToFilePath _ NoPrefix = ""+    relPathPrefixToFilePath sep (ParentDir n) =+      intercalate [sep] (replicate n "..") ++ [sep]++    -- TODO: This function and helper functions above are somewhat too loose and hard to+    --   follow, implement them in better way.+    -- Here we are assuming that prefix is of form (../)*, therefore it ends with separator,+    -- and it could also be empty.+    combinePrefixWithPath :: Char -> String -> FilePath -> FilePath+    combinePrefixWithPath sep prefix path+      | path `elem` [".", ['.', sep], "./"] && not (null prefix) = prefix+    combinePrefixWithPath _ prefix path = prefix ++ path++-- These functions just call toFilePath, but their value is in+-- their type: they allow you to capture expected type of the strong path+-- that you want to convert into FilePath.+fromRelDir :: Path System (Rel r) (Dir d) -> FilePath+fromRelDir = toFilePath++fromRelFile :: Path System (Rel r) (File f) -> FilePath+fromRelFile = toFilePath++fromAbsDir :: Path System Abs (Dir d) -> FilePath+fromAbsDir = toFilePath++fromAbsFile :: Path System Abs (File f) -> FilePath+fromAbsFile = toFilePath++fromRelDirP :: Path Posix (Rel r) (Dir d) -> FilePath+fromRelDirP = toFilePath++fromRelFileP :: Path Posix (Rel r) (File f) -> FilePath+fromRelFileP = toFilePath++fromAbsDirP :: Path Posix Abs (Dir d) -> FilePath+fromAbsDirP = toFilePath++fromAbsFileP :: Path Posix Abs (File f) -> FilePath+fromAbsFileP = toFilePath++fromRelDirW :: Path Windows (Rel r) (Dir d) -> FilePath+fromRelDirW = toFilePath++fromRelFileW :: Path Windows (Rel r) (File f) -> FilePath+fromRelFileW = toFilePath++fromAbsDirW :: Path Windows Abs (Dir d) -> FilePath+fromAbsDirW = toFilePath++fromAbsFileW :: Path Windows Abs (File f) -> FilePath+fromAbsFileW = toFilePath
+ src/StrongPath/Internal.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE DeriveLift #-}++module StrongPath.Internal where++import Control.Monad.Catch (MonadThrow)+import Language.Haskell.TH.Syntax (Lift)+import qualified Path as P+import qualified Path.Posix as PP+import qualified Path.Windows as PW++-- | Strongly typed file path. Central type of the "StrongPath".+--+--   [@s@]: __Standard__: Posix or windows. Can be fixed ('Posix', 'Windows') or determined by the system ('System').+--+--   [@b@]: __Base__: Absolute ('Abs') or relative ('Rel').+--+--   [@t@]: __Type__: File ('File') or directory ('Dir').+--+-- Some examples:+--+-- > Path System (Dir HomeDir) (File FooFile)+-- > Path System Abs (Dir HomeDir)+-- > Path Posix (Rel ProjectRoot) (File ())+data Path s b t+  = -- System+    RelDir (P.Path P.Rel P.Dir) RelPathPrefix+  | RelFile (P.Path P.Rel P.File) RelPathPrefix+  | AbsDir (P.Path P.Abs P.Dir)+  | AbsFile (P.Path P.Abs P.File)+  | -- Windows+    RelDirW (PW.Path PW.Rel PW.Dir) RelPathPrefix+  | RelFileW (PW.Path PW.Rel PW.File) RelPathPrefix+  | AbsDirW (PW.Path PW.Abs PW.Dir)+  | AbsFileW (PW.Path PW.Abs PW.File)+  | -- Posix+    RelDirP (PP.Path PP.Rel PP.Dir) RelPathPrefix+  | RelFileP (PP.Path PP.Rel PP.File) RelPathPrefix+  | AbsDirP (PP.Path PP.Abs PP.Dir)+  | AbsFileP (PP.Path PP.Abs PP.File)+  deriving (Show, Eq, Lift)++data RelPathPrefix+  = -- | ../, Int saying how many times it repeats.+    ParentDir Int+  | NoPrefix+  deriving (Show, Eq, Lift)++-- | Describes 'Path' base as absolute.+data Abs deriving (Lift)++-- | Describes 'Path' base as relative to the directory @dir@.+data Rel dir deriving (Lift)++-- | Means that path points to a directory @dir@.+-- To use as a type in place of @dir@, we recommend creating an empty+-- data type representing the specific directory, e.g. @data ProjectRootDir@.+data Dir dir deriving (Lift)++-- | Means that path points to a file @file@.+-- To use as a type in place of @file@, we recommend creating an empty+-- data type representing the specific file, e.g. @data ProjectManifestFile@.+data File file deriving (Lift)++-- | Describes 'Path' standard as posix (e.g. @\/path\/to\/foobar@).+-- This makes 'Path' behave in system-independent fashion: code behaves the same+-- regardless of the system it is running on.+-- You will normally want to use it when dealing with paths from some external source,+-- or with paths that have explicitely fixed standard.+-- For example, if you are running your Haskell program on Windows and parsing logs which+-- were obtained from the Linux server, or maybe you are parsing Javascript import statements,+-- you will want to use 'Posix'.+data Posix deriving (Lift)++-- | Describes 'Path' standard as windows (e.g. @C:\\path\\to\\foobar@).+-- Check 'Posix' for more details, everything is analogous.+data Windows deriving (Lift)++-- | Describes 'Path' standard to be determined by the system/OS.+--+-- If the system is Windows, it will resolve to 'Windows' internally, and if not,+-- it will resolve to 'Posix'.+--+-- However, keep in mind that even if running on Windows, @Path Windows b t@+-- and @Path System b t@ are still considered to be different types,+-- even though @Path System b t @ internally uses Windows standard.+--+-- You will normally want to use 'System' if you are dealing with the paths on the disk of the host OS+-- (where your code is running), for example if user is providing you with the path to the file on the disk+-- that you will be doing something with.+-- Keep in mind that 'System' causes the behaviour of 'Path' to be system/platform-dependant.+data System deriving (Lift) -- Depends on the platform, it is either Posix or Windows.++-- | 'System' is the most commonly used standard, so we provide you with a type alias for it.+type Path' = Path System++-- | When you don't want your path to be relative to anything specific,+-- it is convenient to use unit @()@.+type Rel' = Rel ()++-- | When you don't want your directory path to be named,+-- it is convenient to use unit @()@.+type Dir' = Dir ()++-- | When you don't want your file path to be named,+-- it is convenient to use unit @()@.+type File' = File ()++-- TODO: Extract `parseRelFP` and `extractRelPathPrefix` into StrongPath.FilePath.Internals?+parseRelFP ::+  MonadThrow m =>+  (p -> RelPathPrefix -> Path s (Rel d) t) ->+  [Char] ->+  (FilePath -> m p) ->+  FilePath ->+  m (Path s (Rel d) t)+parseRelFP constructor validSeparators pathParser fp =+  let (prefix, fp') = extractRelPathPrefix validSeparators fp+      fp'' = if fp' == "" then "." else fp' -- Because Path Rel parsers can't handle just "".+   in (\p -> constructor p prefix) <$> pathParser fp''++-- | Extracts a multiple "../" from start of the file path.+--   If path is completely ../../.., also handles the last one.+--   NOTE: We don't normalize path in any way.+extractRelPathPrefix :: [Char] -> FilePath -> (RelPathPrefix, FilePath)+extractRelPathPrefix validSeparators path =+  let (n, path') = dropParentDirs path+   in (if n == 0 then NoPrefix else ParentDir n, path')+  where+    parentDirStrings :: [String]+    parentDirStrings = [['.', '.', s] | s <- validSeparators]++    pathStartsWithParentDir :: FilePath -> Bool+    pathStartsWithParentDir p = take 3 p `elem` parentDirStrings++    dropParentDirs :: FilePath -> (Int, FilePath)+    dropParentDirs p+      | pathStartsWithParentDir p =+        let (n, p') = dropParentDirs (drop 3 p)+         in (1 + n, p')+      | p == ".." = (1, "")+      | otherwise = (0, p)++prefixNumParentDirs :: RelPathPrefix -> Int+prefixNumParentDirs NoPrefix = 0+prefixNumParentDirs (ParentDir n) = n++relPathNumParentDirs :: Path s (Rel r) t -> Int+relPathNumParentDirs = prefixNumParentDirs . relPathPrefix++relPathPrefix :: Path s (Rel r) t -> RelPathPrefix+relPathPrefix sp = case sp of+  RelDir _ pr -> pr+  RelFile _ pr -> pr+  RelDirW _ pr -> pr+  RelFileW _ pr -> pr+  RelDirP _ pr -> pr+  RelFileP _ pr -> pr+  _ -> impossible++impossible :: a+impossible = error "This should be impossible."
+ src/StrongPath/Operations.hs view
@@ -0,0 +1,207 @@+{-# OPTIONS_HADDOCK hide #-}++module StrongPath.Operations+  ( -- ** Operations+    (</>),+    parent,++    -- ** Casting+    castRel,+    castDir,+    castFile,++    -- ** Conversion of path standard+    relDirToPosix,+    relFileToPosix,+  )+where++import Control.Monad.Catch (MonadThrow)+import qualified Path as P+import qualified Path.Posix as PP+import qualified Path.Windows as PW+import StrongPath.FilePath+import StrongPath.Internal+import qualified System.FilePath as FP+import qualified System.FilePath.Posix as FPP+import qualified System.FilePath.Windows as FPW++-- TODO: Add relDirToWindows and relFileToWindows?+-- TODO: Implement relFile?+-- TODO: Can I use type classes and return type polymorhipsm to make all this shorter and reduce duplication?+-- class Path, and then I have PathWindows and PathPosix and PathSystem implement it, smth like that?+-- And then fromPathRelDir has polymorhic return type based on standard? I tried a little bit but it is complicated.+-- TODO: If there is no other solution to all this duplication, do some template haskell magic to simplify it.++-- | Gets parent dir of the path.+--+-- Either removes last entry in the path or if there are no entries and just @\"..\/\"@s, adds one more @\"..\/\"@.+--+-- If path is absolute root and it has no parent, it will return unchanged path.+--+-- Examples (pseudocode):+--+-- > parent "a/b/c" == "a/b"+-- > parent "/a" == "/"+-- > parent "/" == "/"+-- > parent "../a/b" == "../a"+-- > parent ".." == "../.."+-- > parent (parent "../a") == "../.."+parent :: Path s b t -> Path s b (Dir d)+parent path = case path of+  ---- System+  RelDir p prefix -> relDirPathParent RelDir P.parent p prefix+  RelFile p prefix -> RelDir (P.parent p) prefix+  AbsDir p -> AbsDir $ P.parent p+  AbsFile p -> AbsDir $ P.parent p+  ---- Windows+  RelDirW p prefix -> relDirPathParent RelDirW PW.parent p prefix+  RelFileW p prefix -> RelDirW (PW.parent p) prefix+  AbsDirW p -> AbsDirW $ PW.parent p+  AbsFileW p -> AbsDirW $ PW.parent p+  ---- Posix+  RelDirP p prefix -> relDirPathParent RelDirP PP.parent p prefix+  RelFileP p prefix -> RelDirP (PP.parent p) prefix+  AbsDirP p -> AbsDirP $ PP.parent p+  AbsFileP p -> AbsDirP $ PP.parent p+  where+    -- NOTE: We need this special logic for RelDir, because if we have RelDir Path,+    --   it is possible that it is "." or smth like that and no parent can be obtained,+    --   in which case we want to add "../" to our prefix.+    --   For file though, we don't have that concern, because it will always be possible to+    --   get a parent, as per current Path implementation.+    relDirPathParent constructor pathParent p prefix =+      if pathParent p == p+        then+          let prefix' = case prefix of+                ParentDir n -> ParentDir (n + 1)+                NoPrefix -> ParentDir 1+           in constructor p prefix'+        else+          let p' = pathParent p+           in constructor p' prefix++-- | Concatenates two paths, same as "FilePath".'FilePath.</>', but only if the second path is relative+-- to the directory that first path leads to, and if both paths use the same path standard.+--+-- How @\"..\/\"@s are resolved (examples are pseudocode):+--+-- - For each @\"..\/\"@ at the start of the right hand path, one most right entry is removed+--   from the left hand path.+--+-- > "a/b" </> "../c" == "a/c"+--+-- - If left path is absolute and right path has too many @"..\/"@s, they go \"over\" the root+--   and are effectively ignored.+--+-- > "/a/b" </> "../../../../../c" == "/c"+--+-- - If left path is relative and right path has more @\"..\/\"@s then left has entries,+--   the leftover @\"..\/\"@s are carried over.+--+-- > "a/b" </> "../../../../../c" == "../../../c"+(</>) :: Path s b (Dir d) -> Path s (Rel d) t -> Path s b t+---- System+lsp@(RelDir _ _) </> (RelFile rp rprefix) =+  let (RelDir lp' lprefix') = iterate parent lsp !! prefixNumParentDirs rprefix+   in RelFile (lp' P.</> rp) lprefix'+lsp@(RelDir _ _) </> (RelDir rp rprefix) =+  let (RelDir lp' lprefix') = iterate parent lsp !! prefixNumParentDirs rprefix+   in RelDir (lp' P.</> rp) lprefix'+lsp@(AbsDir _) </> (RelFile rp rprefix) =+  let (AbsDir lp') = iterate parent lsp !! prefixNumParentDirs rprefix+   in AbsFile (lp' P.</> rp)+lsp@(AbsDir _) </> (RelDir rp rprefix) =+  let (AbsDir lp') = iterate parent lsp !! prefixNumParentDirs rprefix+   in AbsDir (lp' P.</> rp)+---- Windows+lsp@(RelDirW _ _) </> (RelFileW rp rprefix) =+  let (RelDirW lp' lprefix') = iterate parent lsp !! prefixNumParentDirs rprefix+   in RelFileW (lp' PW.</> rp) lprefix'+lsp@(RelDirW _ _) </> (RelDirW rp rprefix) =+  let (RelDirW lp' lprefix') = iterate parent lsp !! prefixNumParentDirs rprefix+   in RelDirW (lp' PW.</> rp) lprefix'+lsp@(AbsDirW _) </> (RelFileW rp rprefix) =+  let (AbsDirW lp') = iterate parent lsp !! prefixNumParentDirs rprefix+   in AbsFileW (lp' PW.</> rp)+lsp@(AbsDirW _) </> (RelDirW rp rprefix) =+  let (AbsDirW lp') = iterate parent lsp !! prefixNumParentDirs rprefix+   in AbsDirW (lp' PW.</> rp)+---- Posix+lsp@(RelDirP _ _) </> (RelFileP rp rprefix) =+  let (RelDirP lp' lprefix') = iterate parent lsp !! prefixNumParentDirs rprefix+   in RelFileP (lp' PP.</> rp) lprefix'+lsp@(RelDirP _ _) </> (RelDirP rp rprefix) =+  let (RelDirP lp' lprefix') = iterate parent lsp !! prefixNumParentDirs rprefix+   in RelDirP (lp' PP.</> rp) lprefix'+lsp@(AbsDirP _) </> (RelFileP rp rprefix) =+  let (AbsDirP lp') = iterate parent lsp !! prefixNumParentDirs rprefix+   in AbsFileP (lp' PP.</> rp)+lsp@(AbsDirP _) </> (RelDirP rp rprefix) =+  let (AbsDirP lp') = iterate parent lsp !! prefixNumParentDirs rprefix+   in AbsDirP (lp' PP.</> rp)+_ </> _ = impossible++-- | Enables you to redefine which dir is the path relative to.+castRel :: Path s (Rel d1) a -> Path s (Rel d2) a+---- System+castRel (RelDir p pr) = RelDir p pr+castRel (RelFile p pr) = RelFile p pr+---- Windows+castRel (RelDirW p pr) = RelDirW p pr+castRel (RelFileW p pr) = RelFileW p pr+---- Posix+castRel (RelDirP p pr) = RelDirP p pr+castRel (RelFileP p pr) = RelFileP p pr+castRel _ = impossible++-- | Enables you to rename the dir.+castDir :: Path s a (Dir d1) -> Path s a (Dir d2)+---- System+castDir (AbsDir p) = AbsDir p+castDir (RelDir p pr) = RelDir p pr+---- Windows+castDir (AbsDirW p) = AbsDirW p+castDir (RelDirW p pr) = RelDirW p pr+---- Posix+castDir (AbsDirP p) = AbsDirP p+castDir (RelDirP p pr) = RelDirP p pr+castDir _ = impossible++-- | Enables you to rename the file.+castFile :: Path s a (File f1) -> Path s a (File f2)+---- System+castFile (AbsFile p) = AbsFile p+castFile (RelFile p pr) = RelFile p pr+---- Windows+castFile (AbsFileW p) = AbsFileW p+castFile (RelFileW p pr) = RelFileW p pr+---- Posix+castFile (AbsFileP p) = AbsFileP p+castFile (RelFileP p pr) = RelFileP p pr+castFile _ = impossible++-- TODO: I was not able to unite these two functions (`relDirToPosix` and `relFileToPosix`) into just `toPosix``+--   because Haskell did not believe me that I would be returning same "t" (Dir/File) in Path+--   as was in first argument. I wonder if there is easy way to go around that or if+--   we have to redo significant part of the StrongPath to be able to do smth like this.++-- | Converts relative dir path to posix by replacing current path separators with posix path separators.+-- If path is already posix, it will not change.+--+-- Works well for \"normal\" relative paths like @\"a\\b\\c\"@ (Win) or @\"a\/b\/c\"@ (Posix).+-- If path is weird but still considered relative, like just @\"C:\"@ on Win,+-- results can be unexpected, most likely resulting with error thrown.+relDirToPosix :: MonadThrow m => Path s (Rel d1) (Dir d2) -> m (Path Posix (Rel d1) (Dir d2))+relDirToPosix sp@(RelDir _ _) = parseRelDirP $ FPP.joinPath $ FP.splitDirectories $ toFilePath sp+relDirToPosix sp@(RelDirW _ _) = parseRelDirP $ FPP.joinPath $ FPW.splitDirectories $ toFilePath sp+relDirToPosix (RelDirP p pr) = return $ RelDirP p pr+relDirToPosix _ = impossible++-- | Converts relative file path to posix, if it is not already posix.+-- Check 'relDirToPosix' for more details, they behave the same.+relFileToPosix :: MonadThrow m => Path s (Rel d1) (File f) -> m (Path Posix (Rel d1) (File f))+relFileToPosix sp@(RelFile _ _) = parseRelFileP $ FPP.joinPath $ FP.splitDirectories $ toFilePath sp+relFileToPosix sp@(RelFileW _ _) = parseRelFileP $ FPP.joinPath $ FPW.splitDirectories $ toFilePath sp+relFileToPosix (RelFileP p pr) = return $ RelFileP p pr+relFileToPosix _ = impossible
+ src/StrongPath/Path.hs view
@@ -0,0 +1,148 @@+module StrongPath.Path+  ( -- * Parsers (from "Path".'Path.Path' to 'StrongPath.Path')+    -- $parsersPath+    fromPathRelDir,+    fromPathRelFile,+    fromPathAbsDir,+    fromPathAbsFile,+    fromPathRelDirW,+    fromPathRelFileW,+    fromPathAbsDirW,+    fromPathAbsFileW,+    fromPathRelDirP,+    fromPathRelFileP,+    fromPathAbsDirP,+    fromPathAbsFileP,++    -- * Conversion (from 'StrongPath.Path' to "Path".'Path.Path')+    -- $conversionPath+    toPathRelDir,+    toPathRelFile,+    toPathAbsDir,+    toPathAbsFile,+    toPathRelDirW,+    toPathRelFileW,+    toPathAbsDirW,+    toPathAbsFileW,+    toPathRelDirP,+    toPathRelFileP,+    toPathAbsDirP,+    toPathAbsFileP,+  )+where++import qualified Path as P+import qualified Path.Posix as PP+import qualified Path.Windows as PW+import StrongPath.Internal++-- $parsersPath+-- Functions for parsing "Path" paths into "StrongPath" paths.++-- Constructors+fromPathRelDir :: P.Path P.Rel P.Dir -> Path System (Rel a) (Dir b)+fromPathRelFile :: P.Path P.Rel P.File -> Path System (Rel a) (File f)+fromPathAbsDir :: P.Path P.Abs P.Dir -> Path System Abs (Dir a)+fromPathAbsFile :: P.Path P.Abs P.File -> Path System Abs (File f)+fromPathRelDirW :: PW.Path PW.Rel PW.Dir -> Path Windows (Rel a) (Dir b)+fromPathRelFileW :: PW.Path PW.Rel PW.File -> Path Windows (Rel a) (File f)+fromPathAbsDirW :: PW.Path PW.Abs PW.Dir -> Path Windows Abs (Dir a)+fromPathAbsFileW :: PW.Path PW.Abs PW.File -> Path Windows Abs (File f)+fromPathRelDirP :: PP.Path PP.Rel PP.Dir -> Path Posix (Rel a) (Dir b)+fromPathRelFileP :: PP.Path PP.Rel PP.File -> Path Posix (Rel a) (File f)+fromPathAbsDirP :: PP.Path PP.Abs PP.Dir -> Path Posix Abs (Dir a)+fromPathAbsFileP :: PP.Path PP.Abs PP.File -> Path Posix Abs (File f)+---- System+fromPathRelDir p = RelDir p NoPrefix++fromPathRelFile p = RelFile p NoPrefix++fromPathAbsDir = AbsDir++fromPathAbsFile = AbsFile++---- Windows+fromPathRelDirW p = RelDirW p NoPrefix++fromPathRelFileW p = RelFileW p NoPrefix++fromPathAbsDirW = AbsDirW++fromPathAbsFileW = AbsFileW++---- Posix+fromPathRelDirP p = RelDirP p NoPrefix++fromPathRelFileP p = RelFileP p NoPrefix++fromPathAbsDirP = AbsDirP++fromPathAbsFileP = AbsFileP++-- $conversionPath+-- Functions for converting paths from "StrongPath" paths into "Path" paths.++-- TODO: Should I go with MonadThrow here instead of just throwing error? Probably!+--       I could, as error, return actual Path + info on how many ../ were there in StrongPath,+--       so user can recover from error and continue, if they wish.+-- Deconstructors+toPathRelDir :: Path System (Rel a) (Dir b) -> P.Path P.Rel P.Dir+toPathRelFile :: Path System (Rel a) (File f) -> P.Path P.Rel P.File+toPathAbsDir :: Path System Abs (Dir a) -> P.Path P.Abs P.Dir+toPathAbsFile :: Path System Abs (File f) -> P.Path P.Abs P.File+toPathRelDirW :: Path Windows (Rel a) (Dir b) -> PW.Path PW.Rel PW.Dir+toPathRelFileW :: Path Windows (Rel a) (File f) -> PW.Path PW.Rel PW.File+toPathAbsDirW :: Path Windows Abs (Dir a) -> PW.Path PW.Abs PW.Dir+toPathAbsFileW :: Path Windows Abs (File f) -> PW.Path PW.Abs PW.File+toPathRelDirP :: Path Posix (Rel a) (Dir b) -> PP.Path PP.Rel PP.Dir+toPathRelFileP :: Path Posix (Rel a) (File f) -> PP.Path PP.Rel PP.File+toPathAbsDirP :: Path Posix Abs (Dir a) -> PP.Path PP.Abs PP.Dir+toPathAbsFileP :: Path Posix Abs (File f) -> PP.Path PP.Abs PP.File+---- System+toPathRelDir (RelDir p NoPrefix) = p+toPathRelDir (RelDir _ _) = relativeStrongPathWithPrefixToPathError+toPathRelDir _ = impossible++toPathRelFile (RelFile p NoPrefix) = p+toPathRelFile (RelFile _ _) = relativeStrongPathWithPrefixToPathError+toPathRelFile _ = impossible++toPathAbsDir (AbsDir p) = p+toPathAbsDir _ = impossible++toPathAbsFile (AbsFile p) = p+toPathAbsFile _ = impossible++---- Windows+toPathRelDirW (RelDirW p NoPrefix) = p+toPathRelDirW (RelDirW _ _) = relativeStrongPathWithPrefixToPathError+toPathRelDirW _ = impossible++toPathRelFileW (RelFileW p NoPrefix) = p+toPathRelFileW (RelFileW _ _) = relativeStrongPathWithPrefixToPathError+toPathRelFileW _ = impossible++toPathAbsDirW (AbsDirW p) = p+toPathAbsDirW _ = impossible++toPathAbsFileW (AbsFileW p) = p+toPathAbsFileW _ = impossible++---- Posix+toPathRelDirP (RelDirP p NoPrefix) = p+toPathRelDirP (RelDirP _ _) = relativeStrongPathWithPrefixToPathError+toPathRelDirP _ = impossible++toPathRelFileP (RelFileP p NoPrefix) = p+toPathRelFileP (RelFileP _ _) = relativeStrongPathWithPrefixToPathError+toPathRelFileP _ = impossible++toPathAbsDirP (AbsDirP p) = p+toPathAbsDirP _ = impossible++toPathAbsFileP (AbsFileP p) = p+toPathAbsFileP _ = impossible++relativeStrongPathWithPrefixToPathError :: a+relativeStrongPathWithPrefixToPathError =+  error "Relative StrongPath.Path with prefix can't be converted into Path.Path."
+ src/StrongPath/TH.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_HADDOCK hide #-}++module StrongPath.TH+  ( -- ** QuasiQuoters+    -- $quasiQuoters+    absdir,+    absdirP,+    absdirW,+    absfile,+    absfileP,+    absfileW,+    reldir,+    reldirP,+    reldirW,+    relfile,+    relfileP,+    relfileW,+  )+where++import Control.Monad ((>=>))+import qualified Language.Haskell.TH.Lib as TH+import Language.Haskell.TH.Quote (QuasiQuoter (..))+import Language.Haskell.TH.Syntax (Lift (..))+import qualified Language.Haskell.TH.Syntax as TH+import StrongPath.FilePath+import StrongPath.Internal++-- $quasiQuoters+-- StrongPath provides quasi quoters that enable you to construct 'Path' in compile time.+-- You will need to enable 'QuasiQuotes' language extension in order to use them.+-- With quasi quoters, you can define paths like this:+--+-- > dirFooAbsPath :: Path System Abs (Dir FooDir)+-- > dirFooAbsPath = [absdir|/foo/bar|]+--+-- > someFile :: Path Posix (Rel FooDir) File ()+-- > someFile = [relfileP|some/file.txt|]+--+-- These will run at compile-time and underneath use the appropriate parser, ensuring that paths are valid and throwing compile-time error if not.++-- TODO: Split these into a separate module, StrongPath.QuasiQuoters, that will be reexported from this module.+--   This will also need extraction of some other parts of this module, in order to avoid cyclic imports.++qq ::+  (Lift p, Show err) =>+  (String -> Either err p) ->+  (p -> TH.ExpQ) ->+  QuasiQuoter+qq parse liftP =+  QuasiQuoter+    { quoteExp = either (fail . show) liftP . parse,+      quotePat = err "pattern",+      quoteType = err "type",+      quoteDec = err "declaration"+    }+  where+    err what x = fail ("unexpected " ++ what ++ ", must be expression: " ++ x)++liftPath :: TH.TypeQ -> TH.TypeQ -> TH.TypeQ -> Path s b t -> TH.ExpQ+liftPath s b t p = [|$(lift p) :: Path $s $b $t|]++typeVar :: String -> TH.TypeQ+typeVar = TH.newName >=> TH.varT++absdir, absdirP, absdirW :: QuasiQuoter+absdir = qq parseAbsDir (liftPath [t|System|] [t|Abs|] [t|Dir $(typeVar "d")|])+absdirP = qq parseAbsDirP (liftPath [t|Posix|] [t|Abs|] [t|Dir $(typeVar "d")|])+absdirW = qq parseAbsDirW (liftPath [t|Windows|] [t|Abs|] [t|Dir $(typeVar "d")|])++absfile, absfileP, absfileW :: QuasiQuoter+absfile = qq parseAbsFile (liftPath [t|System|] [t|Abs|] [t|File $(typeVar "f")|])+absfileP = qq parseAbsFileP (liftPath [t|Posix|] [t|Abs|] [t|File $(typeVar "f")|])+absfileW = qq parseAbsFileW (liftPath [t|Windows|] [t|Abs|] [t|File $(typeVar "f")|])++reldir, reldirP, reldirW :: QuasiQuoter+reldir = qq parseRelDir (liftPath [t|System|] [t|Rel $(typeVar "d1")|] [t|Dir $(typeVar "d2")|])+reldirP = qq parseRelDirP (liftPath [t|Posix|] [t|Rel $(typeVar "d1")|] [t|Dir $(typeVar "d2")|])+reldirW = qq parseRelDirW (liftPath [t|Windows|] [t|Rel $(typeVar "d1")|] [t|Dir $(typeVar "d2")|])++relfile, relfileP, relfileW :: QuasiQuoter+relfile = qq parseRelFile (liftPath [t|System|] [t|Rel $(typeVar "d")|] [t|File $(typeVar "f")|])+relfileP = qq parseRelFileP (liftPath [t|Posix|] [t|Rel $(typeVar "d")|] [t|File $(typeVar "f")|])+relfileW = qq parseRelFileW (liftPath [t|Windows|] [t|Rel $(typeVar "d")|] [t|File $(typeVar "f")|])
+ src/StrongPath/Types.hs view
@@ -0,0 +1,34 @@+{-# OPTIONS_HADDOCK hide #-}++module StrongPath.Types+  ( -- ** Types++    -- *** Path+    Path,++    -- **** 'Path' type+    Dir,+    File,++    -- **** 'Path' base+    Abs,+    Rel,++    -- **** 'Path' standard++    -- | TLDR: If you are not sure which standard to use, go with 'System' since that is the most+    -- common use case, and you will likely recognize the situation in which you need+    -- system-indepenent behaviour ('Posix', 'Windows') when it happens.+    Posix,+    Windows,+    System,++    -- **** 'Path' aliases+    Path',+    Rel',+    Dir',+    File',+  )+where++import StrongPath.Internal
+ strong-path.cabal view
@@ -0,0 +1,70 @@+cabal-version:      1.12+name:               strong-path+version:            1.0.0.0+license:            MIT+license-file:       LICENSE+copyright:          2020 Martin Sosic+maintainer:         sosic.martin@gmail.com+author:             Martin Sosic+homepage:           https://github.com/wasp-lang/strong-path#readme+bug-reports:        https://github.com/wasp-lang/strong-path/issues+synopsis:           Strongly typed paths in Haskell.+description:+    Replacement for a FilePath that enables you to handle filepaths in your code in a type-safe manner. You can specify at type level if they are relative, absolute, file, directory, posix, windows, and even to which file or directory they point to or are relative to.++category:           System, Filesystem, FilePath+build-type:         Simple+extra-source-files:+    README.md+    ChangeLog.md++source-repository head+    type:     git+    location: https://github.com/wasp-lang/strong-path++library+    exposed-modules:+        StrongPath+        StrongPath.FilePath+        StrongPath.Internal+        StrongPath.Operations+        StrongPath.Path+        StrongPath.TH+        StrongPath.Types++    hs-source-dirs:   src+    other-modules:    Paths_strong_path+    default-language: Haskell2010+    ghc-options:      -Wall+    build-depends:+        base >=4.7 && <5,+        exceptions >=0.10.4 && <0.11,+        filepath >=1.4.2.1 && <1.5,+        path ==0.9.*,+        template-haskell >=2.16.0.0 && <2.17++test-suite strong-path-test+    type:             exitcode-stdio-1.0+    main-is:          TastyDiscoverDriver.hs+    hs-source-dirs:   test+    other-modules:+        PathTest+        StrongPath.FilePathTest+        StrongPath.InternalTest+        StrongPath.PathTest+        StrongPath.THTest+        StrongPathTest+        Test.Utils+        Paths_strong_path++    default-language: Haskell2010+    ghc-options:      -threaded -rtsopts -with-rtsopts=-N+    build-depends:+        base >=4.7 && <5,+        filepath >=1.4.2.1 && <1.5,+        path >=0.9.0 && <0.10,+        strong-path -any,+        tasty >=1.4.1 && <1.5,+        tasty-discover >=4.2.2 && <4.3,+        tasty-hspec >=1.1.6 && <1.2,+        tasty-quickcheck >=0.10.1.2 && <0.11
+ test/PathTest.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE QuasiQuotes #-}++module PathTest where++import Data.Maybe (fromJust)+import qualified Path as P+import qualified Path.Posix as PP+import qualified Path.Windows as PW+import qualified System.FilePath as FP+import Test.Tasty.Hspec++spec_Path :: Spec+spec_Path = do+  -- Just checking that Path behaves in a way that we expect it to behave.+  -- At earlier versions of Path (< 0.9.0) there were bugs which made some of the tests below fail.+  -- This way we ensure those bugs are fixed and don't return.++  it "Path.Windows.parseRelDir correctly parses Windows path" $ do+    fromJust (PW.parseRelDir ".\\") `shouldBe` fromJust (PW.parseRelDir "./")+    fromJust (PW.parseRelDir "a\\\\b\\") `shouldBe` fromJust (PW.parseRelDir "a/b/")+    fromJust (PW.parseRelDir "a\\b") `shouldBe` fromJust (PW.parseRelDir "a/b")+    PW.toFilePath (fromJust $ PW.parseRelDir "a\\b\\") `shouldBe` "a\\b\\"++  describe "Concatenation of System . paths works as expected" $ do+    let test lp rp ep =+          it (show lp ++ " </> " ++ show rp ++ " == " ++ show ep) $+            (lp P.</> rp) `shouldBe` ep+    test [P.reldir|.|] [P.reldir|.|] [P.reldir|.|]+    test [P.reldir|a|] [P.reldir|.|] [P.reldir|a|]+    test [P.reldir|.|] [P.reldir|a|] [P.reldir|a|]+    test [P.reldir|.|] [P.relfile|c.txt|] [P.relfile|c.txt|]++  describe "Concatenation of Win . paths works as expected" $ do+    let test lp rp ep =+          it (show lp ++ " </> " ++ show rp ++ " == " ++ show ep) $+            (lp PW.</> rp) `shouldBe` ep+    test [PW.reldir|.|] [PW.reldir|.|] [PW.reldir|.|]+    test [PW.reldir|.|] [PW.reldir|a|] [PW.reldir|a|]+    test [PW.reldir|a|] [PW.reldir|.|] [PW.reldir|a|]++  describe "Concatenation of Posix . paths works as expected" $ do+    let test lp rp ep =+          it (show lp ++ " </> " ++ show rp ++ " == " ++ show ep) $+            (lp PP.</> rp) `shouldBe` ep+    test [PP.reldir|.|] [PP.reldir|.|] [PP.reldir|.|]+    test [PP.reldir|.|] [PP.reldir|a|] [PP.reldir|a|]+    test [PP.reldir|a|] [PP.reldir|.|] [PP.reldir|a|]++  describe "Parsing rel path with .. at start should fail" $ do+    let test parser p =+          it (show p ++ " should unsuccessfully parse") $+            parser p `shouldBe` Nothing+    describe "for PW.parseRelDir" $ do+      test PW.parseRelDir "../a"+      test PW.parseRelDir "..\\a"+    describe "for P.parseRelDir" $ do+      test P.parseRelDir "../a"+      test P.parseRelDir $ ".." FP.</> "a"+    describe "for PP.parseRelDir" $ do+      test PP.parseRelDir "../a"
+ test/StrongPath/FilePathTest.hs view
@@ -0,0 +1,116 @@+module StrongPath.FilePathTest where++import Data.Maybe (fromJust)+import StrongPath.FilePath+import StrongPath.Internal+import qualified System.FilePath as FP+import qualified System.FilePath.Posix as FPP+import qualified System.FilePath.Windows as FPW+import Test.Tasty.Hspec+import Test.Utils++spec_StrongPathFilePath :: Spec+spec_StrongPathFilePath = do+  describe "Parsing FilePath into StrongPath" $ do+    let runTest fpToParseIntoExpectedFp parser fpToParse =+          let expectedFp = fpToParseIntoExpectedFp fpToParse+           in it (fpToParse ++ " should parse into " ++ expectedFp) $ do+                let sp = fromJust $ parser fpToParse+                toFilePath sp `shouldBe` expectedFp+    let runTestRel fpToParseIntoExpectedFp parser fpToParse expectedNumParentDirs =+          let expectedFp = fpToParseIntoExpectedFp fpToParse+           in it (fpToParse ++ " should parse into " ++ expectedFp) $ do+                let sp = fromJust $ parser fpToParse+                toFilePath sp `shouldBe` expectedFp+                relPathNumParentDirs sp `shouldBe` expectedNumParentDirs++    describe "into standard System" $ do+      describe "into base Rel" $ do+        describe "captures one or multiple ../ at start of relative path" $ do+          let test = runTestRel id+          test parseRelDir (posixToSystemFp "../../a/b/") 2+          test parseRelDir (posixToSystemFp "../") 1+          test parseRelDir (posixToSystemFp "../../") 2+          test parseRelDir (posixToSystemFp "./") 0+          test parseRelFile (posixToSystemFp "../a/b.txt") 1+        describe "can parse from system FilePath" $ do+          let test = runTestRel id+          test parseRelDir (posixToSystemFp "../a/b/") 1+          test parseRelDir (posixToSystemFp "a/b/") 0+          test parseRelFile (posixToSystemFp "../a/b.txt") 1+          test parseRelFile (posixToSystemFp "a/b.txt") 0+        describe "can parse from posix FilePath" $ do+          let test = runTestRel posixToSystemFp+          test parseRelDir "../a/b/" 1+          test parseRelDir "a/b/" 0+          test parseRelFile "../a/b.txt" 1+          test parseRelFile "a/b.txt" 0+      describe "into base Abs" $ do+        describe "can parse from system FilePath" $ do+          let test = runTest id+          test parseAbsDir (systemFpRoot FP.</> posixToSystemFp "a/b/")+          test parseAbsFile (systemFpRoot FP.</> posixToSystemFp "a/b.txt")+        describe "can parse from FilePath with system root and posix separators" $ do+          let test = runTest posixToSystemFp+          test parseAbsDir (systemFpRoot FP.</> "a/b/")+          test parseAbsFile (systemFpRoot FP.</> "a/b.txt")++    describe "into standard Windows" $ do+      describe "into base Rel" $ do+        describe "captures one or multiple ../ at start of relative path" $ do+          let test = runTestRel posixToWindowsFp+          test parseRelDirW (posixToSystemFp "../../a/b/") 2+          test parseRelFileW (posixToSystemFp "../a/b.txt") 1+        describe "can parse from windows FilePath" $ do+          let test = runTestRel id+          test parseRelDirW "..\\a\\b\\" 1+          test parseRelDirW "a\\b\\" 0+          test parseRelFileW "..\\a\\b.txt" 1+          test parseRelFileW "..\\..\\a\\b.txt" 2+          test parseRelFileW "a\\b.txt" 0+        describe "can parse from posix FilePath" $ do+          let test = runTestRel posixToWindowsFp+          test parseRelDirW "../a/b/" 1+          test parseRelDirW "a/b/" 0+          test parseRelFileW "../a/b.txt" 1+          test parseRelFileW "a/b.txt" 0+      describe "into base Abs" $ do+        describe "can parse from windows FilePath" $ do+          let test = runTest id+          test parseAbsDirW "C:\\a\\b\\"+          test parseAbsFileW "C:\\a\\b.txt"+        describe "can parse from FilePath with windows root and Posix separators" $ do+          let test = runTest posixToWindowsFp+          test parseAbsDirW "C:\\a/b/"+          test parseAbsFileW "C:\\a/b.txt"++    describe "into standard Posix" $ do+      describe "into base Rel" $ do+        describe "captures one or multiple ../ at start of relative path" $ do+          let test = runTestRel id+          test parseRelDirP "../../a/b/" 2+          test parseRelFileP "../a/b.txt" 1+        describe "can parse from posix FilePath" $ do+          let test = runTestRel id+          test parseRelDirP "../a/b/" 1+          test parseRelDirP "a/b/" 0+          test parseRelFileP "a/b.txt" 0+      describe "into base Abs" $ do+        describe "can parse from posix FilePath" $ do+          let test = runTest id+          test parseAbsDirP "/a/b/"+          test parseAbsFileP "/a/b.txt"++  describe "toFilePath correctly transforms StrongPath into FilePath" $ do+    let test msp efp =+          it ("toFilePath (" ++ show msp ++ ") = " ++ efp) $+            toFilePath (fromJust msp) `shouldBe` efp+    -- TODO: Add more tests.+    test (parseRelDir $ posixToSystemFp "../") (posixToSystemFp "../")+    test (parseRelDir $ posixToSystemFp "a/b") (posixToSystemFp "a/b/")+    test (parseRelFile $ posixToSystemFp "../../foo.txt") (posixToSystemFp "../../foo.txt")+    test (parseRelDirW "../") "..\\"+    test (parseRelDirP "../") "../"++systemSpRoot :: Path' Abs Dir'+systemSpRoot = fromJust $ parseAbsDir systemFpRoot
+ test/StrongPath/InternalTest.hs view
@@ -0,0 +1,24 @@+module StrongPath.InternalTest where++import StrongPath.Internal+  ( RelPathPrefix (..),+    extractRelPathPrefix,+  )+import qualified System.FilePath as FP+import qualified System.FilePath.Posix as FPP+import qualified System.FilePath.Windows as FPW+import Test.Tasty.Hspec++spec_StrongPathInternal :: Spec+spec_StrongPathInternal = do+  describe "extractRelPathPrefix correctly extracts prefix from rel FilePath." $ do+    it "when path starts with multiple ../" $ do+      extractRelPathPrefix [FPP.pathSeparator] "../../" `shouldBe` (ParentDir 2, "")+      extractRelPathPrefix [FPP.pathSeparator] "../.." `shouldBe` (ParentDir 2, "")+      extractRelPathPrefix [FP.pathSeparator] ".." `shouldBe` (ParentDir 1, "")+      extractRelPathPrefix [FP.pathSeparator, FPP.pathSeparator] "../../../a/b" `shouldBe` (ParentDir 3, "a/b")+      extractRelPathPrefix [FPW.pathSeparator] "..\\a\\b" `shouldBe` (ParentDir 1, "a\\b")+    it "when path does not start with ../" $ do+      extractRelPathPrefix [FPP.pathSeparator] "a/b" `shouldBe` (NoPrefix, "a/b")+      extractRelPathPrefix [FP.pathSeparator] "b" `shouldBe` (NoPrefix, "b")+      extractRelPathPrefix [FP.pathSeparator] "." `shouldBe` (NoPrefix, ".")
+ test/StrongPath/PathTest.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE QuasiQuotes #-}++module StrongPath.PathTest where++import Data.Maybe (fromJust)+import qualified Path as P+import qualified Path.Posix as PP+import qualified Path.Windows as PW+import StrongPath.Path+import qualified System.FilePath as FP+import Test.Tasty.Hspec+import Test.Utils++spec_StrongPathPath :: Spec+spec_StrongPathPath = do+  it "Conversion from Path to StrongPath and back returns original value." $ do+    let test pack unpack path = unpack (pack path) == path `shouldBe` True+    test fromPathRelFile toPathRelFile [P.relfile|some/file.txt|]+    test fromPathRelDir toPathRelDir [P.reldir|some/dir/|]+    test fromPathAbsFile toPathAbsFile $ systemPathRoot P.</> [P.relfile|some/file.txt|]+    test fromPathAbsDir toPathAbsDir $ systemPathRoot P.</> [P.reldir|some/dir|]++    test fromPathRelFileP toPathRelFileP [PP.relfile|some/file.txt|]+    test fromPathRelDirP toPathRelDirP [PP.reldir|some/dir/|]+    test fromPathAbsFileP toPathAbsFileP [PP.absfile|/some/file.txt|]+    test fromPathAbsDirP toPathAbsDirP [PP.absdir|/some/dir|]++    test fromPathRelFileW toPathRelFileW [PW.relfile|some\file.txt|]+    test fromPathRelDirW toPathRelDirW [PW.reldir|some\dir\|]+    test fromPathAbsFileW toPathAbsFileW [PW.absfile|C:\some\file.txt|]+    test fromPathAbsDirW toPathAbsDirW [PW.absdir|C:\some\dir|]++systemPathRoot :: P.Path P.Abs P.Dir+systemPathRoot = fromJust $ P.parseAbsDir systemFpRoot
+ test/StrongPath/THTest.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE QuasiQuotes #-}++module StrongPath.THTest where++import Data.Maybe (fromJust)+import qualified StrongPath as SP+import StrongPath.TH+import Test.Tasty.Hspec++spec_StrongPathTH :: Spec+spec_StrongPathTH = do+  describe "Quasi quoters generate expected values with expected types" $ do+    it "System" $ do+      [reldir|foo/bar/|] `shouldBe` fromJust (SP.parseRelDir "foo/bar/")+      [relfile|../foo/bar|] `shouldBe` fromJust (SP.parseRelFile "../foo/bar")+    -- NOTE: I don't test absdir and absfile here because I can't get that piece of code+    -- compile on both Win and Linux.++    it "Posix" $ do+      [reldirP|foo/bar/|] `shouldBe` fromJust (SP.parseRelDirP "foo/bar/")+      [relfileP|../foo/bar|] `shouldBe` fromJust (SP.parseRelFileP "../foo/bar")+      [absdirP|/foo/bar/|] `shouldBe` fromJust (SP.parseAbsDirP "/foo/bar/")+      [absfileP|/foo/bar|] `shouldBe` fromJust (SP.parseAbsFileP "/foo/bar")++    it "Windows" $ do+      [reldirW|foo/bar/|] `shouldBe` fromJust (SP.parseRelDirW "foo/bar/")+      [relfileW|..\foo/bar|] `shouldBe` fromJust (SP.parseRelFileW "..\\foo/bar")+      [absdirW|C:\foo\bar\|] `shouldBe` fromJust (SP.parseAbsDirW "C:\\foo\\bar\\")+      [absfileW|C:\foo\bar|] `shouldBe` fromJust (SP.parseAbsFileW "C:\\foo\\bar")
+ test/StrongPathTest.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE QuasiQuotes #-}++module StrongPathTest where++import Data.Maybe (fromJust)+import StrongPath+import Test.Tasty.Hspec+import Test.Utils++data Bar++data Fizz++-- TODO: I should look into using QuickCheck to simplify / enhcance StrongPath tests,+--       it would probably be a good fit for some cases.++spec_StrongPath :: Spec+spec_StrongPath = do+  it "Example with Foo file and Bar, Fizz and Kokolo dirs" $ do+    let fooFileInBarDir = [relfile|foo.txt|] :: Path' (Rel Bar) File'+    let barDirInFizzDir = [reldir|kokolo/bar|] :: Path' (Rel Fizz) (Dir Bar)+    let fizzDir = systemSpRoot </> [reldir|fizz|] :: Path' Abs (Dir Fizz)+    let fooFile = (fizzDir </> barDirInFizzDir </> fooFileInBarDir) :: Path' Abs File'+    let fooFileInFizzDir = (barDirInFizzDir </> fooFileInBarDir) :: Path' (Rel Fizz) File'+    fromAbsFile fooFile `shouldBe` posixToSystemFp "/fizz/kokolo/bar/foo.txt"+    fromRelFile fooFileInFizzDir `shouldBe` posixToSystemFp "kokolo/bar/foo.txt"++  describe "`parent` correctly returns parent dir" $ do+    let test msp mexpectedSp =+          it ("parent (" ++ show msp ++ ") == " ++ show mexpectedSp) $ do+            let sp = fromJust msp+            let expectedSp = fromJust mexpectedSp+            parent sp `shouldBe` expectedSp+    let tests relDirParser relFileParser absDirParser absFileParser root = do+          test (relDirParser "a/b") (relDirParser "a")+          test (relDirParser "../a") (relDirParser "..")+          test (relDirParser "..") (relDirParser "../..")+          test (relDirParser ".") (relDirParser "..")+          test (relFileParser "a.txt") (relDirParser ".")+          test (relFileParser "../a.txt") (relDirParser "..")+          test (relFileParser "a/b.txt") (relDirParser "a")+          test (absDirParser $ root ++ "a/b") (absDirParser $ root ++ "a")+          test (absDirParser root) (absDirParser root)+          test (absFileParser $ root ++ "a/b.txt") (absDirParser $ root ++ "a")+    describe "when standard is System" $+      tests parseRelDir parseRelFile parseAbsDir parseAbsFile systemFpRoot+    describe "when standard is Windows" $+      tests parseRelDirW parseRelFileW parseAbsDirW parseAbsFileW "C:\\"+    describe "when standard is Posix" $+      tests parseRelDirP parseRelFileP parseAbsDirP parseAbsFileP "/"++  describe "</> correctly concatenates two corresponding paths" $ do+    let test mlsp mrsp mexpectedSp =+          it (show mlsp ++ " </> " ++ show mrsp ++ " == " ++ show mexpectedSp) $ do+            let lsp = fromJust mlsp+            let rsp = fromJust mrsp+            let expectedSp = fromJust mexpectedSp+            (lsp </> rsp) `shouldBe` expectedSp+    let tests relDirParser relFileParser absDirParser absFileParser root = do+          test (relDirParser "a/b") (relFileParser "c.txt") (relFileParser "a/b/c.txt")+          test (relDirParser "a/b") (relFileParser "../c.txt") (relFileParser "a/c.txt")+          test (relDirParser "..") (relFileParser "../c.txt") (relFileParser "../../c.txt")+          test (relDirParser "..") (relDirParser "..") (relDirParser "../..")+          test (relDirParser ".") (relDirParser "../a") (relDirParser "../a")+          test (relDirParser ".") (relDirParser ".") (relDirParser ".")+          test (relDirParser "a/b") (relDirParser "c/d") (relDirParser "a/b/c/d")+          test (relDirParser "../a/b") (relDirParser "c/d") (relDirParser "../a/b/c/d")+          test (absDirParser $ root ++ "a/b") (relFileParser "c.txt") (absFileParser $ root ++ "a/b/c.txt")+          test (absDirParser $ root ++ "a/b") (relFileParser "../c.txt") (absFileParser $ root ++ "a/c.txt")+          test (absDirParser $ root ++ "a") (relDirParser "../b") (absDirParser $ root ++ "b")+          test (absDirParser $ root ++ "a/b") (relDirParser "../../../") (absDirParser root)+    describe "when standard is System" $+      tests parseRelDir parseRelFile parseAbsDir parseAbsFile systemFpRoot+    describe "when standard is Windows" $+      tests parseRelDirW parseRelFileW parseAbsDirW parseAbsFileW "C:\\"+    describe "when standard is Posix" $+      tests parseRelDirP parseRelFileP parseAbsDirP parseAbsFileP "/"++  describe "relDirToPosix/relFileToPosix correctly converts any relative path to relative posix path" $ do+    describe "when strong path is relative dir" $ do+      let expectedPosixPath = [reldirP|test/dir/|]+      it "from standard Win" $+        fromJust (relDirToPosix [reldirW|test\dir\|])+          `shouldBe` expectedPosixPath+      it "from standard Posix" $+        fromJust (relDirToPosix [reldirP|test/dir/|])+          `shouldBe` expectedPosixPath+      it "from standard System" $+        fromJust (relDirToPosix [reldir|test/dir/|])+          `shouldBe` expectedPosixPath+    describe "correctly when strong path is relative file" $ do+      let expectedPosixPath = [relfileP|test/file|]+      it "from standard Win" $+        fromJust (relFileToPosix [relfileW|test\file|])+          `shouldBe` expectedPosixPath+      it "from standard Posix" $+        fromJust (relFileToPosix [relfileP|test/file|])+          `shouldBe` expectedPosixPath+      it "from standard System" $+        fromJust (relFileToPosix [relfileP|test/file|])+          `shouldBe` expectedPosixPath++systemSpRoot :: Path' Abs Dir'+systemSpRoot = fromJust $ parseAbsDir systemFpRoot
+ test/TastyDiscoverDriver.hs view
@@ -0,0 +1,5 @@+{-# OPTIONS_GHC -F -pgmF tasty-discover -optF --modules=*Test.hs #-}++-- -optF --modules=*Test.hs tells tasty-discover to pick up only files that match *Test.hs.+--   I did not do this for any strong reason so we can remove it in the future if we figure out+--   it is too restrictive.
+ test/Test/Utils.hs view
@@ -0,0 +1,28 @@+module Test.Utils+  ( systemFpRoot,+    posixToSystemFp,+    posixToWindowsFp,+  )+where++import System.FilePath as FP+import System.FilePath.Windows as FPW++systemFpRoot :: FilePath+systemFpRoot = if FP.pathSeparator == '\\' then "C:\\" else "/"++-- | Takes posix path and converts it into windows path if running on Windows or leaves as it is if on Unix.+posixToSystemFp :: FilePath -> FilePath+posixToSystemFp posixFp = maybeSystemRoot ++ systemFpRootless+  where+    maybeSystemRoot = if head posixFp == '/' then systemFpRoot else ""+    posixFpRootless = if head posixFp == '/' then tail posixFp else posixFp+    systemFpRootless = map (\c -> if c == '/' then FP.pathSeparator else c) posixFpRootless++-- | Takes posix path and converts it into windows path.+posixToWindowsFp :: FilePath -> FilePath+posixToWindowsFp posixFp = maybeWinRoot ++ winFpRootless+  where+    maybeWinRoot = if head posixFp == '/' then "C:\\" else ""+    posixFpRootless = if head posixFp == '/' then tail posixFp else posixFp+    winFpRootless = map (\c -> if c == '/' then FPW.pathSeparator else c) posixFpRootless