packages feed

strong-path 1.0.0.0 → 1.0.1.0

raw patch · 12 files changed

+246/−79 lines, 12 filesdep +hspecdep ~pathdep ~tasty-hspecdep ~template-haskellPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: hspec

Dependency ranges changed: path, tasty-hspec, template-haskell

API changes (from Hackage documentation)

- StrongPath.Internal: parseRelFP :: MonadThrow m => (p -> RelPathPrefix -> Path s (Rel d) t) -> [Char] -> (FilePath -> m p) -> FilePath -> m (Path s (Rel d) t)
+ StrongPath: basename :: Path s b t -> Path s (Rel d) t
+ StrongPath.Internal: parseRelDirFP :: MonadThrow m => (p -> RelPathPrefix -> Path s (Rel d1) (Dir d2)) -> [Char] -> (FilePath -> m p) -> FilePath -> m (Path s (Rel d1) (Dir d2))
+ StrongPath.Internal: parseRelFileFP :: MonadThrow m => (p -> RelPathPrefix -> Path s (Rel d) (File f)) -> [Char] -> (FilePath -> m p) -> FilePath -> m (Path s (Rel d) (File f))

Files

README.md view
@@ -1,6 +1,7 @@ # 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)+[![Documentation](https://img.shields.io/badge/Docs-Haddock-blue)](https://hackage.haskell.org/package/strong-path/docs/StrongPath.html) [![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)@@ -11,19 +12,17 @@  Without `StrongPath`: ```hs-getGitConfigPath :: IO FilePath-generateHtmlFromMarkdown :: FilePath -> IO FilePath+getBashProfile :: 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))+getBashProfile :: IO (Path System (Rel HomeDir) (File BashProfile)) ```  Simple but complete example: ```hs-import StrongPath (Path, System, Abs, Rel, File, Dir, (</>), parseAbsDir)+import StrongPath (Path, System, Abs, Dir, parseAbsDir)  data HomeDir @@ -51,7 +50,9 @@  ### Publishing to Hackage -`stack sdist` to build publishable .tar.gz., and then we need to upload it manually.+`stack sdist` to build publishable .tar.gz., and then we need to upload it manually to Hackage.++Check if Hackage correctly built the Haddock docs -> if not, you need to upload them manually (check Hackage webpage for instructions).  Make sure to update the version of package in package.yaml. 
src/StrongPath.hs view
@@ -3,21 +3,49 @@      -- | 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):+    -- Example of using "System.FilePath" vs using "StrongPath" to get the path to bash profile file (relative to the home directory):     ---    -- > getGitConfigPath :: IO FilePath+    -- > -- Using FilePath+    -- > getBashProfilePath :: IO FilePath     ---    -- > getGitConfigPath :: IO (Path System (Rel HomeDir) (File GitConfigFile))+    -- This leaves many questions open. Is returned path relative or absolute? If relative, what is it relative to? Is it normalized? Is it maybe invalid? What kind of separators (win, posix) does it use?     ---    -- Or, imagine stumbling onto this function:+    -- > -- Using StrongPath+    -- > getBashProfilePath :: IO (Path System (Rel HomeDir) (File BashProfile))     ---    -- > generateHtmlFromMarkdown :: FilePath -> IO FilePath+    -- With StrongPath, you can read from type that it is relative to home directory, you are guaranteed it is normalized and valid, and type also tells you it is using separators of the OS your program is running on.     ---    -- 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:+    -- Some more examples:     ---    -- > generateHtmlFromMarkdown :: Path System (Rel HomeDir) (File MarkdownFile) -> IO (Path System Abs (File HtmlFile))+    -- > -- System path to "foo" directory, relative to "bar" directory.+    -- > dirFooInDirBar :: Path System (Rel BarDir) (Dir FooDir)+    -- > dirFooInDirBar = [reldir|somedir/foo|]  -- This path is parsed during compile time, ensuring it is valid.+    -- >+    -- > -- Absolute system path to "bar" directory. `Path'` is just alias for `Path System`.+    -- > dirBarAbsPath :: Path' Abs (Dir BarDir)+    -- > dirBarAbsPath = [absdir|/bar/|]+    -- >+    -- > -- Absolute path to "foo" directory, calculated by concatenating two paths from above.+    -- > -- If path on the right was not relative to the path on the left, StrongPath would throw compile error upon concatenation.+    -- > dirFooAbsPath :: Path' 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.     --+    --+    --     -- 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@@ -201,35 +229,6 @@     -- > 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      -- |@@ -242,6 +241,20 @@     -- - 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.++    -- ** StrongPath in practice++    -- |+    -- - "StrongPath" is used extensively in [wasp-lang](https://github.com/wasp-lang/wasp/search?q=StrongPath).++    -- ** Similar libraries++    -- |+    -- - [path](https://hackage.haskell.org/package/path) - Inspiration for StrongPath. Has less information encoded in types than StrongPath but is therefore somewhat simpler to use.+    -- - [data-filepath](https://hackage.haskell.org/package/data-filepath) - Similar to `path`. Check https://github.com/commercialhaskell/path#data-filepath for detailed comparison to `path`.+    -- - [pathtype](https://hackage.haskell.org/package/pathtype) - Similar to `path`. Check https://github.com/commercialhaskell/path#pathtype for detailed comparison to `path`.+    -- - [paths](https://hackage.haskell.org/package/paths) - Focused on capturing if path is relative or absolute, and to what.+    -- - [hpath](https://hackage.haskell.org/package/hpath) - Uses ByteString under the hood (instead of String), written only for Posix, has no File/Dir distinction.      -- * API     module StrongPath.Types,
src/StrongPath/FilePath.hs view
@@ -103,27 +103,27 @@ 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+parseRelDir = parseRelDirFP RelDir [FP.pathSeparator, FPP.pathSeparator] P.parseRelDir -parseRelFile = parseRelFP RelFile [FP.pathSeparator, FPP.pathSeparator] P.parseRelFile+parseRelFile = parseRelFileFP 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+parseRelDirW = parseRelDirFP RelDirW [FPW.pathSeparator, FPP.pathSeparator] PW.parseRelDir -parseRelFileW = parseRelFP RelFileW [FPW.pathSeparator, FPP.pathSeparator] PW.parseRelFile+parseRelFileW = parseRelFileFP 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+parseRelDirP = parseRelDirFP RelDirP [FPP.pathSeparator] PP.parseRelDir -parseRelFileP = parseRelFP RelFileP [FPP.pathSeparator] PP.parseRelFile+parseRelFileP = parseRelFileFP RelFileP [FPP.pathSeparator] PP.parseRelFile  parseAbsDirP fp = fromPathAbsDirP <$> PP.parseAbsDir fp 
src/StrongPath/Internal.hs view
@@ -1,8 +1,30 @@ {-# LANGUAGE DeriveLift #-} -module StrongPath.Internal where+module StrongPath.Internal+  ( Path (..),+    RelPathPrefix (..),+    Abs,+    Rel,+    Dir,+    File,+    Posix,+    Windows,+    System,+    Path',+    File',+    Dir',+    Rel',+    parseRelFileFP,+    parseRelDirFP,+    impossible,+    prefixNumParentDirs,+    relPathNumParentDirs,+    relPathPrefix,+    extractRelPathPrefix,+  )+where -import Control.Monad.Catch (MonadThrow)+import Control.Monad.Catch (MonadThrow, throwM) import Language.Haskell.TH.Syntax (Lift) import qualified Path as P import qualified Path.Posix as PP@@ -22,7 +44,22 @@ -- > Path System Abs (Dir HomeDir) -- > Path Posix (Rel ProjectRoot) (File ()) data Path s b t-  = -- System+  = -- NOTE: Relative paths can be sometimes be tricky when being reasoned about in the internal library code,+    --   when reconstructing them and working with them, due to RelPathPrefix and edge cases like ".", "..".+    --+    --   For example if original relative path was "..", we will parse it into RelDir "." ParentDir 1.+    --   Then it is important to be aware that this should be regarded as "..", and not "../.".+    --   In some functions like `basename` it is important to be aware of this.+    --+    --   Also, Path.Path can't hold empty path, so we can count on paths not to be empty.+    --+    --   And Path.Path can't store "." as file, only as dir, so that is also good to know.+    --+    --   I wonder if we could find a better way to represent path internaly, a way which would encode+    --   tricky situations explicitly, or maybe some kind of lower-level interface around it that would encode+    --   things like "paths can't be empty", "dir can be '.' but file can't", and similar.+    --   But maybe the solution would just be too complicated.+    -- System     RelDir (P.Path P.Rel P.Dir) RelPathPrefix   | RelFile (P.Path P.Rel P.File) RelPathPrefix   | AbsDir (P.Path P.Abs P.Dir)@@ -105,18 +142,42 @@ -- it is convenient to use unit @()@. type File' = File () --- TODO: Extract `parseRelFP` and `extractRelPathPrefix` into StrongPath.FilePath.Internals?+-- TODO: Extract `parseRelFileFP`, `parseRelDirFP`, `parseRelFP` and `extractRelPathPrefix` into StrongPath.FilePath.Internals?++parseRelFileFP ::+  MonadThrow m =>+  (p -> RelPathPrefix -> Path s (Rel d) (File f)) ->+  [Char] ->+  (FilePath -> m p) ->+  FilePath ->+  m (Path s (Rel d) (File f))+parseRelFileFP _ _ _ "" = throwM (P.InvalidRelFile "")+parseRelFileFP constructor validSeparators pathParser fp = parseRelFP constructor validSeparators pathParser fp++parseRelDirFP ::+  MonadThrow m =>+  (p -> RelPathPrefix -> Path s (Rel d1) (Dir d2)) ->+  [Char] ->+  (FilePath -> m p) ->+  FilePath ->+  m (Path s (Rel d1) (Dir d2))+parseRelDirFP _ _ _ "" = throwM (P.InvalidRelDir "")+parseRelDirFP constructor validSeparators pathParser fp = parseRelFP constructor validSeparators pathParser fp++-- Helper function for the parseRelFileFP and parseRelDirFP, should not be used called directly but only+-- by parseRelFileFP and parseRelDirFP. parseRelFP ::   MonadThrow m =>-  (p -> RelPathPrefix -> Path s (Rel d) t) ->+  (p -> RelPathPrefix -> Path s (Rel d1) t) ->   [Char] ->   (FilePath -> m p) ->   FilePath ->-  m (Path s (Rel d) t)-parseRelFP constructor validSeparators pathParser fp =+  m (Path s (Rel d1) t)+parseRelFP _ _ _ "" = error "can't parse empty path"+parseRelFP constructor validSeparators pathParser fp = do   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''+  (\p -> constructor p prefix) <$> pathParser fp''  -- | Extracts a multiple "../" from start of the file path. --   If path is completely ../../.., also handles the last one.
src/StrongPath/Operations.hs view
@@ -1,9 +1,11 @@+{-# LANGUAGE QuasiQuotes #-} {-# OPTIONS_HADDOCK hide #-}  module StrongPath.Operations   ( -- ** Operations     (</>),     parent,+    basename,      -- ** Casting     castRel,@@ -141,6 +143,45 @@   let (AbsDirP lp') = iterate parent lsp !! prefixNumParentDirs rprefix    in AbsDirP (lp' PP.</> rp) _ </> _ = impossible++-- | Returns the most right member of the path once split by separators.+-- If path is pointing to file, basename will be name of the file.+-- If path is pointing to a directory, basename will be name of the directory.+-- Check examples below to see how are special paths like @..@, @.@, @\/@ and similar resolved.+--+-- Examples (pseudocode):+-- > basename "/a/b/c" == "c"+-- > basename "file.txt" == "file.txt"+-- > basename "../file.txt" == "file.txt"+-- > basename "../.." == ".."+-- > basename ".." == ".."+-- > basename "." == "."+-- > basename "/" == "."+basename :: Path s b t -> Path s (Rel d) t+-- System+basename (RelDir p pr) =+  if p == [P.reldir|.|] && pr /= NoPrefix+    then RelDir p (ParentDir 1)+    else RelDir (P.dirname p) NoPrefix+basename (RelFile p _) = RelFile (P.filename p) NoPrefix+basename (AbsDir p) = RelDir (P.dirname p) NoPrefix+basename (AbsFile p) = RelFile (P.filename p) NoPrefix+-- Posix+basename (RelDirP p pr) =+  if p == [PP.reldir|.|] && pr /= NoPrefix+    then RelDirP p (ParentDir 1)+    else RelDirP (PP.dirname p) NoPrefix+basename (RelFileP p _) = RelFileP (PP.filename p) NoPrefix+basename (AbsDirP p) = RelDirP (PP.dirname p) NoPrefix+basename (AbsFileP p) = RelFileP (PP.filename p) NoPrefix+-- Windows+basename (RelDirW p pr) =+  if p == [PW.reldir|.|] && pr /= NoPrefix+    then RelDirW p (ParentDir 1)+    else RelDirW (PW.dirname p) NoPrefix+basename (RelFileW p _) = RelFileW (PW.filename p) NoPrefix+basename (AbsDirW p) = RelDirW (PW.dirname p) NoPrefix+basename (AbsFileW p) = RelFileW (PW.filename p) NoPrefix  -- | Enables you to redefine which dir is the path relative to. castRel :: Path s (Rel d1) a -> Path s (Rel d2) a
strong-path.cabal view
@@ -1,6 +1,6 @@ cabal-version:      1.12 name:               strong-path-version:            1.0.0.0+version:            1.0.1.0 license:            MIT license-file:       LICENSE copyright:          2020 Martin Sosic@@ -40,8 +40,8 @@         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+        path >=0.9.0 && <0.10,+        template-haskell >=2.17.0.0 && <2.18  test-suite strong-path-test     type:             exitcode-stdio-1.0@@ -62,9 +62,10 @@     build-depends:         base >=4.7 && <5,         filepath >=1.4.2.1 && <1.5,+        hspec >=2.8.2 && <2.9,         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-hspec ==1.2.*,         tasty-quickcheck >=0.10.1.2 && <0.11
test/PathTest.hs view
@@ -7,10 +7,12 @@ import qualified Path.Posix as PP import qualified Path.Windows as PW import qualified System.FilePath as FP-import Test.Tasty.Hspec+import Test.Hspec+import Test.Tasty (TestTree)+import Test.Tasty.Hspec (testSpec) -spec_Path :: Spec-spec_Path = do+test_Path :: IO TestTree+test_Path = testSpec "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.
test/StrongPath/FilePathTest.hs view
@@ -6,11 +6,13 @@ 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.Hspec+import Test.Tasty (TestTree)+import Test.Tasty.Hspec (testSpec) import Test.Utils -spec_StrongPathFilePath :: Spec-spec_StrongPathFilePath = do+test_StrongPathFilePath :: IO TestTree+test_StrongPathFilePath = testSpec "StrongPath.FilePath" $ do   describe "Parsing FilePath into StrongPath" $ do     let runTest fpToParseIntoExpectedFp parser fpToParse =           let expectedFp = fpToParseIntoExpectedFp fpToParse@@ -111,6 +113,21 @@     test (parseRelFile $ posixToSystemFp "../../foo.txt") (posixToSystemFp "../../foo.txt")     test (parseRelDirW "../") "..\\"     test (parseRelDirP "../") "../"++  it "Parsing empty paths should fail" $ do+    let test parser p = parser p `shouldBe` Nothing+    test parseRelDir ""+    test parseRelFile ""+    test parseAbsDir ""+    test parseAbsFile ""+    test parseRelDirP ""+    test parseRelFileP ""+    test parseAbsDirP ""+    test parseAbsFileP ""+    test parseRelDirW ""+    test parseRelFileW ""+    test parseAbsDirW ""+    test parseAbsFileW ""  systemSpRoot :: Path' Abs Dir' systemSpRoot = fromJust $ parseAbsDir systemFpRoot
test/StrongPath/InternalTest.hs view
@@ -7,10 +7,12 @@ 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.Hspec+import Test.Tasty (TestTree)+import Test.Tasty.Hspec (testSpec) -spec_StrongPathInternal :: Spec-spec_StrongPathInternal = do+test_StrongPathInternal :: IO TestTree+test_StrongPathInternal = testSpec "StrongPath.Internal" $ do   describe "extractRelPathPrefix correctly extracts prefix from rel FilePath." $ do     it "when path starts with multiple ../" $ do       extractRelPathPrefix [FPP.pathSeparator] "../../" `shouldBe` (ParentDir 2, "")
test/StrongPath/PathTest.hs view
@@ -8,11 +8,13 @@ import qualified Path.Windows as PW import StrongPath.Path import qualified System.FilePath as FP-import Test.Tasty.Hspec+import Test.Hspec+import Test.Tasty (TestTree)+import Test.Tasty.Hspec (testSpec) import Test.Utils -spec_StrongPathPath :: Spec-spec_StrongPathPath = do+test_StrongPathPath :: IO TestTree+test_StrongPathPath = testSpec "StrongPath.Path" $ 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/StrongPath/THTest.hs view
@@ -5,10 +5,12 @@ import Data.Maybe (fromJust) import qualified StrongPath as SP import StrongPath.TH-import Test.Tasty.Hspec+import Test.Hspec+import Test.Tasty (TestTree)+import Test.Tasty.Hspec (testSpec) -spec_StrongPathTH :: Spec-spec_StrongPathTH = do+test_StrongPathTH :: IO TestTree+test_StrongPathTH = testSpec "StrongPath.TH" $ do   describe "Quasi quoters generate expected values with expected types" $ do     it "System" $ do       [reldir|foo/bar/|] `shouldBe` fromJust (SP.parseRelDir "foo/bar/")
test/StrongPathTest.hs view
@@ -4,7 +4,9 @@  import Data.Maybe (fromJust) import StrongPath-import Test.Tasty.Hspec+import Test.Hspec+import Test.Tasty (TestTree)+import Test.Tasty.Hspec (testSpec) import Test.Utils  data Bar@@ -14,8 +16,8 @@ -- 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+test_StrongPath :: IO TestTree+test_StrongPath = testSpec "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)@@ -59,6 +61,7 @@     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 "b/c.txt") (relFileParser "../b/c.txt")           test (relDirParser "..") (relFileParser "../c.txt") (relFileParser "../../c.txt")           test (relDirParser "..") (relDirParser "..") (relDirParser "../..")           test (relDirParser ".") (relDirParser "../a") (relDirParser "../a")@@ -69,6 +72,28 @@           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 "`basename` correctly returns filename/dirname" $ do+    let test msp mexpectedSp =+          it ("basename (" ++ show msp ++ ") == " ++ show mexpectedSp) $ do+            let sp = fromJust msp+            let expectedSp = fromJust mexpectedSp+            basename sp `shouldBe` expectedSp+    let tests relDirParser relFileParser absDirParser absFileParser root = do+          test (absFileParser $ root ++ "a/b/c.txt") (relFileParser "c.txt")+          test (absDirParser $ root ++ "a/b") (relDirParser "b")+          test (absDirParser root) (relDirParser ".")+          test (relFileParser "file.txt") (relFileParser "file.txt")+          test (relFileParser "../file.txt") (relFileParser "file.txt")+          test (relDirParser ".") (relDirParser ".")+          test (relDirParser "..") (relDirParser "..")+          test (relDirParser "../..") (relDirParser "..")     describe "when standard is System" $       tests parseRelDir parseRelFile parseAbsDir parseAbsFile systemFpRoot     describe "when standard is Windows" $