From aac976708b380a8ce77c1a10fc36660c457bdfca Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 9 Feb 2018 16:35:13 +0100 Subject: [PATCH 01/59] Add kinds RelOrAbs FileOrDir SandboxedOrNot fixes #28 --- src/Data/Path/Pathy.purs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index af4c12b..31f18ba 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -80,23 +80,29 @@ import Data.Tuple (Tuple(..), fst, snd) import Unsafe.Coerce (unsafeCoerce) +foreign import kind RelOrAbs + +foreign import kind FileOrDir + +foreign import kind SandboxedOrNot + -- | The (phantom) type of relative paths. -foreign import data Rel :: Type +foreign import data Rel :: RelOrAbs -- | The (phantom) type of absolute paths. -foreign import data Abs :: Type +foreign import data Abs :: RelOrAbs -- | The (phantom) type of files. -foreign import data File :: Type +foreign import data File :: FileOrDir -- | The (phantom) type of directories. -foreign import data Dir :: Type +foreign import data Dir :: FileOrDir -- | The (phantom) type of unsandboxed paths. -foreign import data Unsandboxed :: Type +foreign import data Unsandboxed :: SandboxedOrNot -- | The (phantom) type of sandboxed paths. -foreign import data Sandboxed :: Type +foreign import data Sandboxed :: SandboxedOrNot -- | A newtype around a file name. newtype FileName = FileName String @@ -130,7 +136,7 @@ runDirName (DirName name) = name -- | `parentDir' rootDir`, or by parsing an equivalent string such as `/../`, -- | but such paths are marked as unsandboxed, and may not be rendered to strings -- | until they are first sandboxed to some directory. -data Path a b s +data Path (a :: RelOrAbs) (b :: FileOrDir) (s :: SandboxedOrNot) = Current | Root | ParentIn (Path a b s) From 1aec6005014b51c82b30734f69d17b95d36d14fd Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 9 Feb 2018 17:43:49 +0100 Subject: [PATCH 02/59] Try to change FileName and DirName to Name FileOrDir --- src/Data/Path/Pathy.purs | 119 ++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 65 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 31f18ba..b0cc109 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -4,10 +4,9 @@ module Data.Path.Pathy , AbsFile , AbsPath , Dir - , DirName(..) + , Name(..) , Escaper(..) , File - , FileName(..) , Path , AnyPath , Rel @@ -22,8 +21,7 @@ module Data.Path.Pathy , (<.>) , parentAppend , (<..>) - , runDirName - , runFileName + , runName , canonicalize , changeExtension , currentDir @@ -105,18 +103,11 @@ foreign import data Unsandboxed :: SandboxedOrNot foreign import data Sandboxed :: SandboxedOrNot -- | A newtype around a file name. -newtype FileName = FileName String +newtype Name (n :: FileOrDir) = Name String --- | Unwraps the `FileName` newtype. -runFileName :: FileName -> String -runFileName (FileName name) = name - --- | A newtype around a directory name. -newtype DirName = DirName String - --- | Unwraps the `DirName` newtype. -runDirName :: DirName -> String -runDirName (DirName name) = name +-- | Unwraps the `Name` newtype. +runName :: forall a. Name a -> String +runName (Name name) = name -- | A type that describes a Path. All flavors of paths are described by this -- | type, whether they are absolute or relative paths, whether they @@ -140,8 +131,8 @@ data Path (a :: RelOrAbs) (b :: FileOrDir) (s :: SandboxedOrNot) = Current | Root | ParentIn (Path a b s) - | DirIn (Path a b s) DirName - | FileIn (Path a b s) FileName + | DirIn (Path a Dir s) (Name Dir) + | FileIn (Path a Dir s) (Name File) -- | A type describing a file whose location is given relative to some other, -- | unspecified directory (referred to as the "current directory"). @@ -190,53 +181,53 @@ posixEscaper = Escaper $ -- | Creates a path which points to a relative file of the specified name. file :: forall s. String -> Path Rel File s -file f = file' (FileName f) +file f = file' (Name f) -- | Creates a path which points to a relative file of the specified name. -file' :: forall s. FileName -> Path Rel File s +file' :: forall s. Name File -> Path Rel File s file' f = FileIn Current f -- | Retrieves the name of a file path. -fileName :: forall a s. Path a File s -> FileName +fileName :: forall a s. Path a File s -> Name File fileName (FileIn _ f) = f -fileName _ = FileName "" +fileName _ = Name "" -- | Retrieves the extension of a file name. -extension :: FileName -> String -extension (FileName f) = case S.lastIndexOf (S.Pattern ".") f of +extension :: Name File -> String +extension (Name f) = case S.lastIndexOf (S.Pattern ".") f of Just x -> S.drop (x + 1) f Nothing -> "" -- | Drops the extension on a file name. -dropExtension :: FileName -> FileName -dropExtension (FileName n) = case S.lastIndexOf (S.Pattern ".") n of - Just x -> FileName $ S.take x n - Nothing -> FileName n +dropExtension :: Name File -> Name File +dropExtension (Name n) = case S.lastIndexOf (S.Pattern ".") n of + Just x -> Name $ S.take x n + Nothing -> Name n -- | Changes the extension on a file name. -changeExtension :: (String -> String) -> FileName -> FileName +changeExtension :: (String -> String) -> Name File -> Name File changeExtension f nm = update (f $ extension nm) (dropExtension nm) where update "" n = n - update ext (FileName n) = FileName $ n <> "." <> ext + update ext (Name n) = Name $ n <> "." <> ext -- | Creates a path which points to a relative directory of the specified name. dir :: forall s. String -> Path Rel Dir s -dir d = dir' (DirName d) +dir d = dir' (Name d) -- | Creates a path which points to a relative directory of the specified name. -dir' :: forall s. DirName -> Path Rel Dir s +dir' :: forall s. Name Dir -> Path Rel Dir s dir' d = DirIn Current d -- | Retrieves the name of a directory path. Not all paths have such a name, -- | for example, the root or current directory. -dirName :: forall a s. Path a Dir s -> Maybe DirName +dirName :: forall a s. Path a Dir s -> Maybe (Name Dir) dirName p = case canonicalize p of DirIn _ d -> Just d _ -> Nothing -pathName :: forall b s. AnyPath b s -> Either (Maybe DirName) FileName +pathName :: forall b s. AnyPath b s -> Either (Maybe (Name Dir)) (Name File) pathName = bimap dirName fileName -- | Given a directory path, appends either a file or directory to the path. @@ -299,14 +290,14 @@ isRelative = not <<< isAbsolute peel :: forall a b s . Path a b s - -> Maybe (Tuple (Path a Dir s) (Either DirName FileName)) + -> Maybe (Tuple (Path a Dir s) (Name b)) peel Current = Nothing peel Root = Nothing peel p@(ParentIn _) = case canonicalize' p of Tuple true p' -> peel p' _ -> Nothing -peel (DirIn p d) = Just $ Tuple (unsafeCoerceType p) (Left d) -peel (FileIn p f) = Just $ Tuple (unsafeCoerceType p) (Right f) +peel (DirIn p (Name d)) = Just $ Tuple p (Name d) +peel (FileIn p (Name f)) = Just $ Tuple p (Name f) -- | Determines if the path refers to a directory. maybeDir :: forall a b s. Path a b s -> Maybe (Path a Dir s) @@ -314,7 +305,7 @@ maybeDir Current = Just Current maybeDir Root = Just Root maybeDir (ParentIn p) = Just $ ParentIn (unsafeCoerceType p) maybeDir (FileIn _ _) = Nothing -maybeDir (DirIn p d) = Just $ DirIn (unsafeCoerceType p) d +maybeDir (DirIn p d) = Just $ DirIn p d -- | Determines if the path refers to a file. maybeFile :: forall a b s. Path a b s -> Maybe (Path a File s) @@ -380,13 +371,13 @@ rootDir :: forall s. Path Abs Dir s rootDir = Root -- | Renames a file path. -renameFile :: forall a s. (FileName -> FileName) -> Path a File s -> Path a File s +renameFile :: forall a s. (Name File -> Name File) -> Path a File s -> Path a File s renameFile f (FileIn p f0) = FileIn p (f f0) renameFile _ p = p -- | Renames a directory path. Note: This is a simple rename of the terminal -- | directory name, not a "move". -renameDir :: forall a s. (DirName -> DirName) -> Path a Dir s -> Path a Dir s +renameDir :: forall a s. (Name Dir -> Name Dir) -> Path a Dir s -> Path a Dir s renameDir f (DirIn p d) = DirIn p (f d) renameDir _ p = p @@ -398,8 +389,8 @@ canonicalize = snd <<< canonicalize' canonicalize' :: forall a b s. Path a b s -> Tuple Boolean (Path a b s) canonicalize' Current = Tuple false Current canonicalize' Root = Tuple false Root -canonicalize' (ParentIn (FileIn p f)) = Tuple true (snd $ canonicalize' p) -canonicalize' (ParentIn (DirIn p f)) = Tuple true (snd $ canonicalize' p) +canonicalize' (ParentIn (FileIn p f)) = Tuple true (unsafeCoerceType $ snd $ canonicalize' p) +canonicalize' (ParentIn (DirIn p f)) = Tuple true (unsafeCoerceType $ snd $ canonicalize' p) canonicalize' (ParentIn p) = case canonicalize' p of Tuple changed p' -> let p'' = ParentIn p' @@ -410,13 +401,14 @@ canonicalize' (DirIn p d) = flip DirIn d <$> canonicalize' p unsafePrintPath' :: forall a b s. Escaper -> Path a b s -> String unsafePrintPath' r = go where + go :: forall a' b' s'. Path a' b' s' -> String go Current = "./" go Root = "/" go (ParentIn p) = go p <> "../" - go (DirIn p@(FileIn _ _ ) (DirName d)) = go p <> "/" <> escape d <> "/" -- dir inside a file - go (DirIn p (DirName d)) = go p <> escape d <> "/" -- dir inside a dir - go (FileIn p@(FileIn _ _) (FileName f)) = go p <> "/" <> escape f -- file inside a file - go (FileIn p (FileName f)) = go p <> escape f + go (DirIn p@(FileIn _ _ ) (Name d)) = go p <> "/" <> escape d <> "/" -- dir inside a file + go (DirIn p (Name d)) = go p <> escape d <> "/" -- dir inside a dir + go (FileIn p@(FileIn _ _) (Name f)) = go p <> "/" <> escape f -- file inside a file + go (FileIn p (Name f)) = go p <> escape f escape = runEscaper r unsafePrintPath :: forall a b s. Path a b s -> String @@ -455,6 +447,7 @@ relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) | otherwise = case peel cp1 of Just (Tuple cp1' e) -> flip () (either (DirIn Current) (FileIn Current) e) <$> relativeTo' cp1' cp2 + -- e is not either so it fails here Nothing -> Nothing -- | Attempts to sandbox a path relative to some directory. If successful, the sandboxed @@ -467,13 +460,15 @@ sandbox :: forall a b s. Path a Dir Sandboxed -> Path a b s -> Maybe (Path Rel b sandbox p1 p2 = p2 `relativeTo` p1 -- | Refines path segments but does not change anything else. -refine :: forall a b s. (FileName -> FileName) -> (DirName -> DirName) -> Path a b s -> Path a b s +refine :: forall a b s. (Name File -> Name File) -> (Name Dir -> Name Dir) -> Path a b s -> Path a b s refine f d = go - where go (Current ) = Current - go (Root ) = Root - go (ParentIn p ) = ParentIn (go p) - go (DirIn p d0) = DirIn (go p) (d d0) - go (FileIn p f0) = FileIn (go p) (f f0) + where + go :: forall a' b' s'. Path a' b' s' -> Path a' b' s' + go (Current ) = Current + go (Root ) = Root + go (ParentIn p ) = ParentIn (go p) + go (DirIn p d0) = DirIn (go p) (d d0) + go (FileIn p f0) = FileIn (go p) (f f0) -- | Parses a canonical `String` representation of a path into a `Path` value. -- | Note that in order to be unambiguous, trailing directories should be @@ -501,8 +496,8 @@ parsePath rd ad rf af p = "." -> base "" -> base ".." -> ParentIn base - _ | isFile && idx == last -> FileIn base (FileName seg) - | otherwise -> DirIn base (DirName seg) + _ | isFile && idx == last -> FileIn (unsafeCoerceType base) (Name seg) + | otherwise -> DirIn (unsafeCoerceType base) (Name seg) in case isAbs, isFile of true, true -> af (foldl folder Root tuples) @@ -530,8 +525,8 @@ instance showPath :: Show (Path a b s) where show Current = "currentDir" show Root = "rootDir" show (ParentIn p) = "(parentDir' " <> show p <> ")" - show (FileIn p (FileName f)) = "(" <> show p <> " file " <> show f <> ")" - show (DirIn p (DirName f)) = "(" <> show p <> " dir " <> show f <> ")" + show (FileIn p (Name f)) = "(" <> show p <> " file " <> show f <> ")" + show (DirIn p (Name f)) = "(" <> show p <> " dir " <> show f <> ")" instance eqPath :: Eq (Path a b s) where eq p1 p2 = canonicalize p1 `identicalPath` canonicalize p2 @@ -553,14 +548,8 @@ instance ordPath :: Ord (Path a b s) where go _ (DirIn _ _) = GT go (FileIn p1' f1) (FileIn p2' f2) = compare p1' p2' <> compare f1 f2 -instance showFileName :: Show FileName where - show (FileName name) = "(FileName " <> show name <> ")" - -derive instance eqFileName :: Eq FileName -derive instance ordFileName :: Ord FileName - -instance showDirName :: Show DirName where - show (DirName name) = "(DirName " <> show name <> ")" +instance showName :: Show (Name a) where + show (Name name) = "(Name " <> show name <> ")" -derive instance eqDirName :: Eq DirName -derive instance ordDirName :: Ord DirName +derive instance eqName :: Eq (Name a) +derive instance ordName :: Ord (Name a) From c2ba2c500f0b8bfe61b87e6714f81c6f584583b9 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Sun, 11 Feb 2018 12:58:05 +0100 Subject: [PATCH 03/59] fix relativeTo --- bower.json | 2 +- src/Data/Path/Pathy.purs | 29 +++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/bower.json b/bower.json index ac9ee13..29779db 100644 --- a/bower.json +++ b/bower.json @@ -22,7 +22,7 @@ "purescript-lists": "^4.0.0", "purescript-partial": "^1.2.0", "purescript-profunctor": "^3.0.0", - "purescript-strings": "^3.0.0", + "purescript-strings": "safareli/purescript-strings#non-empty", "purescript-transformers": "^3.0.0", "purescript-unsafe-coerce": "^3.0.0" }, diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index b0cc109..ffad655 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -440,15 +440,28 @@ relativeTo :: forall a b s s'. Path a b s -> Path a Dir s' -> Maybe (Path Rel b relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) where relativeTo' :: forall b'. Path a b' s -> Path a Dir s' -> Maybe (Path Rel b' s') - relativeTo' Root Root = Just Current - relativeTo' Current Current = Just Current + relativeTo' Root Root = pure Current + relativeTo' Current Current = pure Current relativeTo' cp1 cp2 - | identicalPath cp1 cp2 = Just Current - | otherwise = case peel cp1 of - Just (Tuple cp1' e) -> - flip () (either (DirIn Current) (FileIn Current) e) <$> relativeTo' cp1' cp2 - -- e is not either so it fails here - Nothing -> Nothing + | identicalPath cp1 cp2 = pure Current + | otherwise = do + Tuple cp1Parent cp1Top <- peel' cp1 + rel <- relativeTo' cp1Parent cp2 + pure $ rel either (DirIn Current) (FileIn Current) cp1Top + + -- Specialised version of `peel` which is not using canonicalaise for + -- `ParentIn _` as it's input is canonicalized already. + -- it also returns Either of Dir and File Names so we can + -- decide if DirIn or FileIn is needed. + peel' + :: forall a' b' s'' + . Path a' b' s'' + -> Maybe (Tuple (Path a' Dir s'') (Either (Name Dir) (Name File))) + peel' Current = Nothing + peel' Root = Nothing + peel' (ParentIn _) = Nothing + peel' (DirIn p (Name d)) = Just $ Tuple p (Left $ Name d) + peel' (FileIn p (Name f)) = Just $ Tuple p (Right $ Name f) -- | Attempts to sandbox a path relative to some directory. If successful, the sandboxed -- | directory will be returned relative to the sandbox directory (although this can easily From 4fcf61a74ea1b97e59bfdbe3d5c4374d77134a8c Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 12 Feb 2018 16:41:36 +0100 Subject: [PATCH 04/59] Use non empty string --- bower.json | 2 +- src/Data/Path/Pathy.purs | 148 ++++++++++++++++++----------- test/Main.purs | 199 +++++++++++++++++++++++++++++---------- 3 files changed, 246 insertions(+), 103 deletions(-) diff --git a/bower.json b/bower.json index 29779db..4af6dc6 100644 --- a/bower.json +++ b/bower.json @@ -22,7 +22,7 @@ "purescript-lists": "^4.0.0", "purescript-partial": "^1.2.0", "purescript-profunctor": "^3.0.0", - "purescript-strings": "safareli/purescript-strings#non-empty", + "purescript-strings": "purescript/purescript-strings#nonempty", "purescript-transformers": "^3.0.0", "purescript-unsafe-coerce": "^3.0.0" }, diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index ffad655..8aa48f8 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -1,6 +1,7 @@ module Data.Path.Pathy ( Abs , AbsDir + , ParseError , AbsFile , AbsPath , Dir @@ -57,6 +58,7 @@ module Data.Path.Pathy , relativeTo , renameDir , renameFile + , renameFile' , rootDir , runEscaper , sandbox @@ -68,14 +70,19 @@ module Data.Path.Pathy import Prelude -import Data.Array ((!!), filter, length, zipWith, range) +import Data.Array (drop, dropEnd, filter, length) import Data.Bifunctor (bimap) import Data.Either (Either(..), either) -import Data.Foldable (foldl) -import Data.Maybe (Maybe(..), maybe) +import Data.FoldableWithIndex (foldlWithIndex) +import Data.Identity (Identity(..)) +import Data.Maybe (Maybe(..)) +import Data.Newtype (un) import Data.String as S +import Data.String.NonEmpty (NonEmptyString, appendString) +import Data.String.NonEmpty (fromString, toString) as NEString +import Data.Traversable (traverse) import Data.Tuple (Tuple(..), fst, snd) - +import Partial.Unsafe (unsafeCrashWith) import Unsafe.Coerce (unsafeCoerce) foreign import kind RelOrAbs @@ -103,11 +110,11 @@ foreign import data Unsandboxed :: SandboxedOrNot foreign import data Sandboxed :: SandboxedOrNot -- | A newtype around a file name. -newtype Name (n :: FileOrDir) = Name String +newtype Name (n :: FileOrDir) = Name NonEmptyString -- | Unwraps the `Name` newtype. runName :: forall a. Name a -> String -runName (Name name) = name +runName (Name name) = NEString.toString name -- | A type that describes a Path. All flavors of paths are described by this -- | type, whether they are absolute or relative paths, whether they @@ -180,7 +187,7 @@ posixEscaper = Escaper $ s -> s -- | Creates a path which points to a relative file of the specified name. -file :: forall s. String -> Path Rel File s +file :: forall s. NonEmptyString -> Path Rel File s file f = file' (Name f) -- | Creates a path which points to a relative file of the specified name. @@ -190,30 +197,49 @@ file' f = FileIn Current f -- | Retrieves the name of a file path. fileName :: forall a s. Path a File s -> Name File fileName (FileIn _ f) = f -fileName _ = Name "" +fileName _ = unsafeCrashWith + """Hit unrechable path in Data.Pathy.fileName + Based on type of this function, it must be called with a Path such that FileIn node is a root node + The reason might be a bug in this module or incorrect unsafeCoerce in it's use site + """ -- | Retrieves the extension of a file name. extension :: Name File -> String -extension (Name f) = case S.lastIndexOf (S.Pattern ".") f of - Just x -> S.drop (x + 1) f - Nothing -> "" +extension (Name f) = + let s = NEString.toString f + in case S.lastIndexOf (S.Pattern ".") s of + Just x -> S.drop (x + 1) s + Nothing -> "" -- | Drops the extension on a file name. -dropExtension :: Name File -> Name File -dropExtension (Name n) = case S.lastIndexOf (S.Pattern ".") n of - Just x -> Name $ S.take x n - Nothing -> Name n +dropExtension :: Name File -> Maybe (Name File) +dropExtension (Name n) = + let + s = NEString.toString n + in case S.lastIndexOf (S.Pattern ".") s of + Just x -> map Name $ NEString.fromString $ S.take x s + Nothing -> Just (Name n) --- | Changes the extension on a file name. -changeExtension :: (String -> String) -> Name File -> Name File +changeExtension :: (String -> String) -> Name File -> Maybe (Name File) changeExtension f nm = update (f $ extension nm) (dropExtension nm) where - update "" n = n - update ext (Name n) = Name $ n <> "." <> ext + update ext' name = case NEString.fromString ext' of + Nothing -> name + Just ext -> Just $ _updateExt ext name + +changeExtension' :: (String -> NonEmptyString) -> Name File -> Name File +changeExtension' f nm = + _updateExt (f $ extension nm) (dropExtension nm) + + +_updateExt :: NonEmptyString -> Maybe (Name File) -> Name File +_updateExt ext = case _ of + Just (Name n) -> Name $ n `appendString` "." <> ext + Nothing -> Name ext -- | Creates a path which points to a relative directory of the specified name. -dir :: forall s. String -> Path Rel Dir s +dir :: forall s. NonEmptyString -> Path Rel Dir s dir d = dir' (Name d) -- | Creates a path which points to a relative directory of the specified name. @@ -254,8 +280,8 @@ infixl 6 appendPath as -- | ```purescript -- | file "image" <.> "png" -- | ``` -setExtension :: forall a s. Path a File s -> String -> Path a File s -setExtension p ext = renameFile (changeExtension $ const ext) p +setExtension :: forall a s. Path a File s -> NonEmptyString -> Path a File s +setExtension p ext = renameFile (changeExtension' $ const ext) p infixl 6 setExtension as <.> @@ -372,8 +398,11 @@ rootDir = Root -- | Renames a file path. renameFile :: forall a s. (Name File -> Name File) -> Path a File s -> Path a File s -renameFile f (FileIn p f0) = FileIn p (f f0) -renameFile _ p = p +renameFile f = un Identity <<< renameFile' (pure <<< f) + +renameFile' :: forall f a s. Applicative f => (Name File -> f (Name File)) -> Path a File s -> f (Path a File s) +renameFile' f (FileIn p f0) = FileIn p <$> f f0 +renameFile' _ p = pure p -- | Renames a directory path. Note: This is a simple rename of the terminal -- | directory name, not a "move". @@ -405,10 +434,10 @@ unsafePrintPath' r = go go Current = "./" go Root = "/" go (ParentIn p) = go p <> "../" - go (DirIn p@(FileIn _ _ ) (Name d)) = go p <> "/" <> escape d <> "/" -- dir inside a file - go (DirIn p (Name d)) = go p <> escape d <> "/" -- dir inside a dir - go (FileIn p@(FileIn _ _) (Name f)) = go p <> "/" <> escape f -- file inside a file - go (FileIn p (Name f)) = go p <> escape f + go (DirIn p@(FileIn _ _ ) d) = go p <> "/" <> escape (runName d) <> "/" -- dir inside a file + go (DirIn p d) = go p <> escape (runName d) <> "/" -- dir inside a dir + go (FileIn p@(FileIn _ _) f) = go p <> "/" <> escape (runName f) -- file inside a file + go (FileIn p f) = go p <> escape (runName f) escape = runEscaper r unsafePrintPath :: forall a b s. Path a b s -> String @@ -483,6 +512,8 @@ refine f d = go go (DirIn p d0) = DirIn (go p) (d d0) go (FileIn p f0) = FileIn (go p) (f f0) +type ParseError = Unit + -- | Parses a canonical `String` representation of a path into a `Path` value. -- | Note that in order to be unambiguous, trailing directories should be -- | marked with a trailing slash character (`'/'`). @@ -492,47 +523,58 @@ parsePath -> (AbsDir Unsandboxed -> z) -> (RelFile Unsandboxed -> z) -> (AbsFile Unsandboxed -> z) + -> (ParseError -> z) -> String -> z -parsePath rd ad rf af "" = rd Current -parsePath rd ad rf af p = +parsePath rd ad rf af err "" = err unit +parsePath rd ad rf af err "/" = ad Root +parsePath rd ad rf af err p = let - segs = S.split (S.Pattern "/") p - last = length segs - 1 - isAbs = S.take 1 p == "/" - isFile = maybe false (_ /= "") (segs !! last) - tuples = zipWith Tuple segs (range 0 last) - - folder :: forall a b s. Path a b s -> Tuple String Int -> Path a b s - folder base (Tuple seg idx) = - case seg of - "." -> base - "" -> base - ".." -> ParentIn base - _ | isFile && idx == last -> FileIn (unsafeCoerceType base) (Name seg) - | otherwise -> DirIn (unsafeCoerceType base) (Name seg) + isAbs = S.take 1 p == "/" + isFile = S.takeRight 1 p /= "/" + segsRaw = S.split (S.Pattern "/") p + segsDropped = + -- drop last or/and first empty segment(s) if any + case isAbs, isFile of + true, true -> drop 1 $ segsRaw + true, false -> drop 1 $ dropEnd 1 segsRaw + false, true -> segsRaw + false, false -> dropEnd 1 segsRaw + last = length segsDropped - 1 + folder :: forall a b s. Int -> Path a b s -> NonEmptyString -> Path a b s + folder idx base seg = + if NEString.toString seg == "." then + base + else if NEString.toString seg == ".." then + ParentIn base + else if isFile && idx == last then + FileIn (unsafeCoerceType base) (Name seg) + else + DirIn (unsafeCoerceType base) (Name seg) in - case isAbs, isFile of - true, true -> af (foldl folder Root tuples) - true, false -> ad (foldl folder Root tuples) - false, true -> rf (foldl folder Current tuples) - false, false -> rd (foldl folder Current tuples) + case traverse NEString.fromString segsDropped of + Nothing -> err unit + Just segs -> case isAbs, isFile of + true, true -> af $ foldlWithIndex folder Root segs + true, false -> ad $ foldlWithIndex folder Root segs + false, true -> rf $ foldlWithIndex folder Current segs + false, false -> rd $ foldlWithIndex folder Current segs -- | Attempts to parse a relative file from a string. parseRelFile :: String -> Maybe (RelFile Unsandboxed) -parseRelFile = parsePath (const Nothing) (const Nothing) Just (const Nothing) +parseRelFile = parsePath (const Nothing) (const Nothing) Just (const Nothing) (const Nothing) -- | Attempts to parse an absolute file from a string. parseAbsFile :: String -> Maybe (AbsFile Unsandboxed) -parseAbsFile = parsePath (const Nothing) (const Nothing) (const Nothing) Just +parseAbsFile = parsePath (const Nothing) (const Nothing) (const Nothing) Just (const Nothing) -- | Attempts to parse a relative directory from a string. parseRelDir :: String -> Maybe (RelDir Unsandboxed) -parseRelDir = parsePath Just (const Nothing) (const Nothing) (const Nothing) +parseRelDir = parsePath Just (const Nothing) (const Nothing) (const Nothing) (const Nothing) -- | Attempts to parse an absolute directory from a string. parseAbsDir :: String -> Maybe (AbsDir Unsandboxed) -parseAbsDir = parsePath (const Nothing) Just (const Nothing) (const Nothing) +parseAbsDir = parsePath (const Nothing) Just (const Nothing) (const Nothing) (const Nothing) instance showPath :: Show (Path a b s) where show Current = "currentDir" diff --git a/test/Main.purs b/test/Main.purs index ca41e0e..bb648ca 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -1,17 +1,24 @@ module Test.Main where import Prelude + import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, info, infoShow) import Data.Foldable (foldl) import Data.Maybe (Maybe(..), fromJust) -import Data.Path.Pathy (Path, Abs, Rel, Dir, File, Sandboxed, dir, rootDir, parseAbsDir, parseRelDir, currentDir, file, parseAbsFile, parseRelFile, parentDir', depth, sandbox, dropExtension, renameFile, canonicalize, unsandbox, unsafePrintPath, (), (<..>), (<.>)) +import Data.Symbol (SProxy(..)) +import Data.Symbol (class IsSymbol, reflectSymbol) as Symbol +import Type.Data.Boolean (False) as Symbol +import Type.Data.Symbol (class Equals) as Symbol +import Data.Path.Pathy (Abs, Dir, File, Path, Rel, Sandboxed, canonicalize, currentDir, depth, dir, dropExtension, file, parentDir', parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, renameFile', rootDir, sandbox, unsafePrintPath, unsandbox, (<..>), (<.>), ()) import Data.String as Str +import Data.String.NonEmpty (NonEmptyString) import Partial.Unsafe (unsafePartial) import Test.QuickCheck as QC import Test.QuickCheck.Gen as Gen import Test.QuickCheck.Laws.Data as Laws.Data import Type.Proxy (Proxy(..)) +import Unsafe.Coerce (unsafeCoerce) test :: forall a eff. Show a => Eq a => String -> a -> a -> Eff (console :: CONSOLE | eff) Unit test name actual expected= do @@ -36,82 +43,176 @@ instance arbitraryArbPath ∷ QC.Arbitrary ArbPath where filename ← file <$> pathPart pure $ ArbPath $ rootDir foldl (flip ()) filename (dirs ∷ Array (Path Rel Dir Sandboxed)) -pathPart ∷ Gen.Gen String -pathPart = Gen.suchThat QC.arbitrary (not <<< Str.null) +pathPart ∷ Gen.Gen NonEmptyString +pathPart = asNonEmptyString <$> Gen.suchThat QC.arbitrary (not <<< Str.null) + where + asNonEmptyString :: String -> NonEmptyString + asNonEmptyString = unsafeCoerce + +dirFoo :: forall s. Path Rel Dir s +dirFoo = dir (reflectNonEmpty $ SProxy :: SProxy "foo") + +dirBar :: forall s. Path Rel Dir s +dirBar = dir (reflectNonEmpty $ SProxy :: SProxy "bar") main :: QC.QC () Unit main = do -- Should not compile: - -- test "() - file in dir" (printPath (file "image.png" dir "foo")) "./image.png/foo" + -- test + -- "() - file in dir" + -- (printPath (file "image.png" dirFoo)) + -- "./image.png/foo" -- Should not compile: - -- test "() - absolute dir in absolute dir" (printPath (rootDir rootDir)) "/" + -- test + -- "() - absolute dir in absolute dir" + -- (printPath (rootDir rootDir)) + -- "/" -- Should not compile: - -- test "() - absolute dir in relative dir" (printPath (currentDir rootDir)) "/" + -- test + -- "() - absolute dir in relative dir" + -- (printPath (currentDir rootDir)) + -- "/" -- Should not compile: - -- test "printPath -- cannot print unsandboxed" (printPath (parentDir' currentDir)) "./../" - - test' "() - two directories" (dir "foo" dir "bar") "./foo/bar/" - - test' "() - file with two parents" (dir "foo" dir "bar" file "image.png") "./foo/bar/image.png" - - test' "(<.>) - file without extension" (file "image" <.> "png") "./image.png" - - test' "(<.>) - file with extension" (file "image.jpg" <.> "png") "./image.png" - - test' "printPath - ./../" (parentDir' currentDir) "./../" - - test' "() - ./../foo/" (parentDir' currentDir unsandbox (dir "foo")) "./../foo/" - - test' "parentDir' - ./../foo/../" ((parentDir' currentDir unsandbox (dir "foo")) (parentDir' currentDir)) "./../foo/../" - - test' "(<..>) - ./../" (currentDir <..> currentDir) "./../" - - test' "(<..>) - ./../foo/" (currentDir <..> dir "foo") "./../foo/" - - test' "(<..>) - ./../foo/../" ((currentDir <..> dir "foo") <..> currentDir) "./../foo/../" - - test' "canonicalize - 1 down, 1 up" (canonicalize $ parentDir' $ dir "foo") "./" - - test' "canonicalize - 2 down, 2 up" (canonicalize (parentDir' (parentDir' (dir "foo" dir "bar")))) "./" - - test' "renameFile - single level deep" (renameFile dropExtension (file "image.png")) "./image" + -- test + -- "printPath -- cannot print unsandboxed" + -- (printPath (parentDir' currentDir)) + -- "./../" + + test' "() - two directories" + (dirFoo dirBar) + "./foo/bar/" + + test' "() - file with two parents" + (dirFoo + dirBar + file (reflectNonEmpty $ SProxy :: SProxy "image.png")) + "./foo/bar/image.png" + + test' "(<.>) - file without extension" + (file (reflectNonEmpty $ SProxy :: SProxy "image") + <.> (reflectNonEmpty $ SProxy :: SProxy "png")) + "./image.png" + + test' "(<.>) - file with extension" + (file (reflectNonEmpty $ SProxy :: SProxy "image.jpg") + <.> (reflectNonEmpty $ SProxy :: SProxy "png")) + "./image.png" + + test' "printPath - ./../" + (parentDir' currentDir) + "./../" + + test' "() - ./../foo/" + (parentDir' currentDir unsandbox (dirFoo)) + "./../foo/" + + test' "parentDir' - ./../foo/../" + ((parentDir' currentDir unsandbox (dirFoo)) (parentDir' currentDir)) + "./../foo/../" + + test' "(<..>) - ./../" + (currentDir <..> currentDir) + "./../" + + test' "(<..>) - ./../foo/" + (currentDir <..> dirFoo) + "./../foo/" + + test' "(<..>) - ./../foo/../" + ((currentDir <..> dirFoo) <..> currentDir) + "./../foo/../" + + test' "canonicalize - 1 down, 1 up" + (canonicalize $ parentDir' $ dirFoo) + "./" + + test' "canonicalize - 2 down, 2 up" + (canonicalize (parentDir' (parentDir' (dirFoo dirBar)))) + "./" + + test "renameFile - single level deep" + (renameFile' dropExtension (file (reflectNonEmpty $ SProxy :: SProxy "image.png"))) + + (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image") test' "sandbox - sandbox absolute dir to one level higher" - (unsafePartial $ fromJust $ sandbox (rootDir dir "foo") (rootDir dir "foo" dir "bar")) "./bar/" + (unsafePartial $ fromJust $ sandbox (rootDir dirFoo) (rootDir dirFoo dirBar)) + "./bar/" - test "depth - negative" (depth (parentDir' $ parentDir' $ parentDir' $ currentDir)) (-3) + test "depth - negative" + (depth (parentDir' $ parentDir' $ parentDir' $ currentDir)) (-3) - test "parseRelFile - image.png" (parseRelFile "image.png") (Just $ file "image.png") + test "parseRelFile - image.png" + (parseRelFile "image.png") + (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") - test "parseRelFile - ./image.png" (parseRelFile "./image.png") (Just $ file "image.png") + test "parseRelFile - ./image.png" + (parseRelFile "./image.png") + (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") - test "parseRelFile - foo/image.png" (parseRelFile "foo/image.png") (Just $ dir "foo" file "image.png") + test "parseRelFile - foo/image.png" + (parseRelFile "foo/image.png") + (Just $ dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) - test "parseRelFile - ../foo/image.png" (parseRelFile "../foo/image.png") (Just $ currentDir <..> dir "foo" file "image.png") + test "parseRelFile - ../foo/image.png" + (parseRelFile "../foo/image.png") + (Just $ currentDir <..> dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) - test "parseAbsFile - /image.png" (parseAbsFile "/image.png") (Just $ rootDir file "image.png") + test "parseAbsFile - /image.png" + (parseAbsFile "/image.png") + (Just $ rootDir file (reflectNonEmpty $ SProxy :: SProxy "image.png")) - test "parseAbsFile - /foo/image.png" (parseAbsFile "/foo/image.png") (Just $ rootDir dir "foo" file "image.png") + test "parseAbsFile - /foo/image.png" + (parseAbsFile "/foo/image.png") + (Just $ rootDir dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) - test "parseRelDir - empty string" (parseRelDir "") (Just $ currentDir) + test "parseRelDir - empty string" + (parseRelDir "") + -- (Just $ currentDir) + Nothing - test "parseRelDir - ./../" (parseRelDir "./../") (Just $ currentDir <..> currentDir) + test "parseRelDir - ./../" + (parseRelDir "./../") + (Just $ currentDir <..> currentDir) - test "parseRelDir - foo/" (parseRelDir "foo/") (Just $ dir "foo") + test "parseRelDir - foo/" + (parseRelDir "foo/") + (Just $ dirFoo) - test "parseRelDir - foo/bar" (parseRelDir "foo/bar/") (Just $ dir "foo" dir "bar") + test "parseRelDir - foo/bar" + (parseRelDir "foo/bar/") + (Just $ dirFoo dirBar) - test "parseRelDir - ./foo/bar" (parseRelDir "./foo/bar/") (Just $ dir "foo" dir "bar") + test "parseRelDir - ./foo/bar" + (parseRelDir "./foo/bar/") + (Just $ dirFoo dirBar) - test "parseAbsDir - /" (parseAbsDir "/") (Just $ rootDir) + test "parseAbsDir - /" + (parseAbsDir "/") + (Just $ rootDir) - test "parseAbsDir - /foo/" (parseAbsDir "/foo/") (Just $ rootDir dir "foo") + test "parseAbsDir - /foo/" + (parseAbsDir "/foo/") + (Just $ rootDir dirFoo) - test "parseAbsDir - /foo/bar" (parseAbsDir "/foo/bar/") (Just $ rootDir dir "foo" dir "bar") + test "parseAbsDir - /foo/bar" + (parseAbsDir "/foo/bar/") + (Just $ rootDir dirFoo dirBar) info "Checking typeclass laws..." Laws.Data.checkEq (Proxy :: Proxy ArbPath) Laws.Data.checkOrd (Proxy :: Proxy ArbPath) + + + +class IsSymbolNonEmpty sym where + reflectNonEmpty :: SProxy sym -> NonEmptyString + +instance isSymbolNonEmpty :: (Symbol.IsSymbol s, Symbol.Equals s "" Symbol.False) => IsSymbolNonEmpty s where + reflectNonEmpty _ = asNonEmpty $ Symbol.reflectSymbol (SProxy :: SProxy s) + where + asNonEmpty :: String -> NonEmptyString + asNonEmpty = unsafeCoerce From 8bed445431e009443f6bf72249072ed95fa34cf4 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 13 Feb 2018 12:48:01 +0100 Subject: [PATCH 05/59] remove maybe{Dir,File,Rel,Abs} is{Absolute,Relative} in favor of SplitRelOrAbs SplitDirOrFile --- src/Data/Path/Pathy.purs | 76 +++++++++++----------------------------- test/Main.purs | 36 ++++++++++++++++--- 2 files changed, 52 insertions(+), 60 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 8aa48f8..aa35e1e 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -37,12 +37,6 @@ module Data.Path.Pathy , fileName , pathName , identicalPath - , isAbsolute - , isRelative - , maybeAbs - , maybeDir - , maybeFile - , maybeRel , parentDir , parentDir' , peel @@ -54,6 +48,10 @@ module Data.Path.Pathy , parseRelFile , printPath , printPath' + , class SplitRelOrAbs + , relOrAbs + , class SplitDirOrFile + , dirOrFile , refine , relativeTo , renameDir @@ -87,7 +85,7 @@ import Unsafe.Coerce (unsafeCoerce) foreign import kind RelOrAbs -foreign import kind FileOrDir +foreign import kind DirOrFile foreign import kind SandboxedOrNot @@ -98,10 +96,10 @@ foreign import data Rel :: RelOrAbs foreign import data Abs :: RelOrAbs -- | The (phantom) type of files. -foreign import data File :: FileOrDir +foreign import data File :: DirOrFile -- | The (phantom) type of directories. -foreign import data Dir :: FileOrDir +foreign import data Dir :: DirOrFile -- | The (phantom) type of unsandboxed paths. foreign import data Unsandboxed :: SandboxedOrNot @@ -110,7 +108,7 @@ foreign import data Unsandboxed :: SandboxedOrNot foreign import data Sandboxed :: SandboxedOrNot -- | A newtype around a file name. -newtype Name (n :: FileOrDir) = Name NonEmptyString +newtype Name (n :: DirOrFile) = Name NonEmptyString -- | Unwraps the `Name` newtype. runName :: forall a. Name a -> String @@ -134,7 +132,7 @@ runName (Name name) = NEString.toString name -- | `parentDir' rootDir`, or by parsing an equivalent string such as `/../`, -- | but such paths are marked as unsandboxed, and may not be rendered to strings -- | until they are first sandboxed to some directory. -data Path (a :: RelOrAbs) (b :: FileOrDir) (s :: SandboxedOrNot) +data Path (a :: RelOrAbs) (b :: DirOrFile) (s :: SandboxedOrNot) = Current | Root | ParentIn (Path a b s) @@ -164,6 +162,18 @@ type RelPath s = AnyPath Rel s -- | A type describing an absolute file or directory path. type AbsPath s = AnyPath Abs s +class SplitDirOrFile (b :: DirOrFile) where + dirOrFile :: forall a s. Path a b s -> AnyPath a s + +instance relSplitDirOrFile :: SplitDirOrFile Dir where dirOrFile = Left +instance absSplitDirOrFile :: SplitDirOrFile File where dirOrFile = Right + +class SplitRelOrAbs (a :: RelOrAbs) where + relOrAbs :: forall b s. Path a b s -> Either (Path Rel b s) (Path Abs b s) + +instance relSplitRelOrAbs :: SplitRelOrAbs Rel where relOrAbs = Left +instance absSplitRelOrAbs :: SplitRelOrAbs Abs where relOrAbs = Right + -- | Escapers encode segments or characters which have reserved meaning. newtype Escaper = Escaper (String -> String) @@ -297,18 +307,6 @@ parentAppend d p = parentDir' d unsandbox p infixl 6 parentAppend as <..> --- | Determines if this path is absolutely located. -isAbsolute :: forall a b s. Path a b s -> Boolean -isAbsolute Current = false -isAbsolute Root = true -isAbsolute (ParentIn p) = isAbsolute p -isAbsolute (FileIn p _) = isAbsolute p -isAbsolute (DirIn p _) = isAbsolute p - --- | Determines if this path is relatively located. -isRelative :: forall a b s. Path a b s -> Boolean -isRelative = not <<< isAbsolute - -- | Peels off the last directory and the terminal file or directory name -- | from the path. Returns `Nothing` if there is no such pair (for example, -- | if the last path segment is root directory, current directory, or parent @@ -325,38 +323,6 @@ peel p@(ParentIn _) = case canonicalize' p of peel (DirIn p (Name d)) = Just $ Tuple p (Name d) peel (FileIn p (Name f)) = Just $ Tuple p (Name f) --- | Determines if the path refers to a directory. -maybeDir :: forall a b s. Path a b s -> Maybe (Path a Dir s) -maybeDir Current = Just Current -maybeDir Root = Just Root -maybeDir (ParentIn p) = Just $ ParentIn (unsafeCoerceType p) -maybeDir (FileIn _ _) = Nothing -maybeDir (DirIn p d) = Just $ DirIn p d - --- | Determines if the path refers to a file. -maybeFile :: forall a b s. Path a b s -> Maybe (Path a File s) -maybeFile Current = Nothing -maybeFile Root = Nothing -maybeFile (ParentIn _) = Nothing -maybeFile (FileIn p f) = () <$> maybeDir p <*> Just (file' f) -maybeFile (DirIn _ _) = Nothing - --- | Determines if the path is relatively specified. -maybeRel :: forall a b s. Path a b s -> Maybe (Path Rel b s) -maybeRel Current = Just Current -maybeRel Root = Nothing -maybeRel (ParentIn p) = ParentIn <$> maybeRel p -maybeRel (FileIn p f) = flip FileIn f <$> maybeRel p -maybeRel (DirIn p d) = flip DirIn d <$> maybeRel p - --- | Determines if the path is absolutely specified. -maybeAbs :: forall a b s. Path a b s -> Maybe (Path Rel b s) -maybeAbs Current = Nothing -maybeAbs Root = Just Root -maybeAbs (ParentIn p) = ParentIn <$> maybeAbs p -maybeAbs (FileIn p f) = flip FileIn f <$> maybeAbs p -maybeAbs (DirIn p d) = flip DirIn d <$> maybeAbs p - -- | Returns the depth of the path. This may be negative in some cases, e.g. -- | `./../../../` has depth `-3`. depth :: forall a b s. Path a b s -> Int diff --git a/test/Main.purs b/test/Main.purs index bb648ca..98a0d3c 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -4,19 +4,20 @@ import Prelude import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, info, infoShow) +import Data.Either (either) import Data.Foldable (foldl) import Data.Maybe (Maybe(..), fromJust) -import Data.Symbol (SProxy(..)) -import Data.Symbol (class IsSymbol, reflectSymbol) as Symbol -import Type.Data.Boolean (False) as Symbol -import Type.Data.Symbol (class Equals) as Symbol -import Data.Path.Pathy (Abs, Dir, File, Path, Rel, Sandboxed, canonicalize, currentDir, depth, dir, dropExtension, file, parentDir', parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, renameFile', rootDir, sandbox, unsafePrintPath, unsandbox, (<..>), (<.>), ()) +import Data.Path.Pathy (class SplitDirOrFile, class SplitRelOrAbs, Abs, Dir, File, Path, Rel, Sandboxed, canonicalize, currentDir, depth, dir, dirOrFile, dropExtension, file, parentDir', parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, relOrAbs, renameFile', rootDir, sandbox, unsafePrintPath, unsandbox, (<..>), (<.>), ()) import Data.String as Str import Data.String.NonEmpty (NonEmptyString) +import Data.Symbol (SProxy(..)) +import Data.Symbol (class IsSymbol, reflectSymbol) as Symbol import Partial.Unsafe (unsafePartial) import Test.QuickCheck as QC import Test.QuickCheck.Gen as Gen import Test.QuickCheck.Laws.Data as Laws.Data +import Type.Data.Boolean (False) as Symbol +import Type.Data.Symbol (class Equals) as Symbol import Type.Proxy (Proxy(..)) import Unsafe.Coerce (unsafeCoerce) @@ -216,3 +217,28 @@ instance isSymbolNonEmpty :: (Symbol.IsSymbol s, Symbol.Equals s "" Symbol.False where asNonEmpty :: String -> NonEmptyString asNonEmpty = unsafeCoerce + + +-- | Determines if the path refers to a directory. +maybeDir :: forall a b s. SplitDirOrFile b => Path a b s -> Maybe (Path a Dir s) +maybeDir p = either Just (const Nothing) (dirOrFile p) + +-- | Determines if the path refers to a file. +maybeFile :: forall a b s. SplitDirOrFile b => Path a b s -> Maybe (Path a File s) +maybeFile p = either (const Nothing) Just (dirOrFile p) + +-- | Determines if the path is relatively specified. +maybeRel :: forall a b s. SplitRelOrAbs a => Path a b s -> Maybe (Path Rel b s) +maybeRel p = either Just (const Nothing) (relOrAbs p) + +-- | Determines if the path is absolutely specified. +maybeAbs :: forall a b s. SplitRelOrAbs a => Path a b s -> Maybe (Path Abs b s) +maybeAbs p = either (const Nothing) Just (relOrAbs p) + +-- | Determines if this path is absolutely located. +isAbsolute :: forall a b s. SplitRelOrAbs a => Path a b s -> Boolean +isAbsolute p = either (const false) (const true) (relOrAbs p) + +-- | Determines if this path is relatively located. +isRelative :: forall a b s. SplitRelOrAbs a => Path a b s -> Boolean +isRelative p= either (const true) (const false) (relOrAbs p) From 2d5b7f703312ea090a08136315968b4028904133 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 13 Feb 2018 13:03:09 +0100 Subject: [PATCH 06/59] remove special version of peel --- src/Data/Path/Pathy.purs | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index aa35e1e..c1e249a 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -70,7 +70,8 @@ import Prelude import Data.Array (drop, dropEnd, filter, length) import Data.Bifunctor (bimap) -import Data.Either (Either(..), either) +import Data.Bitraversable (bitraverse) +import Data.Either (Either(..)) import Data.FoldableWithIndex (foldlWithIndex) import Data.Identity (Identity(..)) import Data.Maybe (Maybe(..)) @@ -431,40 +432,30 @@ identicalPath p1 p2 = show p1 == show p2 -- | reference path. -- | -- | Note there are some cases this function cannot handle. -relativeTo :: forall a b s s'. Path a b s -> Path a Dir s' -> Maybe (Path Rel b s') +relativeTo :: forall a b s s'. SplitDirOrFile b => Path a b s -> Path a Dir s' -> Maybe (Path Rel b s') relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) where - relativeTo' :: forall b'. Path a b' s -> Path a Dir s' -> Maybe (Path Rel b' s') + relativeTo' :: forall b'. SplitDirOrFile b' => Path a b' s -> Path a Dir s' -> Maybe (Path Rel b' s') relativeTo' Root Root = pure Current relativeTo' Current Current = pure Current relativeTo' cp1 cp2 | identicalPath cp1 cp2 = pure Current | otherwise = do - Tuple cp1Parent cp1Top <- peel' cp1 - rel <- relativeTo' cp1Parent cp2 - pure $ rel either (DirIn Current) (FileIn Current) cp1Top - - -- Specialised version of `peel` which is not using canonicalaise for - -- `ParentIn _` as it's input is canonicalized already. - -- it also returns Either of Dir and File Names so we can - -- decide if DirIn or FileIn is needed. - peel' - :: forall a' b' s'' - . Path a' b' s'' - -> Maybe (Tuple (Path a' Dir s'') (Either (Name Dir) (Name File))) - peel' Current = Nothing - peel' Root = Nothing - peel' (ParentIn _) = Nothing - peel' (DirIn p (Name d)) = Just $ Tuple p (Left $ Name d) - peel' (FileIn p (Name f)) = Just $ Tuple p (Right $ Name f) - + peeled <- bitraverse peel peel (dirOrFile cp1) + case peeled of + Left (Tuple cp1Parent cp1Top) -> do + rel <- relativeTo' cp1Parent cp2 + pure $ rel DirIn Current cp1Top + Right (Tuple cp1Parent cp1Top) -> do + rel <- relativeTo' cp1Parent cp2 + pure $ rel FileIn Current cp1Top -- | Attempts to sandbox a path relative to some directory. If successful, the sandboxed -- | directory will be returned relative to the sandbox directory (although this can easily -- | be converted into an absolute path using ``). -- | -- | This combinator can be used to ensure that paths which originate from user-code -- | cannot access data outside a given directory. -sandbox :: forall a b s. Path a Dir Sandboxed -> Path a b s -> Maybe (Path Rel b Sandboxed) +sandbox :: forall a b s. SplitDirOrFile b => Path a Dir Sandboxed -> Path a b s -> Maybe (Path Rel b Sandboxed) sandbox p1 p2 = p2 `relativeTo` p1 -- | Refines path segments but does not change anything else. From 18e1beec670d6a561b27c0ae1aa5729be6086b98 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 13 Feb 2018 14:31:37 +0100 Subject: [PATCH 07/59] dir or file info is in types only now --- src/Data/Path/Pathy.purs | 146 +++++++++++++++++++++------------------ test/Main.purs | 4 +- 2 files changed, 81 insertions(+), 69 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index c1e249a..565a8ec 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -52,6 +52,8 @@ module Data.Path.Pathy , relOrAbs , class SplitDirOrFile , dirOrFile + , class SplitDirOrFileName + , dirOrFileName , refine , relativeTo , renameDir @@ -70,7 +72,6 @@ import Prelude import Data.Array (drop, dropEnd, filter, length) import Data.Bifunctor (bimap) -import Data.Bitraversable (bitraverse) import Data.Either (Either(..)) import Data.FoldableWithIndex (foldlWithIndex) import Data.Identity (Identity(..)) @@ -137,8 +138,7 @@ data Path (a :: RelOrAbs) (b :: DirOrFile) (s :: SandboxedOrNot) = Current | Root | ParentIn (Path a b s) - | DirIn (Path a Dir s) (Name Dir) - | FileIn (Path a Dir s) (Name File) + | In (Path a Dir s) (Name b) -- | A type describing a file whose location is given relative to some other, -- | unspecified directory (referred to as the "current directory"). @@ -169,6 +169,13 @@ class SplitDirOrFile (b :: DirOrFile) where instance relSplitDirOrFile :: SplitDirOrFile Dir where dirOrFile = Left instance absSplitDirOrFile :: SplitDirOrFile File where dirOrFile = Right +class SplitDirOrFileName (b :: DirOrFile) where + dirOrFileName :: Name b -> Either (Name Dir) (Name File) + +instance relSplitDirOrFileName :: SplitDirOrFileName Dir where dirOrFileName = Left +instance absSplitDirOrFileName :: SplitDirOrFileName File where dirOrFileName = Right + + class SplitRelOrAbs (a :: RelOrAbs) where relOrAbs :: forall b s. Path a b s -> Either (Path Rel b s) (Path Abs b s) @@ -203,14 +210,14 @@ file f = file' (Name f) -- | Creates a path which points to a relative file of the specified name. file' :: forall s. Name File -> Path Rel File s -file' f = FileIn Current f +file' f = In Current f -- | Retrieves the name of a file path. fileName :: forall a s. Path a File s -> Name File -fileName (FileIn _ f) = f +fileName (In _ f) = f fileName _ = unsafeCrashWith """Hit unrechable path in Data.Pathy.fileName - Based on type of this function, it must be called with a Path such that FileIn node is a root node + Based on type of this function, it must be called with a Path such that In node is a root node The reason might be a bug in this module or incorrect unsafeCoerce in it's use site """ @@ -255,13 +262,13 @@ dir d = dir' (Name d) -- | Creates a path which points to a relative directory of the specified name. dir' :: forall s. Name Dir -> Path Rel Dir s -dir' d = DirIn Current d +dir' d = In Current d -- | Retrieves the name of a directory path. Not all paths have such a name, -- | for example, the root or current directory. dirName :: forall a s. Path a Dir s -> Maybe (Name Dir) dirName p = case canonicalize p of - DirIn _ d -> Just d + In _ d -> Just d _ -> Nothing pathName :: forall b s. AnyPath b s -> Either (Maybe (Name Dir)) (Name File) @@ -272,17 +279,14 @@ appendPath :: forall a b s. Path a Dir s -> Path Rel b s -> Path a b s appendPath Current Current = Current appendPath Root Current = Root appendPath (ParentIn p1) Current = ParentIn (p1 Current) -appendPath (FileIn p1 f1) Current = FileIn (p1 Current) f1 -appendPath (DirIn p1 d1) Current = DirIn (p1 Current) d1 +appendPath (In p1 f1) Current = In (unsafeCoerce $ p1 Current) (unsafeCoerce f1) appendPath p1 (ParentIn p2) = ParentIn (p1 p2) -appendPath p1 (FileIn p2 f2) = FileIn (p1 p2) f2 -appendPath p1 (DirIn p2 d2) = DirIn (p1 p2) d2 +appendPath p1 (In p2 f2) = In (p1 p2) f2 -- following cases don't make sense but cannot exist appendPath Current Root = Current appendPath Root Root = Root appendPath (ParentIn p1) Root = ParentIn (p1 Current) -appendPath (FileIn p1 f1) Root = FileIn (p1 Current) f1 -appendPath (DirIn p1 d1) Root = DirIn (p1 Current) d1 +appendPath (In p1 f1) Root = In (unsafeCoerce $ p1 Current) (unsafeCoerce $f1) infixl 6 appendPath as @@ -321,8 +325,7 @@ peel Root = Nothing peel p@(ParentIn _) = case canonicalize' p of Tuple true p' -> peel p' _ -> Nothing -peel (DirIn p (Name d)) = Just $ Tuple p (Name d) -peel (FileIn p (Name f)) = Just $ Tuple p (Name f) +peel (In p n) = Just $ Tuple p n -- | Returns the depth of the path. This may be negative in some cases, e.g. -- | `./../../../` has depth `-3`. @@ -330,8 +333,7 @@ depth :: forall a b s. Path a b s -> Int depth Current = 0 depth Root = 0 depth (ParentIn p) = depth p - 1 -depth (FileIn p _) = depth p + 1 -depth (DirIn p _) = depth p + 1 +depth (In p _) = depth p + 1 -- | Attempts to extract out the parent directory of the specified path. If the -- | function would have to use a relative path in the return value, the function will @@ -344,8 +346,7 @@ unsandbox :: forall a b s. Path a b s -> Path a b Unsandboxed unsandbox Current = Current unsandbox Root = Root unsandbox (ParentIn p) = ParentIn (unsandbox p) -unsandbox (DirIn p d) = DirIn (unsandbox p) d -unsandbox (FileIn p f) = FileIn (unsandbox p) f +unsandbox (In p n) = In (unsandbox p) n -- | Creates a path that points to the parent directory of the specified path. -- | This function always unsandboxes the path. @@ -368,13 +369,13 @@ renameFile :: forall a s. (Name File -> Name File) -> Path a File s -> Path a Fi renameFile f = un Identity <<< renameFile' (pure <<< f) renameFile' :: forall f a s. Applicative f => (Name File -> f (Name File)) -> Path a File s -> f (Path a File s) -renameFile' f (FileIn p f0) = FileIn p <$> f f0 +renameFile' f (In p f0) = In p <$> f f0 renameFile' _ p = pure p -- | Renames a directory path. Note: This is a simple rename of the terminal -- | directory name, not a "move". renameDir :: forall a s. (Name Dir -> Name Dir) -> Path a Dir s -> Path a Dir s -renameDir f (DirIn p d) = DirIn p (f d) +renameDir f (In p d) = In p (f d) renameDir _ p = p -- | Canonicalizes a path, by reducing things in the form `/x/../` to just `/x/`. @@ -385,46 +386,43 @@ canonicalize = snd <<< canonicalize' canonicalize' :: forall a b s. Path a b s -> Tuple Boolean (Path a b s) canonicalize' Current = Tuple false Current canonicalize' Root = Tuple false Root -canonicalize' (ParentIn (FileIn p f)) = Tuple true (unsafeCoerceType $ snd $ canonicalize' p) -canonicalize' (ParentIn (DirIn p f)) = Tuple true (unsafeCoerceType $ snd $ canonicalize' p) +canonicalize' (ParentIn (In p f)) = Tuple true (unsafeCoerceType $ snd $ canonicalize' p) canonicalize' (ParentIn p) = case canonicalize' p of Tuple changed p' -> let p'' = ParentIn p' in if changed then canonicalize' p'' else Tuple changed p'' -canonicalize' (FileIn p f) = flip FileIn f <$> canonicalize' p -canonicalize' (DirIn p d) = flip DirIn d <$> canonicalize' p +canonicalize' (In p f) = flip In f <$> canonicalize' p -unsafePrintPath' :: forall a b s. Escaper -> Path a b s -> String +unsafePrintPath' :: forall a b s. SplitDirOrFileName b => Escaper -> Path a b s -> String unsafePrintPath' r = go where - go :: forall a' b' s'. Path a' b' s' -> String + go :: forall a' b' s'. SplitDirOrFileName b' => Path a' b' s' -> String go Current = "./" go Root = "/" go (ParentIn p) = go p <> "../" - go (DirIn p@(FileIn _ _ ) d) = go p <> "/" <> escape (runName d) <> "/" -- dir inside a file - go (DirIn p d) = go p <> escape (runName d) <> "/" -- dir inside a dir - go (FileIn p@(FileIn _ _) f) = go p <> "/" <> escape (runName f) -- file inside a file - go (FileIn p f) = go p <> escape (runName f) + go (In p n) = case dirOrFileName n of + Left dirN -> go p <> escape (runName dirN) <> "/" + Right fileN -> go p <> escape (runName fileN) escape = runEscaper r -unsafePrintPath :: forall a b s. Path a b s -> String +unsafePrintPath :: forall a b s. SplitDirOrFileName b => Path a b s -> String unsafePrintPath = unsafePrintPath' posixEscaper -- | Prints a `Path` into its canonical `String` representation. For security -- | reasons, the path must be sandboxed before it can be rendered to a string. -printPath :: forall a b. Path a b Sandboxed -> String +printPath :: forall a b. SplitDirOrFileName b => Path a b Sandboxed -> String printPath = unsafePrintPath -- | Prints a `Path` into its canonical `String` representation, using the -- | specified escaper to escape special characters in path segments. For -- | security reasons, the path must be sandboxed before rendering to string. -printPath' :: forall a b. Escaper -> Path a b Sandboxed -> String +printPath' :: forall a b. SplitDirOrFileName b => Escaper -> Path a b Sandboxed -> String printPath' = unsafePrintPath' -- | Determines if two paths have the exact same representation. Note that -- | two paths may represent the same path even if they have different -- | representations! -identicalPath :: forall a a' b b' s s'. Path a b s -> Path a' b' s' -> Boolean +identicalPath :: forall a a' b b' s s'. SplitDirOrFileName b => SplitDirOrFileName b' => Path a b s -> Path a' b' s' -> Boolean identicalPath p1 p2 = show p1 == show p2 -- | Makes one path relative to another reference path, if possible, otherwise @@ -432,42 +430,59 @@ identicalPath p1 p2 = show p1 == show p2 -- | reference path. -- | -- | Note there are some cases this function cannot handle. -relativeTo :: forall a b s s'. SplitDirOrFile b => Path a b s -> Path a Dir s' -> Maybe (Path Rel b s') +relativeTo :: forall a b s s'. SplitDirOrFile b => SplitDirOrFileName b => Path a b s -> Path a Dir s' -> Maybe (Path Rel b s') relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) where - relativeTo' :: forall b'. SplitDirOrFile b' => Path a b' s -> Path a Dir s' -> Maybe (Path Rel b' s') + relativeTo' :: forall b'. SplitDirOrFile b' => SplitDirOrFileName b' => Path a b' s -> Path a Dir s' -> Maybe (Path Rel b' s') relativeTo' Root Root = pure Current relativeTo' Current Current = pure Current relativeTo' cp1 cp2 | identicalPath cp1 cp2 = pure Current | otherwise = do - peeled <- bitraverse peel peel (dirOrFile cp1) - case peeled of - Left (Tuple cp1Parent cp1Top) -> do - rel <- relativeTo' cp1Parent cp2 - pure $ rel DirIn Current cp1Top - Right (Tuple cp1Parent cp1Top) -> do - rel <- relativeTo' cp1Parent cp2 - pure $ rel FileIn Current cp1Top + mapInsidePath cp1 + (\dirP -> do + Tuple cp1Path dirN <- peel dirP + rel <- relativeTo' cp1Path cp2 + pure $ rel In Current dirN) + (\fileP -> do + Tuple cp1Path fileN <- peel fileP + rel <- relativeTo' cp1Path cp2 + pure $ rel In Current fileN) + -- | Attempts to sandbox a path relative to some directory. If successful, the sandboxed -- | directory will be returned relative to the sandbox directory (although this can easily -- | be converted into an absolute path using ``). -- | -- | This combinator can be used to ensure that paths which originate from user-code -- | cannot access data outside a given directory. -sandbox :: forall a b s. SplitDirOrFile b => Path a Dir Sandboxed -> Path a b s -> Maybe (Path Rel b Sandboxed) +sandbox :: forall a b s. SplitDirOrFileName b => SplitDirOrFile b => Path a Dir Sandboxed -> Path a b s -> Maybe (Path Rel b Sandboxed) sandbox p1 p2 = p2 `relativeTo` p1 +mapInsidePath :: forall a a' b s s' f. SplitDirOrFile b => Functor f => Path a b s -> (Path a Dir s -> f (Path a' Dir s')) -> (Path a File s -> f (Path a' File s')) -> f (Path a' b s') +mapInsidePath p onDir onFile = case dirOrFile p of + Left p' -> unsafeCoerce $ onDir p' + Right p' -> unsafeCoerce $ onFile p' + +mapInsideName :: forall b. SplitDirOrFileName b => Name b -> (Name Dir -> Name Dir) -> (Name File -> Name File) -> Name b +mapInsideName p onDir onFile = case dirOrFileName p of + Left p' -> unsafeCoerce $ onDir p' + Right p' -> unsafeCoerce $ onFile p' + -- | Refines path segments but does not change anything else. -refine :: forall a b s. (Name File -> Name File) -> (Name Dir -> Name Dir) -> Path a b s -> Path a b s +refine :: forall a b s. SplitDirOrFileName b => (Name File -> Name File) -> (Name Dir -> Name Dir) -> Path a b s -> Path a b s refine f d = go where - go :: forall a' b' s'. Path a' b' s' -> Path a' b' s' - go (Current ) = Current - go (Root ) = Root - go (ParentIn p ) = ParentIn (go p) - go (DirIn p d0) = DirIn (go p) (d d0) - go (FileIn p f0) = FileIn (go p) (f f0) + go :: forall a' b' s'. SplitDirOrFileName b' => Path a' b' s' -> Path a' b' s' + go Current = Current + go Root = Root + go (ParentIn p) = ParentIn (go p) + go (In p name) = case dirOrFileName name of + Left dirN -> + -- We need to unwrap name so it compiles :(( + let Name n = (d dirN) in In (go p) (Name n) + Right fileN -> + -- We need to unwrap name so it compiles :(( + let Name n = (f fileN) in In (go p) (Name n) type ParseError = Unit @@ -504,10 +519,7 @@ parsePath rd ad rf af err p = base else if NEString.toString seg == ".." then ParentIn base - else if isFile && idx == last then - FileIn (unsafeCoerceType base) (Name seg) - else - DirIn (unsafeCoerceType base) (Name seg) + else In (unsafeCoerceType base) (Name seg) in case traverse NEString.fromString segsDropped of Nothing -> err unit @@ -533,17 +545,20 @@ parseRelDir = parsePath Just (const Nothing) (const Nothing) (const Nothing) (co parseAbsDir :: String -> Maybe (AbsDir Unsandboxed) parseAbsDir = parsePath (const Nothing) Just (const Nothing) (const Nothing) (const Nothing) -instance showPath :: Show (Path a b s) where +instance showPath :: SplitDirOrFileName b => Show (Path a b s) where show Current = "currentDir" show Root = "rootDir" show (ParentIn p) = "(parentDir' " <> show p <> ")" - show (FileIn p (Name f)) = "(" <> show p <> " file " <> show f <> ")" - show (DirIn p (Name f)) = "(" <> show p <> " dir " <> show f <> ")" + show (In p n ) = case dirOrFileName n of + Left dirN -> + "(" <> show p <> " dir " <> show dirN <> ")" + Right fileN -> + "(" <> show p <> " file " <> show fileN <> ")" -instance eqPath :: Eq (Path a b s) where +instance eqPath :: SplitDirOrFileName b => Eq (Path a b s) where eq p1 p2 = canonicalize p1 `identicalPath` canonicalize p2 -instance ordPath :: Ord (Path a b s) where +instance ordPath :: SplitDirOrFileName b => Ord (Path a b s) where compare p1 p2 = go (canonicalize p1) (canonicalize p2) where go Current Current = EQ @@ -555,10 +570,7 @@ instance ordPath :: Ord (Path a b s) where go (ParentIn p1') (ParentIn p2') = compare p1' p2' go (ParentIn _) _ = LT go _ (ParentIn _) = GT - go (DirIn p1' d1) (DirIn p2' d2) = compare p1' p2' <> compare d1 d2 - go (DirIn _ _) _ = LT - go _ (DirIn _ _) = GT - go (FileIn p1' f1) (FileIn p2' f2) = compare p1' p2' <> compare f1 f2 + go (In p1' d1) (In p2' d2) = compare p1' p2' <> compare d1 d2 instance showName :: Show (Name a) where show (Name name) = "(Name " <> show name <> ")" diff --git a/test/Main.purs b/test/Main.purs index 98a0d3c..5b59231 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -7,7 +7,7 @@ import Control.Monad.Eff.Console (CONSOLE, info, infoShow) import Data.Either (either) import Data.Foldable (foldl) import Data.Maybe (Maybe(..), fromJust) -import Data.Path.Pathy (class SplitDirOrFile, class SplitRelOrAbs, Abs, Dir, File, Path, Rel, Sandboxed, canonicalize, currentDir, depth, dir, dirOrFile, dropExtension, file, parentDir', parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, relOrAbs, renameFile', rootDir, sandbox, unsafePrintPath, unsandbox, (<..>), (<.>), ()) +import Data.Path.Pathy (class SplitDirOrFileName, class SplitDirOrFile, class SplitRelOrAbs, Abs, Dir, File, Path, Rel, Sandboxed, canonicalize, currentDir, depth, dir, dirOrFile, dropExtension, file, parentDir', parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, relOrAbs, renameFile', rootDir, sandbox, unsafePrintPath, unsandbox, (<..>), (<.>), ()) import Data.String as Str import Data.String.NonEmpty (NonEmptyString) import Data.Symbol (SProxy(..)) @@ -26,7 +26,7 @@ test name actual expected= do infoShow $ "Test: " <> name if expected == actual then infoShow $ "Passed: " <> (show expected) else infoShow $ "Failed: Expected " <> (show expected) <> " but found " <> (show actual) -test' :: forall a b s eff. String -> Path a b s -> String -> Eff (console :: CONSOLE | eff) Unit +test' :: forall a b s eff. SplitDirOrFileName b => String -> Path a b s -> String -> Eff (console :: CONSOLE | eff) Unit test' n p s = test n (unsafePrintPath p) s newtype ArbPath = ArbPath (Path Abs File Sandboxed) From 08efb73129676af9ebd44b41e791c0e656722cde Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 13 Feb 2018 14:53:19 +0100 Subject: [PATCH 08/59] join SplitDirOrFileName and SplitDirOrFile now we have one universal SplitDirOrFile --- src/Data/Path/Pathy.purs | 54 +++++++++++++++++++++------------------- test/Main.purs | 4 +-- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 565a8ec..4f5a334 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -51,8 +51,8 @@ module Data.Path.Pathy , class SplitRelOrAbs , relOrAbs , class SplitDirOrFile + , dirOrFileF , dirOrFile - , class SplitDirOrFileName , dirOrFileName , refine , relativeTo @@ -76,7 +76,7 @@ import Data.Either (Either(..)) import Data.FoldableWithIndex (foldlWithIndex) import Data.Identity (Identity(..)) import Data.Maybe (Maybe(..)) -import Data.Newtype (un) +import Data.Newtype (class Newtype, un) import Data.String as S import Data.String.NonEmpty (NonEmptyString, appendString) import Data.String.NonEmpty (fromString, toString) as NEString @@ -163,18 +163,20 @@ type RelPath s = AnyPath Rel s -- | A type describing an absolute file or directory path. type AbsPath s = AnyPath Abs s -class SplitDirOrFile (b :: DirOrFile) where - dirOrFile :: forall a s. Path a b s -> AnyPath a s +newtype PathFlipped a s b = PathFlipped (Path a b s) +derive instance newtypePathFlipped ∷ Newtype (PathFlipped a s b) _ -instance relSplitDirOrFile :: SplitDirOrFile Dir where dirOrFile = Left -instance absSplitDirOrFile :: SplitDirOrFile File where dirOrFile = Right +class SplitDirOrFile (x :: DirOrFile) where + dirOrFileF :: forall f. f x -> Either (f Dir) (f File) -class SplitDirOrFileName (b :: DirOrFile) where - dirOrFileName :: Name b -> Either (Name Dir) (Name File) - -instance relSplitDirOrFileName :: SplitDirOrFileName Dir where dirOrFileName = Left -instance absSplitDirOrFileName :: SplitDirOrFileName File where dirOrFileName = Right +instance relSplitDirOrFile :: SplitDirOrFile Dir where dirOrFileF = Left +instance absSplitDirOrFile :: SplitDirOrFile File where dirOrFileF = Right +dirOrFile :: forall a b s. SplitDirOrFile b => Path a b s -> AnyPath a s +dirOrFile p = bimap (un PathFlipped) (un PathFlipped) $ dirOrFileF (PathFlipped p) + +dirOrFileName :: forall b. SplitDirOrFile b => Name b -> Either (Name Dir) (Name File) +dirOrFileName = dirOrFileF class SplitRelOrAbs (a :: RelOrAbs) where relOrAbs :: forall b s. Path a b s -> Either (Path Rel b s) (Path Abs b s) @@ -393,10 +395,10 @@ canonicalize' (ParentIn p) = case canonicalize' p of in if changed then canonicalize' p'' else Tuple changed p'' canonicalize' (In p f) = flip In f <$> canonicalize' p -unsafePrintPath' :: forall a b s. SplitDirOrFileName b => Escaper -> Path a b s -> String +unsafePrintPath' :: forall a b s. SplitDirOrFile b => Escaper -> Path a b s -> String unsafePrintPath' r = go where - go :: forall a' b' s'. SplitDirOrFileName b' => Path a' b' s' -> String + go :: forall a' b' s'. SplitDirOrFile b' => Path a' b' s' -> String go Current = "./" go Root = "/" go (ParentIn p) = go p <> "../" @@ -405,24 +407,24 @@ unsafePrintPath' r = go Right fileN -> go p <> escape (runName fileN) escape = runEscaper r -unsafePrintPath :: forall a b s. SplitDirOrFileName b => Path a b s -> String +unsafePrintPath :: forall a b s. SplitDirOrFile b => Path a b s -> String unsafePrintPath = unsafePrintPath' posixEscaper -- | Prints a `Path` into its canonical `String` representation. For security -- | reasons, the path must be sandboxed before it can be rendered to a string. -printPath :: forall a b. SplitDirOrFileName b => Path a b Sandboxed -> String +printPath :: forall a b. SplitDirOrFile b => Path a b Sandboxed -> String printPath = unsafePrintPath -- | Prints a `Path` into its canonical `String` representation, using the -- | specified escaper to escape special characters in path segments. For -- | security reasons, the path must be sandboxed before rendering to string. -printPath' :: forall a b. SplitDirOrFileName b => Escaper -> Path a b Sandboxed -> String +printPath' :: forall a b. SplitDirOrFile b => Escaper -> Path a b Sandboxed -> String printPath' = unsafePrintPath' -- | Determines if two paths have the exact same representation. Note that -- | two paths may represent the same path even if they have different -- | representations! -identicalPath :: forall a a' b b' s s'. SplitDirOrFileName b => SplitDirOrFileName b' => Path a b s -> Path a' b' s' -> Boolean +identicalPath :: forall a a' b b' s s'. SplitDirOrFile b => SplitDirOrFile b' => Path a b s -> Path a' b' s' -> Boolean identicalPath p1 p2 = show p1 == show p2 -- | Makes one path relative to another reference path, if possible, otherwise @@ -430,10 +432,10 @@ identicalPath p1 p2 = show p1 == show p2 -- | reference path. -- | -- | Note there are some cases this function cannot handle. -relativeTo :: forall a b s s'. SplitDirOrFile b => SplitDirOrFileName b => Path a b s -> Path a Dir s' -> Maybe (Path Rel b s') +relativeTo :: forall a b s s'. SplitDirOrFile b => Path a b s -> Path a Dir s' -> Maybe (Path Rel b s') relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) where - relativeTo' :: forall b'. SplitDirOrFile b' => SplitDirOrFileName b' => Path a b' s -> Path a Dir s' -> Maybe (Path Rel b' s') + relativeTo' :: forall b'. SplitDirOrFile b' => Path a b' s -> Path a Dir s' -> Maybe (Path Rel b' s') relativeTo' Root Root = pure Current relativeTo' Current Current = pure Current relativeTo' cp1 cp2 @@ -455,7 +457,7 @@ relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) -- | -- | This combinator can be used to ensure that paths which originate from user-code -- | cannot access data outside a given directory. -sandbox :: forall a b s. SplitDirOrFileName b => SplitDirOrFile b => Path a Dir Sandboxed -> Path a b s -> Maybe (Path Rel b Sandboxed) +sandbox :: forall a b s. SplitDirOrFile b => Path a Dir Sandboxed -> Path a b s -> Maybe (Path Rel b Sandboxed) sandbox p1 p2 = p2 `relativeTo` p1 mapInsidePath :: forall a a' b s s' f. SplitDirOrFile b => Functor f => Path a b s -> (Path a Dir s -> f (Path a' Dir s')) -> (Path a File s -> f (Path a' File s')) -> f (Path a' b s') @@ -463,16 +465,16 @@ mapInsidePath p onDir onFile = case dirOrFile p of Left p' -> unsafeCoerce $ onDir p' Right p' -> unsafeCoerce $ onFile p' -mapInsideName :: forall b. SplitDirOrFileName b => Name b -> (Name Dir -> Name Dir) -> (Name File -> Name File) -> Name b +mapInsideName :: forall b. SplitDirOrFile b => Name b -> (Name Dir -> Name Dir) -> (Name File -> Name File) -> Name b mapInsideName p onDir onFile = case dirOrFileName p of Left p' -> unsafeCoerce $ onDir p' Right p' -> unsafeCoerce $ onFile p' -- | Refines path segments but does not change anything else. -refine :: forall a b s. SplitDirOrFileName b => (Name File -> Name File) -> (Name Dir -> Name Dir) -> Path a b s -> Path a b s +refine :: forall a b s. SplitDirOrFile b => (Name File -> Name File) -> (Name Dir -> Name Dir) -> Path a b s -> Path a b s refine f d = go where - go :: forall a' b' s'. SplitDirOrFileName b' => Path a' b' s' -> Path a' b' s' + go :: forall a' b' s'. SplitDirOrFile b' => Path a' b' s' -> Path a' b' s' go Current = Current go Root = Root go (ParentIn p) = ParentIn (go p) @@ -545,7 +547,7 @@ parseRelDir = parsePath Just (const Nothing) (const Nothing) (const Nothing) (co parseAbsDir :: String -> Maybe (AbsDir Unsandboxed) parseAbsDir = parsePath (const Nothing) Just (const Nothing) (const Nothing) (const Nothing) -instance showPath :: SplitDirOrFileName b => Show (Path a b s) where +instance showPath :: SplitDirOrFile b => Show (Path a b s) where show Current = "currentDir" show Root = "rootDir" show (ParentIn p) = "(parentDir' " <> show p <> ")" @@ -555,10 +557,10 @@ instance showPath :: SplitDirOrFileName b => Show (Path a b s) where Right fileN -> "(" <> show p <> " file " <> show fileN <> ")" -instance eqPath :: SplitDirOrFileName b => Eq (Path a b s) where +instance eqPath :: SplitDirOrFile b => Eq (Path a b s) where eq p1 p2 = canonicalize p1 `identicalPath` canonicalize p2 -instance ordPath :: SplitDirOrFileName b => Ord (Path a b s) where +instance ordPath :: SplitDirOrFile b => Ord (Path a b s) where compare p1 p2 = go (canonicalize p1) (canonicalize p2) where go Current Current = EQ diff --git a/test/Main.purs b/test/Main.purs index 5b59231..e6c75f4 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -7,7 +7,7 @@ import Control.Monad.Eff.Console (CONSOLE, info, infoShow) import Data.Either (either) import Data.Foldable (foldl) import Data.Maybe (Maybe(..), fromJust) -import Data.Path.Pathy (class SplitDirOrFileName, class SplitDirOrFile, class SplitRelOrAbs, Abs, Dir, File, Path, Rel, Sandboxed, canonicalize, currentDir, depth, dir, dirOrFile, dropExtension, file, parentDir', parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, relOrAbs, renameFile', rootDir, sandbox, unsafePrintPath, unsandbox, (<..>), (<.>), ()) +import Data.Path.Pathy (class SplitDirOrFile, class SplitRelOrAbs, Abs, Dir, File, Path, Rel, Sandboxed, canonicalize, currentDir, depth, dir, dirOrFile, dropExtension, file, parentDir', parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, relOrAbs, renameFile', rootDir, sandbox, unsafePrintPath, unsandbox, (<..>), (<.>), ()) import Data.String as Str import Data.String.NonEmpty (NonEmptyString) import Data.Symbol (SProxy(..)) @@ -26,7 +26,7 @@ test name actual expected= do infoShow $ "Test: " <> name if expected == actual then infoShow $ "Passed: " <> (show expected) else infoShow $ "Failed: Expected " <> (show expected) <> " but found " <> (show actual) -test' :: forall a b s eff. SplitDirOrFileName b => String -> Path a b s -> String -> Eff (console :: CONSOLE | eff) Unit +test' :: forall a b s eff. SplitDirOrFile b => String -> Path a b s -> String -> Eff (console :: CONSOLE | eff) Unit test' n p s = test n (unsafePrintPath p) s newtype ArbPath = ArbPath (Path Abs File Sandboxed) From 5b600abfe55b4d9533fe8923ad9ebce22345e326 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 13 Feb 2018 15:22:41 +0100 Subject: [PATCH 09/59] overName instead of mapInside{Path,Name} --- src/Data/Path/Pathy.purs | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 4f5a334..e960838 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -441,15 +441,21 @@ relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) relativeTo' cp1 cp2 | identicalPath cp1 cp2 = pure Current | otherwise = do - mapInsidePath cp1 - (\dirP -> do - Tuple cp1Path dirN <- peel dirP + Tuple cp1Path name <- peel cp1 rel <- relativeTo' cp1Path cp2 - pure $ rel In Current dirN) - (\fileP -> do - Tuple cp1Path fileN <- peel fileP - rel <- relativeTo' cp1Path cp2 - pure $ rel In Current fileN) + pure $ overName name + (\dirN -> rel In Current dirN) + (\fileN -> rel In Current fileN) + overName + :: forall n a' s'' + . SplitDirOrFile n + => Name n + -> (Name Dir -> Path a' Dir s'') + -> (Name File -> Path a' File s'') + -> Path a' n s'' + overName p onDir onFile = case dirOrFileName p of + Left p' -> unsafeCoerce $ onDir p' + Right p' -> unsafeCoerce $ onFile p' -- | Attempts to sandbox a path relative to some directory. If successful, the sandboxed -- | directory will be returned relative to the sandbox directory (although this can easily @@ -460,16 +466,6 @@ relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) sandbox :: forall a b s. SplitDirOrFile b => Path a Dir Sandboxed -> Path a b s -> Maybe (Path Rel b Sandboxed) sandbox p1 p2 = p2 `relativeTo` p1 -mapInsidePath :: forall a a' b s s' f. SplitDirOrFile b => Functor f => Path a b s -> (Path a Dir s -> f (Path a' Dir s')) -> (Path a File s -> f (Path a' File s')) -> f (Path a' b s') -mapInsidePath p onDir onFile = case dirOrFile p of - Left p' -> unsafeCoerce $ onDir p' - Right p' -> unsafeCoerce $ onFile p' - -mapInsideName :: forall b. SplitDirOrFile b => Name b -> (Name Dir -> Name Dir) -> (Name File -> Name File) -> Name b -mapInsideName p onDir onFile = case dirOrFileName p of - Left p' -> unsafeCoerce $ onDir p' - Right p' -> unsafeCoerce $ onFile p' - -- | Refines path segments but does not change anything else. refine :: forall a b s. SplitDirOrFile b => (Name File -> Name File) -> (Name Dir -> Name Dir) -> Path a b s -> Path a b s refine f d = go From dee127aa925eb29e198854121f91b90a1482907c Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 13 Feb 2018 15:24:58 +0100 Subject: [PATCH 10/59] remove some unsafe leftover --- src/Data/Path/Pathy.purs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index e960838..c108a96 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -281,14 +281,14 @@ appendPath :: forall a b s. Path a Dir s -> Path Rel b s -> Path a b s appendPath Current Current = Current appendPath Root Current = Root appendPath (ParentIn p1) Current = ParentIn (p1 Current) -appendPath (In p1 f1) Current = In (unsafeCoerce $ p1 Current) (unsafeCoerce f1) +appendPath (In p1 f1) Current = In (p1 Current) (unsafeCoerce $ f1) appendPath p1 (ParentIn p2) = ParentIn (p1 p2) appendPath p1 (In p2 f2) = In (p1 p2) f2 -- following cases don't make sense but cannot exist appendPath Current Root = Current appendPath Root Root = Root appendPath (ParentIn p1) Root = ParentIn (p1 Current) -appendPath (In p1 f1) Root = In (unsafeCoerce $ p1 Current) (unsafeCoerce $f1) +appendPath (In p1 f1) Root = In (p1 Current) (unsafeCoerce $ f1) infixl 6 appendPath as @@ -442,7 +442,7 @@ relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) | identicalPath cp1 cp2 = pure Current | otherwise = do Tuple cp1Path name <- peel cp1 - rel <- relativeTo' cp1Path cp2 + rel <- relativeTo' cp1Path cp2 pure $ overName name (\dirN -> rel In Current dirN) (\fileN -> rel In Current fileN) From 9ef9a9f937c9e382c518fc60bc4f825b76c210d7 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 13 Feb 2018 15:27:35 +0100 Subject: [PATCH 11/59] use published version of ps-strings --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 4af6dc6..b132dcb 100644 --- a/bower.json +++ b/bower.json @@ -22,7 +22,7 @@ "purescript-lists": "^4.0.0", "purescript-partial": "^1.2.0", "purescript-profunctor": "^3.0.0", - "purescript-strings": "purescript/purescript-strings#nonempty", + "purescript-strings": "^3.5.0", "purescript-transformers": "^3.0.0", "purescript-unsafe-coerce": "^3.0.0" }, From c2f5cdd1271c96512a1a5152b4e6f6b300a90530 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 13 Feb 2018 16:35:00 +0100 Subject: [PATCH 12/59] We should only have DirPath in ParentIn --- src/Data/Path/Pathy.purs | 48 ++++++++++++++++++---------------------- test/Main.purs | 18 +++++++-------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index c108a96..3e7e09b 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -38,7 +38,6 @@ module Data.Path.Pathy , pathName , identicalPath , parentDir - , parentDir' , peel , posixEscaper , parsePath @@ -81,7 +80,7 @@ import Data.String as S import Data.String.NonEmpty (NonEmptyString, appendString) import Data.String.NonEmpty (fromString, toString) as NEString import Data.Traversable (traverse) -import Data.Tuple (Tuple(..), fst, snd) +import Data.Tuple (Tuple(..), snd) import Partial.Unsafe (unsafeCrashWith) import Unsafe.Coerce (unsafeCoerce) @@ -130,14 +129,14 @@ runName (Name name) = NEString.toString name -- | -- | This ADT allows invalid paths (e.g. paths inside files), but there is no -- | possible way for such paths to be constructed by user-land code. The only --- | "invalid path" that may be constructed is using the `parentDir'` function, e.g. --- | `parentDir' rootDir`, or by parsing an equivalent string such as `/../`, +-- | "invalid path" that may be constructed is using the `parentDir` function, e.g. +-- | `parentDir rootDir`, or by parsing an equivalent string such as `/../`, -- | but such paths are marked as unsandboxed, and may not be rendered to strings -- | until they are first sandboxed to some directory. data Path (a :: RelOrAbs) (b :: DirOrFile) (s :: SandboxedOrNot) = Current | Root - | ParentIn (Path a b s) + | ParentIn (Path a Dir s) | In (Path a Dir s) (Name b) -- | A type describing a file whose location is given relative to some other, @@ -277,18 +276,18 @@ pathName :: forall b s. AnyPath b s -> Either (Maybe (Name Dir)) (Name File) pathName = bimap dirName fileName -- | Given a directory path, appends either a file or directory to the path. -appendPath :: forall a b s. Path a Dir s -> Path Rel b s -> Path a b s +appendPath :: forall a b s. SplitDirOrFile b => Path a Dir s -> Path Rel b s -> Path a b s +appendPath _ Root = unsafeCrashWith "Imposible as Root can't be Path Rel" appendPath Current Current = Current appendPath Root Current = Root -appendPath (ParentIn p1) Current = ParentIn (p1 Current) -appendPath (In p1 f1) Current = In (p1 Current) (unsafeCoerce $ f1) +-- TODO this shold be correct? +-- appendPath (ParentIn p) c@Current = ParentIn (p c) +appendPath (ParentIn p) Current = ParentIn (p Current) +appendPath (In p1 (Name f1)) c@Current = case dirOrFile c of + Left dir -> In (p1 dir) (Name f1) + Right _ -> unsafeCrashWith "Imposible" appendPath p1 (ParentIn p2) = ParentIn (p1 p2) appendPath p1 (In p2 f2) = In (p1 p2) f2 --- following cases don't make sense but cannot exist -appendPath Current Root = Current -appendPath Root Root = Root -appendPath (ParentIn p1) Root = ParentIn (p1 Current) -appendPath (In p1 f1) Root = In (p1 Current) (unsafeCoerce $ f1) infixl 6 appendPath as @@ -307,10 +306,11 @@ infixl 6 setExtension as <.> -- | its previous sandbox. parentAppend :: forall a b s s' - . Path a Dir s + . SplitDirOrFile b + => Path a Dir s -> Path Rel b s' -> Path a b Unsandboxed -parentAppend d p = parentDir' d unsandbox p +parentAppend d p = parentDir d unsandbox p infixl 6 parentAppend as <..> @@ -337,12 +337,6 @@ depth Root = 0 depth (ParentIn p) = depth p - 1 depth (In p _) = depth p + 1 --- | Attempts to extract out the parent directory of the specified path. If the --- | function would have to use a relative path in the return value, the function will --- | instead return `Nothing`. -parentDir :: forall a b s. Path a b s -> Maybe (Path a Dir s) -parentDir p = fst <$> peel p - -- | Unsandboxes any path (whether sandboxed or not). unsandbox :: forall a b s. Path a b s -> Path a b Unsandboxed unsandbox Current = Current @@ -352,8 +346,8 @@ unsandbox (In p n) = In (unsandbox p) n -- | Creates a path that points to the parent directory of the specified path. -- | This function always unsandboxes the path. -parentDir' :: forall a b s. Path a b s -> Path a Dir Unsandboxed -parentDir' = ParentIn <<< unsafeCoerceType <<< unsandbox +parentDir :: forall a b s. Path a Dir s -> Path a Dir Unsandboxed +parentDir = ParentIn <<< unsandbox unsafeCoerceType :: forall a b b' s. Path a b s -> Path a b' s unsafeCoerceType = unsafeCoerce @@ -388,7 +382,7 @@ canonicalize = snd <<< canonicalize' canonicalize' :: forall a b s. Path a b s -> Tuple Boolean (Path a b s) canonicalize' Current = Tuple false Current canonicalize' Root = Tuple false Root -canonicalize' (ParentIn (In p f)) = Tuple true (unsafeCoerceType $ snd $ canonicalize' p) +canonicalize' (ParentIn (In p f)) = Tuple true (unsafeCoerceType $ snd $ canonicalize' p) canonicalize' (ParentIn p) = case canonicalize' p of Tuple changed p' -> let p'' = ParentIn p' @@ -511,12 +505,12 @@ parsePath rd ad rf af err p = false, true -> segsRaw false, false -> dropEnd 1 segsRaw last = length segsDropped - 1 - folder :: forall a b s. Int -> Path a b s -> NonEmptyString -> Path a b s + folder :: forall a b s. SplitDirOrFile b => Int -> Path a b s -> NonEmptyString -> Path a b s folder idx base seg = if NEString.toString seg == "." then base else if NEString.toString seg == ".." then - ParentIn base + ParentIn $ unsafeCoerceType base else In (unsafeCoerceType base) (Name seg) in case traverse NEString.fromString segsDropped of @@ -546,7 +540,7 @@ parseAbsDir = parsePath (const Nothing) Just (const Nothing) (const Nothing) (co instance showPath :: SplitDirOrFile b => Show (Path a b s) where show Current = "currentDir" show Root = "rootDir" - show (ParentIn p) = "(parentDir' " <> show p <> ")" + show (ParentIn p) = "(parentDir " <> show p <> ")" show (In p n ) = case dirOrFileName n of Left dirN -> "(" <> show p <> " dir " <> show dirN <> ")" diff --git a/test/Main.purs b/test/Main.purs index e6c75f4..ac4293c 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -7,7 +7,7 @@ import Control.Monad.Eff.Console (CONSOLE, info, infoShow) import Data.Either (either) import Data.Foldable (foldl) import Data.Maybe (Maybe(..), fromJust) -import Data.Path.Pathy (class SplitDirOrFile, class SplitRelOrAbs, Abs, Dir, File, Path, Rel, Sandboxed, canonicalize, currentDir, depth, dir, dirOrFile, dropExtension, file, parentDir', parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, relOrAbs, renameFile', rootDir, sandbox, unsafePrintPath, unsandbox, (<..>), (<.>), ()) +import Data.Path.Pathy (class SplitDirOrFile, class SplitRelOrAbs, Abs, Dir, File, Path, Rel, Sandboxed, canonicalize, currentDir, depth, dir, dirOrFile, dropExtension, file, parentDir, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, relOrAbs, renameFile', rootDir, sandbox, unsafePrintPath, unsandbox, (<..>), (<.>), ()) import Data.String as Str import Data.String.NonEmpty (NonEmptyString) import Data.Symbol (SProxy(..)) @@ -79,7 +79,7 @@ main = do -- Should not compile: -- test -- "printPath -- cannot print unsandboxed" - -- (printPath (parentDir' currentDir)) + -- (printPath (parentDir currentDir)) -- "./../" test' "() - two directories" @@ -103,15 +103,15 @@ main = do "./image.png" test' "printPath - ./../" - (parentDir' currentDir) + (parentDir currentDir) "./../" test' "() - ./../foo/" - (parentDir' currentDir unsandbox (dirFoo)) + (parentDir currentDir unsandbox (dirFoo)) "./../foo/" - test' "parentDir' - ./../foo/../" - ((parentDir' currentDir unsandbox (dirFoo)) (parentDir' currentDir)) + test' "parentDir - ./../foo/../" + ((parentDir currentDir unsandbox (dirFoo)) (parentDir currentDir)) "./../foo/../" test' "(<..>) - ./../" @@ -127,11 +127,11 @@ main = do "./../foo/../" test' "canonicalize - 1 down, 1 up" - (canonicalize $ parentDir' $ dirFoo) + (canonicalize $ parentDir $ dirFoo) "./" test' "canonicalize - 2 down, 2 up" - (canonicalize (parentDir' (parentDir' (dirFoo dirBar)))) + (canonicalize (parentDir (parentDir (dirFoo dirBar)))) "./" test "renameFile - single level deep" @@ -144,7 +144,7 @@ main = do "./bar/" test "depth - negative" - (depth (parentDir' $ parentDir' $ parentDir' $ currentDir)) (-3) + (depth (parentDir $ parentDir $ parentDir $ currentDir)) (-3) test "parseRelFile - image.png" (parseRelFile "image.png") From ae145cd25cc3b716074ea194946bd3a89082157c Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 13 Feb 2018 17:03:56 +0100 Subject: [PATCH 13/59] clenup appendPath --- src/Data/Path/Pathy.purs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 3e7e09b..0d78efb 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -276,18 +276,14 @@ pathName :: forall b s. AnyPath b s -> Either (Maybe (Name Dir)) (Name File) pathName = bimap dirName fileName -- | Given a directory path, appends either a file or directory to the path. -appendPath :: forall a b s. SplitDirOrFile b => Path a Dir s -> Path Rel b s -> Path a b s +appendPath :: forall a b s. Path a Dir s -> Path Rel b s -> Path a b s appendPath _ Root = unsafeCrashWith "Imposible as Root can't be Path Rel" appendPath Current Current = Current appendPath Root Current = Root --- TODO this shold be correct? --- appendPath (ParentIn p) c@Current = ParentIn (p c) appendPath (ParentIn p) Current = ParentIn (p Current) -appendPath (In p1 (Name f1)) c@Current = case dirOrFile c of - Left dir -> In (p1 dir) (Name f1) - Right _ -> unsafeCrashWith "Imposible" +appendPath (In p (Name d)) Current = In (p Current) (Name d) appendPath p1 (ParentIn p2) = ParentIn (p1 p2) -appendPath p1 (In p2 f2) = In (p1 p2) f2 +appendPath p1 (In p2 n2) = In (p1 p2) n2 infixl 6 appendPath as @@ -306,8 +302,7 @@ infixl 6 setExtension as <.> -- | its previous sandbox. parentAppend :: forall a b s s' - . SplitDirOrFile b - => Path a Dir s + . Path a Dir s -> Path Rel b s' -> Path a b Unsandboxed parentAppend d p = parentDir d unsandbox p @@ -346,7 +341,7 @@ unsandbox (In p n) = In (unsandbox p) n -- | Creates a path that points to the parent directory of the specified path. -- | This function always unsandboxes the path. -parentDir :: forall a b s. Path a Dir s -> Path a Dir Unsandboxed +parentDir :: forall a s. Path a Dir s -> Path a Dir Unsandboxed parentDir = ParentIn <<< unsandbox unsafeCoerceType :: forall a b b' s. Path a b s -> Path a b' s From d85f0d27153dc18ed2a58e018872c45883798387 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Tue, 13 Feb 2018 16:27:03 +0000 Subject: [PATCH 14/59] Add computed sandbox outcomes --- src/Data/Path/Pathy.purs | 68 +++++++++++++++++++++++----------------- test/Main.purs | 32 +++++++++---------- 2 files changed, 55 insertions(+), 45 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 0d78efb..8093ba9 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -64,6 +64,7 @@ module Data.Path.Pathy , unsandbox , unsafePrintPath , unsafePrintPath' + , class AppendOutcome ) where @@ -173,7 +174,7 @@ instance absSplitDirOrFile :: SplitDirOrFile File where dirOrFileF = Right dirOrFile :: forall a b s. SplitDirOrFile b => Path a b s -> AnyPath a s dirOrFile p = bimap (un PathFlipped) (un PathFlipped) $ dirOrFileF (PathFlipped p) - + dirOrFileName :: forall b. SplitDirOrFile b => Name b -> Either (Name Dir) (Name File) dirOrFileName = dirOrFileF @@ -206,11 +207,11 @@ posixEscaper = Escaper $ s -> s -- | Creates a path which points to a relative file of the specified name. -file :: forall s. NonEmptyString -> Path Rel File s +file :: NonEmptyString -> Path Rel File Sandboxed file f = file' (Name f) -- | Creates a path which points to a relative file of the specified name. -file' :: forall s. Name File -> Path Rel File s +file' :: Name File -> Path Rel File Sandboxed file' f = In Current f -- | Retrieves the name of a file path. @@ -232,7 +233,7 @@ extension (Name f) = -- | Drops the extension on a file name. dropExtension :: Name File -> Maybe (Name File) -dropExtension (Name n) = +dropExtension (Name n) = let s = NEString.toString n in case S.lastIndexOf (S.Pattern ".") s of @@ -258,11 +259,11 @@ _updateExt ext = case _ of Nothing -> Name ext -- | Creates a path which points to a relative directory of the specified name. -dir :: forall s. NonEmptyString -> Path Rel Dir s +dir :: NonEmptyString -> Path Rel Dir Sandboxed dir d = dir' (Name d) -- | Creates a path which points to a relative directory of the specified name. -dir' :: forall s. Name Dir -> Path Rel Dir s +dir' :: Name Dir -> Path Rel Dir Sandboxed dir' d = In Current d -- | Retrieves the name of a directory path. Not all paths have such a name, @@ -276,17 +277,23 @@ pathName :: forall b s. AnyPath b s -> Either (Maybe (Name Dir)) (Name File) pathName = bimap dirName fileName -- | Given a directory path, appends either a file or directory to the path. -appendPath :: forall a b s. Path a Dir s -> Path Rel b s -> Path a b s +appendPath :: forall a b is rs s. AppendOutcome is rs s => Path a Dir is -> Path Rel b rs -> Path a b s appendPath _ Root = unsafeCrashWith "Imposible as Root can't be Path Rel" appendPath Current Current = Current appendPath Root Current = Root -appendPath (ParentIn p) Current = ParentIn (p Current) -appendPath (In p (Name d)) Current = In (p Current) (Name d) +appendPath (ParentIn p) Current = ParentIn (p Current :: Path Rel Dir rs) +appendPath (In p (Name d)) Current = In (p Current :: Path Rel Dir rs) (Name d) appendPath p1 (ParentIn p2) = ParentIn (p1 p2) appendPath p1 (In p2 n2) = In (p1 p2) n2 infixl 6 appendPath as +class AppendOutcome (is :: SandboxedOrNot) (rs :: SandboxedOrNot) (s :: SandboxedOrNot) | is rs -> s +instance appendSSOutcome :: AppendOutcome Sandboxed Sandboxed Sandboxed +instance appendUSOutcome :: AppendOutcome Unsandboxed Sandboxed Unsandboxed +instance appendSUOutcome :: AppendOutcome Sandboxed Unsandboxed Unsandboxed +instance appendUUOutcome :: AppendOutcome Unsandboxed Unsandboxed Unsandboxed + -- | Sets the extension of the file to the specified extension. -- | -- | ```purescript @@ -314,15 +321,21 @@ infixl 6 parentAppend as <..> -- | if the last path segment is root directory, current directory, or parent -- | directory). peel + :: forall a b s + . Path a b s + -> Maybe (Tuple (Path a Dir Unsandboxed) (Name b)) +peel = unsafeCoerce unsafePeel + +unsafePeel :: forall a b s . Path a b s -> Maybe (Tuple (Path a Dir s) (Name b)) -peel Current = Nothing -peel Root = Nothing -peel p@(ParentIn _) = case canonicalize' p of - Tuple true p' -> peel p' +unsafePeel Current = Nothing +unsafePeel Root = Nothing +unsafePeel p@(ParentIn _) = case canonicalize' p of + Tuple true p' -> unsafePeel p' _ -> Nothing -peel (In p n) = Just $ Tuple p n +unsafePeel (In p n) = Just $ Tuple p n -- | Returns the depth of the path. This may be negative in some cases, e.g. -- | `./../../../` has depth `-3`. @@ -334,10 +347,7 @@ depth (In p _) = depth p + 1 -- | Unsandboxes any path (whether sandboxed or not). unsandbox :: forall a b s. Path a b s -> Path a b Unsandboxed -unsandbox Current = Current -unsandbox Root = Root -unsandbox (ParentIn p) = ParentIn (unsandbox p) -unsandbox (In p n) = In (unsandbox p) n +unsandbox = unsafeCoerce -- | Creates a path that points to the parent directory of the specified path. -- | This function always unsandboxes the path. @@ -348,11 +358,11 @@ unsafeCoerceType :: forall a b b' s. Path a b s -> Path a b' s unsafeCoerceType = unsafeCoerce -- | The "current directory", which can be used to define relatively-located resources. -currentDir :: forall s. Path Rel Dir s +currentDir :: Path Rel Dir Sandboxed currentDir = Current -- | The root directory, which can be used to define absolutely-located resources. -rootDir :: forall s. Path Abs Dir s +rootDir :: Path Abs Dir Sandboxed rootDir = Root -- | Renames a file path. @@ -360,7 +370,7 @@ renameFile :: forall a s. (Name File -> Name File) -> Path a File s -> Path a Fi renameFile f = un Identity <<< renameFile' (pure <<< f) renameFile' :: forall f a s. Applicative f => (Name File -> f (Name File)) -> Path a File s -> f (Path a File s) -renameFile' f (In p f0) = In p <$> f f0 +renameFile' f (In p f0) = In p <$> f f0 renameFile' _ p = pure p -- | Renames a directory path. Note: This is a simple rename of the terminal @@ -421,7 +431,7 @@ identicalPath p1 p2 = show p1 == show p2 -- | reference path. -- | -- | Note there are some cases this function cannot handle. -relativeTo :: forall a b s s'. SplitDirOrFile b => Path a b s -> Path a Dir s' -> Maybe (Path Rel b s') +relativeTo :: forall a b s s'. SplitDirOrFile b => AppendOutcome s' s' s' => Path a b s -> Path a Dir s' -> Maybe (Path Rel b s') relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) where relativeTo' :: forall b'. SplitDirOrFile b' => Path a b' s -> Path a Dir s' -> Maybe (Path Rel b' s') @@ -430,14 +440,14 @@ relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) relativeTo' cp1 cp2 | identicalPath cp1 cp2 = pure Current | otherwise = do - Tuple cp1Path name <- peel cp1 + Tuple cp1Path name <- unsafePeel cp1 rel <- relativeTo' cp1Path cp2 pure $ overName name - (\dirN -> rel In Current dirN) - (\fileN -> rel In Current fileN) - overName + (\dirN -> rel In (Current :: Path Rel Dir s') dirN) + (\fileN -> rel In (Current :: Path Rel Dir s') fileN) + overName :: forall n a' s'' - . SplitDirOrFile n + . SplitDirOrFile n => Name n -> (Name Dir -> Path a' Dir s'') -> (Name File -> Path a' File s'') @@ -537,9 +547,9 @@ instance showPath :: SplitDirOrFile b => Show (Path a b s) where show Root = "rootDir" show (ParentIn p) = "(parentDir " <> show p <> ")" show (In p n ) = case dirOrFileName n of - Left dirN -> + Left dirN -> "(" <> show p <> " dir " <> show dirN <> ")" - Right fileN -> + Right fileN -> "(" <> show p <> " file " <> show fileN <> ")" instance eqPath :: SplitDirOrFile b => Eq (Path a b s) where diff --git a/test/Main.purs b/test/Main.purs index ac4293c..cae1ec9 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -45,15 +45,15 @@ instance arbitraryArbPath ∷ QC.Arbitrary ArbPath where pure $ ArbPath $ rootDir foldl (flip ()) filename (dirs ∷ Array (Path Rel Dir Sandboxed)) pathPart ∷ Gen.Gen NonEmptyString -pathPart = asNonEmptyString <$> Gen.suchThat QC.arbitrary (not <<< Str.null) +pathPart = asNonEmptyString <$> Gen.suchThat QC.arbitrary (not <<< Str.null) where asNonEmptyString :: String -> NonEmptyString asNonEmptyString = unsafeCoerce -dirFoo :: forall s. Path Rel Dir s +dirFoo :: Path Rel Dir Sandboxed dirFoo = dir (reflectNonEmpty $ SProxy :: SProxy "foo") -dirBar :: forall s. Path Rel Dir s +dirBar :: Path Rel Dir Sandboxed dirBar = dir (reflectNonEmpty $ SProxy :: SProxy "bar") main :: QC.QC () Unit @@ -136,7 +136,7 @@ main = do test "renameFile - single level deep" (renameFile' dropExtension (file (reflectNonEmpty $ SProxy :: SProxy "image.png"))) - + (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image") test' "sandbox - sandbox absolute dir to one level higher" @@ -148,27 +148,27 @@ main = do test "parseRelFile - image.png" (parseRelFile "image.png") - (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") + (Just $ unsandbox $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") test "parseRelFile - ./image.png" (parseRelFile "./image.png") - (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") + (Just $ unsandbox $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") test "parseRelFile - foo/image.png" (parseRelFile "foo/image.png") - (Just $ dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) + (Just $ unsandbox $ dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) test "parseRelFile - ../foo/image.png" (parseRelFile "../foo/image.png") - (Just $ currentDir <..> dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) + (Just $ unsandbox $ currentDir <..> dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) test "parseAbsFile - /image.png" (parseAbsFile "/image.png") - (Just $ rootDir file (reflectNonEmpty $ SProxy :: SProxy "image.png")) + (Just $ unsandbox $ rootDir file (reflectNonEmpty $ SProxy :: SProxy "image.png")) test "parseAbsFile - /foo/image.png" (parseAbsFile "/foo/image.png") - (Just $ rootDir dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) + (Just $ unsandbox $ rootDir dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) test "parseRelDir - empty string" (parseRelDir "") @@ -181,27 +181,27 @@ main = do test "parseRelDir - foo/" (parseRelDir "foo/") - (Just $ dirFoo) + (Just $ unsandbox dirFoo) test "parseRelDir - foo/bar" (parseRelDir "foo/bar/") - (Just $ dirFoo dirBar) + (Just $ unsandbox $ dirFoo dirBar) test "parseRelDir - ./foo/bar" (parseRelDir "./foo/bar/") - (Just $ dirFoo dirBar) + (Just $ unsandbox $ dirFoo dirBar) test "parseAbsDir - /" (parseAbsDir "/") - (Just $ rootDir) + (Just $ unsandbox rootDir) test "parseAbsDir - /foo/" (parseAbsDir "/foo/") - (Just $ rootDir dirFoo) + (Just $ unsandbox $ rootDir dirFoo) test "parseAbsDir - /foo/bar" (parseAbsDir "/foo/bar/") - (Just $ rootDir dirFoo dirBar) + (Just $ unsandbox $ rootDir dirFoo dirBar) info "Checking typeclass laws..." Laws.Data.checkEq (Proxy :: Proxy ArbPath) From 26710de826d43651d47cf40027e977494a63000f Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 14 Feb 2018 14:23:14 +0100 Subject: [PATCH 15/59] add view functions and types --- src/Data/Path/Pathy.purs | 61 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 8093ba9..49d3491 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -65,6 +65,14 @@ module Data.Path.Pathy , unsafePrintPath , unsafePrintPath' , class AppendOutcome + , ViewRelDir(..) + , viewRelDir + , ViewAbsDir(..) + , viewAbsDir + , ViewAbsFile(..) + , viewAbsFile + , ViewRelFile(..) + , viewRelFile ) where @@ -547,10 +555,10 @@ instance showPath :: SplitDirOrFile b => Show (Path a b s) where show Root = "rootDir" show (ParentIn p) = "(parentDir " <> show p <> ")" show (In p n ) = case dirOrFileName n of - Left dirN -> - "(" <> show p <> " dir " <> show dirN <> ")" - Right fileN -> - "(" <> show p <> " file " <> show fileN <> ")" + Left dirN -> + "(" <> show p <> " dir " <> show dirN <> ")" + Right fileN -> + "(" <> show p <> " file " <> show fileN <> ")" instance eqPath :: SplitDirOrFile b => Eq (Path a b s) where eq p1 p2 = canonicalize p1 `identicalPath` canonicalize p2 @@ -574,3 +582,48 @@ instance showName :: Show (Name a) where derive instance eqName :: Eq (Name a) derive instance ordName :: Ord (Name a) + +data ViewRelDir + = ViewRelDirCurrent + | ViewRelDirIn ViewRelDir (Name Dir) + +viewRelDir :: Path Rel Dir Sandboxed -> ViewRelDir +viewRelDir = case _ of + Current -> ViewRelDirCurrent + Root -> unsafeCrashWith "Imposible, Root can't be in Rel path" + ParentIn dir -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" + In dir n -> ViewRelDirIn (viewRelDir dir) n + + +data ViewAbsDir + = ViewAbsDirRoot + | ViewAbsDirIn ViewAbsDir (Name Dir) + +viewAbsDir :: Path Abs Dir Sandboxed -> ViewAbsDir +viewAbsDir = case _ of + Current -> unsafeCrashWith "Imposible, Current can't be in Abs path" + Root -> ViewAbsDirRoot + ParentIn dir -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" + In dir n -> ViewAbsDirIn (viewAbsDir dir) n + + +data ViewAbsFile + = ViewAbsFileIn ViewAbsDir (Name File) + +viewAbsFile :: Path Abs File Sandboxed -> ViewAbsFile +viewAbsFile = case _ of + Current -> unsafeCrashWith "Imposibl, Current can't be in File path" + Root -> unsafeCrashWith "Imposible, Root can't be in File path" + ParentIn dir -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" + In dir n -> ViewAbsFileIn (viewAbsDir dir) n + + +data ViewRelFile + = ViewRelFileIn ViewRelDir (Name File) + +viewRelFile :: Path Rel File Sandboxed -> ViewRelFile +viewRelFile = case _ of + Current -> unsafeCrashWith "Imposibl, Current can't be in File path" + Root -> unsafeCrashWith "Imposible, Root can't be in File path" + ParentIn dir -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" + In dir n -> ViewRelFileIn (viewRelDir dir) n From 5f1fc17034973066b0ab75ac237b95986cab1e93 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 14 Feb 2018 15:47:01 +0100 Subject: [PATCH 16/59] fix shadowed dir --- src/Data/Path/Pathy.purs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 49d3491..1adc4db 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -591,8 +591,8 @@ viewRelDir :: Path Rel Dir Sandboxed -> ViewRelDir viewRelDir = case _ of Current -> ViewRelDirCurrent Root -> unsafeCrashWith "Imposible, Root can't be in Rel path" - ParentIn dir -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" - In dir n -> ViewRelDirIn (viewRelDir dir) n + ParentIn _ -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" + In d n -> ViewRelDirIn (viewRelDir d) n data ViewAbsDir @@ -603,8 +603,8 @@ viewAbsDir :: Path Abs Dir Sandboxed -> ViewAbsDir viewAbsDir = case _ of Current -> unsafeCrashWith "Imposible, Current can't be in Abs path" Root -> ViewAbsDirRoot - ParentIn dir -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" - In dir n -> ViewAbsDirIn (viewAbsDir dir) n + ParentIn _ -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" + In d n -> ViewAbsDirIn (viewAbsDir d) n data ViewAbsFile @@ -614,8 +614,8 @@ viewAbsFile :: Path Abs File Sandboxed -> ViewAbsFile viewAbsFile = case _ of Current -> unsafeCrashWith "Imposibl, Current can't be in File path" Root -> unsafeCrashWith "Imposible, Root can't be in File path" - ParentIn dir -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" - In dir n -> ViewAbsFileIn (viewAbsDir dir) n + ParentIn _ -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" + In d n -> ViewAbsFileIn (viewAbsDir d) n data ViewRelFile @@ -625,5 +625,5 @@ viewRelFile :: Path Rel File Sandboxed -> ViewRelFile viewRelFile = case _ of Current -> unsafeCrashWith "Imposibl, Current can't be in File path" Root -> unsafeCrashWith "Imposible, Root can't be in File path" - ParentIn dir -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" - In dir n -> ViewRelFileIn (viewRelDir dir) n + ParentIn _ -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" + In d n -> ViewRelFileIn (viewRelDir d) n From 6dad268291d8f547d69e89dc39432289ca59c525 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Thu, 15 Feb 2018 12:29:18 +0100 Subject: [PATCH 17/59] add relativify and absolutify --- src/Data/Path/Pathy.purs | 61 +++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 1adc4db..2b0eeb8 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -73,6 +73,8 @@ module Data.Path.Pathy , viewAbsFile , ViewRelFile(..) , viewRelFile + , relativify + , absolutify ) where @@ -119,6 +121,7 @@ foreign import data Sandboxed :: SandboxedOrNot -- | A newtype around a file name. newtype Name (n :: DirOrFile) = Name NonEmptyString +derive instance newtypeName :: Newtype (Name n) _ -- | Unwraps the `Name` newtype. runName :: forall a. Name a -> String @@ -216,11 +219,11 @@ posixEscaper = Escaper $ -- | Creates a path which points to a relative file of the specified name. file :: NonEmptyString -> Path Rel File Sandboxed -file f = file' (Name f) +file = file' <<< Name -- | Creates a path which points to a relative file of the specified name. file' :: Name File -> Path Rel File Sandboxed -file' f = In Current f +file' = In Current -- | Retrieves the name of a file path. fileName :: forall a s. Path a File s -> Name File @@ -268,11 +271,11 @@ _updateExt ext = case _ of -- | Creates a path which points to a relative directory of the specified name. dir :: NonEmptyString -> Path Rel Dir Sandboxed -dir d = dir' (Name d) +dir = dir' <<< Name -- | Creates a path which points to a relative directory of the specified name. dir' :: Name Dir -> Path Rel Dir Sandboxed -dir' d = In Current d +dir' = In Current -- | Retrieves the name of a directory path. Not all paths have such a name, -- | for example, the root or current directory. @@ -434,6 +437,37 @@ printPath' = unsafePrintPath' identicalPath :: forall a a' b b' s s'. SplitDirOrFile b => SplitDirOrFile b' => Path a b s -> Path a' b' s' -> Boolean identicalPath p1 p2 = show p1 == show p2 +relativify :: forall a. SplitDirOrFile a => Path Abs a Sandboxed -> Path Rel a Sandboxed +relativify p = case dirOrFile p of + Left d -> + joinSplit $ asRel $ viewAbsDir d + Right f -> + let (ViewAbsFileIn d name) = viewAbsFile f + in joinSplit $ asRel d file' name + where + joinSplit :: forall x. Path Rel x Sandboxed -> Path Rel a Sandboxed + joinSplit = unsafeCoerce + asRel :: ViewAbsDir -> Path Rel Dir Sandboxed + asRel = case _ of + ViewAbsDirRoot -> currentDir + ViewAbsDirIn d n -> asRel d dir' n + +absolutify :: forall a. SplitDirOrFile a => Path Rel a Sandboxed -> Path Abs a Sandboxed +absolutify p = case dirOrFile p of + Left d -> + joinSplit $ asAbs $ viewRelDir d + Right f -> + let (ViewRelFileIn d name) = viewRelFile f + in joinSplit $ asAbs d file' name + where + joinSplit :: forall x. Path Abs x Sandboxed -> Path Abs a Sandboxed + joinSplit = unsafeCoerce + asAbs :: ViewRelDir -> Path Abs Dir Sandboxed + asAbs = case _ of + ViewRelDirCurrent -> rootDir + ViewRelDirIn d n -> asAbs d dir' n + + -- | Makes one path relative to another reference path, if possible, otherwise -- | returns `Nothing`. The returned path inherits the sandbox settings of the -- | reference path. @@ -450,19 +484,12 @@ relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) | otherwise = do Tuple cp1Path name <- unsafePeel cp1 rel <- relativeTo' cp1Path cp2 - pure $ overName name - (\dirN -> rel In (Current :: Path Rel Dir s') dirN) - (\fileN -> rel In (Current :: Path Rel Dir s') fileN) - overName - :: forall n a' s'' - . SplitDirOrFile n - => Name n - -> (Name Dir -> Path a' Dir s'') - -> (Name File -> Path a' File s'') - -> Path a' n s'' - overName p onDir onFile = case dirOrFileName p of - Left p' -> unsafeCoerce $ onDir p' - Right p' -> unsafeCoerce $ onFile p' + pure case dirOrFileName name of + Left dirN -> joinSplit $ rel In (Current :: Path Rel Dir s') dirN + Right fileN -> joinSplit $ rel In (Current :: Path Rel Dir s') fileN + where + joinSplit :: forall a_ b_ s_. Path a_ b_ s_ -> Path a_ b' s_ + joinSplit = unsafeCoerce -- | Attempts to sandbox a path relative to some directory. If successful, the sandboxed -- | directory will be returned relative to the sandbox directory (although this can easily From 166bb20a952b1d28339ce5a3b138f97292c5d42d Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 16 Feb 2018 13:03:02 +0100 Subject: [PATCH 18/59] use one node for Current and Root --- src/Data/Path/Pathy.purs | 133 ++++++++++++++++++++------------------- test/Main.purs | 8 ++- 2 files changed, 75 insertions(+), 66 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 2b0eeb8..d26cbf3 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -146,8 +146,7 @@ runName (Name name) = NEString.toString name -- | but such paths are marked as unsandboxed, and may not be rendered to strings -- | until they are first sandboxed to some directory. data Path (a :: RelOrAbs) (b :: DirOrFile) (s :: SandboxedOrNot) - = Current - | Root + = Init | ParentIn (Path a Dir s) | In (Path a Dir s) (Name b) @@ -223,7 +222,7 @@ file = file' <<< Name -- | Creates a path which points to a relative file of the specified name. file' :: Name File -> Path Rel File Sandboxed -file' = In Current +file' = In Init -- | Retrieves the name of a file path. fileName :: forall a s. Path a File s -> Name File @@ -275,7 +274,7 @@ dir = dir' <<< Name -- | Creates a path which points to a relative directory of the specified name. dir' :: Name Dir -> Path Rel Dir Sandboxed -dir' = In Current +dir' = In Init -- | Retrieves the name of a directory path. Not all paths have such a name, -- | for example, the root or current directory. @@ -289,11 +288,9 @@ pathName = bimap dirName fileName -- | Given a directory path, appends either a file or directory to the path. appendPath :: forall a b is rs s. AppendOutcome is rs s => Path a Dir is -> Path Rel b rs -> Path a b s -appendPath _ Root = unsafeCrashWith "Imposible as Root can't be Path Rel" -appendPath Current Current = Current -appendPath Root Current = Root -appendPath (ParentIn p) Current = ParentIn (p Current :: Path Rel Dir rs) -appendPath (In p (Name d)) Current = In (p Current :: Path Rel Dir rs) (Name d) +appendPath Init Init = (Init :: Path a b s) +appendPath (ParentIn p) Init = ParentIn (p Init :: Path Rel Dir rs) +appendPath (In p (Name d)) Init = In (p Init :: Path Rel Dir rs) (Name d) appendPath p1 (ParentIn p2) = ParentIn (p1 p2) appendPath p1 (In p2 n2) = In (p1 p2) n2 @@ -341,8 +338,7 @@ unsafePeel :: forall a b s . Path a b s -> Maybe (Tuple (Path a Dir s) (Name b)) -unsafePeel Current = Nothing -unsafePeel Root = Nothing +unsafePeel Init = Nothing unsafePeel p@(ParentIn _) = case canonicalize' p of Tuple true p' -> unsafePeel p' _ -> Nothing @@ -351,8 +347,7 @@ unsafePeel (In p n) = Just $ Tuple p n -- | Returns the depth of the path. This may be negative in some cases, e.g. -- | `./../../../` has depth `-3`. depth :: forall a b s. Path a b s -> Int -depth Current = 0 -depth Root = 0 +depth Init = 0 depth (ParentIn p) = depth p - 1 depth (In p _) = depth p + 1 @@ -370,11 +365,11 @@ unsafeCoerceType = unsafeCoerce -- | The "current directory", which can be used to define relatively-located resources. currentDir :: Path Rel Dir Sandboxed -currentDir = Current +currentDir = Init -- | The root directory, which can be used to define absolutely-located resources. rootDir :: Path Abs Dir Sandboxed -rootDir = Root +rootDir = Init -- | Renames a file path. renameFile :: forall a s. (Name File -> Name File) -> Path a File s -> Path a File s @@ -396,8 +391,7 @@ canonicalize = snd <<< canonicalize' -- | Canonicalizes a path and returns information on whether or not it actually changed. canonicalize' :: forall a b s. Path a b s -> Tuple Boolean (Path a b s) -canonicalize' Current = Tuple false Current -canonicalize' Root = Tuple false Root +canonicalize' Init = Tuple false Init canonicalize' (ParentIn (In p f)) = Tuple true (unsafeCoerceType $ snd $ canonicalize' p) canonicalize' (ParentIn p) = case canonicalize' p of Tuple changed p' -> @@ -405,36 +399,59 @@ canonicalize' (ParentIn p) = case canonicalize' p of in if changed then canonicalize' p'' else Tuple changed p'' canonicalize' (In p f) = flip In f <$> canonicalize' p -unsafePrintPath' :: forall a b s. SplitDirOrFile b => Escaper -> Path a b s -> String +unsafePrintPath' :: forall a b s. SplitRelOrAbs a => SplitDirOrFile b => Escaper -> Path a b s -> String unsafePrintPath' r = go where - go :: forall a' b' s'. SplitDirOrFile b' => Path a' b' s' -> String - go Current = "./" - go Root = "/" + go :: forall a' b' s'. SplitRelOrAbs a' => SplitDirOrFile b' => Path a' b' s' -> String + go p@Init = case relOrAbs p of + Left _ -> "./" + Right _ -> "/" go (ParentIn p) = go p <> "../" go (In p n) = case dirOrFileName n of Left dirN -> go p <> escape (runName dirN) <> "/" Right fileN -> go p <> escape (runName fileN) escape = runEscaper r -unsafePrintPath :: forall a b s. SplitDirOrFile b => Path a b s -> String +unsafePrintPath + :: forall a b s + . SplitRelOrAbs a + => SplitDirOrFile b + => Path a b s + -> String unsafePrintPath = unsafePrintPath' posixEscaper -- | Prints a `Path` into its canonical `String` representation. For security -- | reasons, the path must be sandboxed before it can be rendered to a string. -printPath :: forall a b. SplitDirOrFile b => Path a b Sandboxed -> String +printPath + :: forall a b + . SplitRelOrAbs a + => SplitDirOrFile b + => Path a b Sandboxed + -> String printPath = unsafePrintPath -- | Prints a `Path` into its canonical `String` representation, using the -- | specified escaper to escape special characters in path segments. For -- | security reasons, the path must be sandboxed before rendering to string. -printPath' :: forall a b. SplitDirOrFile b => Escaper -> Path a b Sandboxed -> String +printPath' + :: forall a b + . SplitRelOrAbs a + => SplitDirOrFile b + => Escaper + -> Path a b Sandboxed + -> String printPath' = unsafePrintPath' -- | Determines if two paths have the exact same representation. Note that -- | two paths may represent the same path even if they have different -- | representations! -identicalPath :: forall a a' b b' s s'. SplitDirOrFile b => SplitDirOrFile b' => Path a b s -> Path a' b' s' -> Boolean +identicalPath + :: forall a a' b b' s s' + . SplitRelOrAbs a + => SplitRelOrAbs a' + => SplitDirOrFile b + => SplitDirOrFile b' + => Path a b s -> Path a' b' s' -> Boolean identicalPath p1 p2 = show p1 == show p2 relativify :: forall a. SplitDirOrFile a => Path Abs a Sandboxed -> Path Rel a Sandboxed @@ -473,20 +490,19 @@ absolutify p = case dirOrFile p of -- | reference path. -- | -- | Note there are some cases this function cannot handle. -relativeTo :: forall a b s s'. SplitDirOrFile b => AppendOutcome s' s' s' => Path a b s -> Path a Dir s' -> Maybe (Path Rel b s') +relativeTo :: forall a b s s'. SplitRelOrAbs a => SplitDirOrFile b => AppendOutcome s' s' s' => Path a b s -> Path a Dir s' -> Maybe (Path Rel b s') relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) where relativeTo' :: forall b'. SplitDirOrFile b' => Path a b' s -> Path a Dir s' -> Maybe (Path Rel b' s') - relativeTo' Root Root = pure Current - relativeTo' Current Current = pure Current + relativeTo' Init Init = pure (Init :: Path Rel b' s') relativeTo' cp1 cp2 - | identicalPath cp1 cp2 = pure Current + | identicalPath cp1 cp2 = pure (Init :: Path Rel b' s') | otherwise = do Tuple cp1Path name <- unsafePeel cp1 rel <- relativeTo' cp1Path cp2 pure case dirOrFileName name of - Left dirN -> joinSplit $ rel In (Current :: Path Rel Dir s') dirN - Right fileN -> joinSplit $ rel In (Current :: Path Rel Dir s') fileN + Left dirN -> joinSplit $ rel In (Init :: Path Rel Dir s') dirN + Right fileN -> joinSplit $ rel In (Init :: Path Rel Dir s') fileN where joinSplit :: forall a_ b_ s_. Path a_ b_ s_ -> Path a_ b' s_ joinSplit = unsafeCoerce @@ -497,7 +513,7 @@ relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) -- | -- | This combinator can be used to ensure that paths which originate from user-code -- | cannot access data outside a given directory. -sandbox :: forall a b s. SplitDirOrFile b => Path a Dir Sandboxed -> Path a b s -> Maybe (Path Rel b Sandboxed) +sandbox :: forall a b s. SplitRelOrAbs a => SplitDirOrFile b => Path a Dir Sandboxed -> Path a b s -> Maybe (Path Rel b Sandboxed) sandbox p1 p2 = p2 `relativeTo` p1 -- | Refines path segments but does not change anything else. @@ -505,8 +521,7 @@ refine :: forall a b s. SplitDirOrFile b => (Name File -> Name File) -> (Name Di refine f d = go where go :: forall a' b' s'. SplitDirOrFile b' => Path a' b' s' -> Path a' b' s' - go Current = Current - go Root = Root + go Init = Init go (ParentIn p) = ParentIn (go p) go (In p name) = case dirOrFileName name of Left dirN -> @@ -531,7 +546,7 @@ parsePath -> String -> z parsePath rd ad rf af err "" = err unit -parsePath rd ad rf af err "/" = ad Root +parsePath rd ad rf af err "/" = ad Init parsePath rd ad rf af err p = let isAbs = S.take 1 p == "/" @@ -556,10 +571,10 @@ parsePath rd ad rf af err p = case traverse NEString.fromString segsDropped of Nothing -> err unit Just segs -> case isAbs, isFile of - true, true -> af $ foldlWithIndex folder Root segs - true, false -> ad $ foldlWithIndex folder Root segs - false, true -> rf $ foldlWithIndex folder Current segs - false, false -> rd $ foldlWithIndex folder Current segs + true, true -> af $ foldlWithIndex folder Init segs + true, false -> ad $ foldlWithIndex folder Init segs + false, true -> rf $ foldlWithIndex folder Init segs + false, false -> rd $ foldlWithIndex folder Init segs -- | Attempts to parse a relative file from a string. parseRelFile :: String -> Maybe (RelFile Unsandboxed) @@ -577,28 +592,24 @@ parseRelDir = parsePath Just (const Nothing) (const Nothing) (const Nothing) (co parseAbsDir :: String -> Maybe (AbsDir Unsandboxed) parseAbsDir = parsePath (const Nothing) Just (const Nothing) (const Nothing) (const Nothing) -instance showPath :: SplitDirOrFile b => Show (Path a b s) where - show Current = "currentDir" - show Root = "rootDir" +instance showPathRelDir :: (SplitRelOrAbs a, SplitDirOrFile b) => Show (Path a b s) where + show p@Init = case relOrAbs p of + Left _ -> "currentDir" + Right _ -> "rootDir" show (ParentIn p) = "(parentDir " <> show p <> ")" - show (In p n ) = case dirOrFileName n of - Left dirN -> - "(" <> show p <> " dir " <> show dirN <> ")" - Right fileN -> - "(" <> show p <> " file " <> show fileN <> ")" + show (In p n ) = "(" <> show p <> " " <> case dirOrFileName n of + Left d -> "dir " <> show n <> ")" + Right f -> "file " <> show f <> ")" -instance eqPath :: SplitDirOrFile b => Eq (Path a b s) where +instance eqPath :: (SplitRelOrAbs a, SplitDirOrFile b) => Eq (Path a b s) where eq p1 p2 = canonicalize p1 `identicalPath` canonicalize p2 -instance ordPath :: SplitDirOrFile b => Ord (Path a b s) where +instance ordPath :: (SplitRelOrAbs a, SplitDirOrFile b) => Ord (Path a b s) where compare p1 p2 = go (canonicalize p1) (canonicalize p2) where - go Current Current = EQ - go Current _ = LT - go _ Current = GT - go Root Root = EQ - go Root _ = LT - go _ Root = GT + go Init Init = EQ + go Init _ = LT + go _ Init = GT go (ParentIn p1') (ParentIn p2') = compare p1' p2' go (ParentIn _) _ = LT go _ (ParentIn _) = GT @@ -616,8 +627,7 @@ data ViewRelDir viewRelDir :: Path Rel Dir Sandboxed -> ViewRelDir viewRelDir = case _ of - Current -> ViewRelDirCurrent - Root -> unsafeCrashWith "Imposible, Root can't be in Rel path" + Init -> ViewRelDirCurrent ParentIn _ -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" In d n -> ViewRelDirIn (viewRelDir d) n @@ -628,8 +638,7 @@ data ViewAbsDir viewAbsDir :: Path Abs Dir Sandboxed -> ViewAbsDir viewAbsDir = case _ of - Current -> unsafeCrashWith "Imposible, Current can't be in Abs path" - Root -> ViewAbsDirRoot + Init -> ViewAbsDirRoot ParentIn _ -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" In d n -> ViewAbsDirIn (viewAbsDir d) n @@ -639,8 +648,7 @@ data ViewAbsFile viewAbsFile :: Path Abs File Sandboxed -> ViewAbsFile viewAbsFile = case _ of - Current -> unsafeCrashWith "Imposibl, Current can't be in File path" - Root -> unsafeCrashWith "Imposible, Root can't be in File path" + Init -> unsafeCrashWith "Imposibl, Init can't be in File path" ParentIn _ -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" In d n -> ViewAbsFileIn (viewAbsDir d) n @@ -650,7 +658,6 @@ data ViewRelFile viewRelFile :: Path Rel File Sandboxed -> ViewRelFile viewRelFile = case _ of - Current -> unsafeCrashWith "Imposibl, Current can't be in File path" - Root -> unsafeCrashWith "Imposible, Root can't be in File path" + Init -> unsafeCrashWith "Imposibl, Init can't be in File path" ParentIn _ -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" In d n -> ViewRelFileIn (viewRelDir d) n diff --git a/test/Main.purs b/test/Main.purs index 80c253e..42b614b 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -25,9 +25,11 @@ import Unsafe.Coerce (unsafeCoerce) test :: forall a eff. Show a => Eq a => String -> a -> a -> Eff (console :: CONSOLE | eff) Unit test name actual expected= do infoShow $ "Test: " <> name - if expected == actual then infoShow $ "Passed: " <> (show expected) else infoShow $ "Failed: Expected " <> (show expected) <> " but found " <> (show actual) + if expected == actual + then infoShow $ "Passed: " <> (show expected) + else infoShow $ "Failed: Expected " <> (show expected) <> " but found " <> (show actual) -test' :: forall a b s eff. SplitDirOrFile b => String -> Path a b s -> String -> Eff (console :: CONSOLE | eff) Unit +test' :: forall a b s eff. SplitRelOrAbs a => SplitDirOrFile b => String -> Path a b s -> String -> Eff (console :: CONSOLE | eff) Unit test' n p s = test n (unsafePrintPath p) s newtype ArbPath = ArbPath (Path Abs File Sandboxed) @@ -57,7 +59,7 @@ dirFoo = dir (reflectNonEmpty $ SProxy :: SProxy "foo") dirBar :: Path Rel Dir Sandboxed dirBar = dir (reflectNonEmpty $ SProxy :: SProxy "bar") -parsePrintCheck :: forall a b. SplitDirOrFile b => Path a b Sandboxed -> Maybe (Path a b Unsandboxed) -> QC.Result +parsePrintCheck :: forall a b. SplitRelOrAbs a => SplitDirOrFile b => Path a b Sandboxed -> Maybe (Path a b Unsandboxed) -> QC.Result parsePrintCheck input parsed = if parsed == Just (unsandbox input) then QC.Success From 6e69d237b6801804ce126fffadbd6679b5e66a1c Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 16 Feb 2018 13:21:12 +0100 Subject: [PATCH 19/59] use Tuple and List for View types --- src/Data/Path/Pathy.purs | 81 ++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 48 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index d26cbf3..bdd6abe 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -65,13 +65,11 @@ module Data.Path.Pathy , unsafePrintPath , unsafePrintPath' , class AppendOutcome - , ViewRelDir(..) + , DirPathView + , FilePathView , viewRelDir - , ViewAbsDir(..) , viewAbsDir - , ViewAbsFile(..) , viewAbsFile - , ViewRelFile(..) , viewRelFile , relativify , absolutify @@ -85,12 +83,13 @@ import Data.Bifunctor (bimap) import Data.Either (Either(..)) import Data.FoldableWithIndex (foldlWithIndex) import Data.Identity (Identity(..)) +import Data.List (List(..), reverse) import Data.Maybe (Maybe(..)) import Data.Newtype (class Newtype, un) import Data.String as S import Data.String.NonEmpty (NonEmptyString, appendString) import Data.String.NonEmpty (fromString, toString) as NEString -import Data.Traversable (traverse) +import Data.Traversable (foldl, traverse) import Data.Tuple (Tuple(..), snd) import Partial.Unsafe (unsafeCrashWith) import Unsafe.Coerce (unsafeCoerce) @@ -459,30 +458,26 @@ relativify p = case dirOrFile p of Left d -> joinSplit $ asRel $ viewAbsDir d Right f -> - let (ViewAbsFileIn d name) = viewAbsFile f + let (Tuple d name) = viewAbsFile f in joinSplit $ asRel d file' name where joinSplit :: forall x. Path Rel x Sandboxed -> Path Rel a Sandboxed joinSplit = unsafeCoerce - asRel :: ViewAbsDir -> Path Rel Dir Sandboxed - asRel = case _ of - ViewAbsDirRoot -> currentDir - ViewAbsDirIn d n -> asRel d dir' n + asRel :: DirPathView -> Path Rel Dir Sandboxed + asRel = foldl (\p n -> p dir' n) currentDir absolutify :: forall a. SplitDirOrFile a => Path Rel a Sandboxed -> Path Abs a Sandboxed absolutify p = case dirOrFile p of Left d -> joinSplit $ asAbs $ viewRelDir d Right f -> - let (ViewRelFileIn d name) = viewRelFile f + let (Tuple d name) = viewRelFile f in joinSplit $ asAbs d file' name where joinSplit :: forall x. Path Abs x Sandboxed -> Path Abs a Sandboxed joinSplit = unsafeCoerce - asAbs :: ViewRelDir -> Path Abs Dir Sandboxed - asAbs = case _ of - ViewRelDirCurrent -> rootDir - ViewRelDirIn d n -> asAbs d dir' n + asAbs :: DirPathView -> Path Abs Dir Sandboxed + asAbs = foldl (\p n -> p dir' n) rootDir -- | Makes one path relative to another reference path, if possible, otherwise @@ -621,43 +616,33 @@ instance showName :: Show (Name a) where derive instance eqName :: Eq (Name a) derive instance ordName :: Ord (Name a) -data ViewRelDir - = ViewRelDirCurrent - | ViewRelDirIn ViewRelDir (Name Dir) - -viewRelDir :: Path Rel Dir Sandboxed -> ViewRelDir -viewRelDir = case _ of - Init -> ViewRelDirCurrent - ParentIn _ -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" - In d n -> ViewRelDirIn (viewRelDir d) n - - -data ViewAbsDir - = ViewAbsDirRoot - | ViewAbsDirIn ViewAbsDir (Name Dir) - -viewAbsDir :: Path Abs Dir Sandboxed -> ViewAbsDir -viewAbsDir = case _ of - Init -> ViewAbsDirRoot - ParentIn _ -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" - In d n -> ViewAbsDirIn (viewAbsDir d) n +type DirPathView = List (Name Dir) +type FilePathView = Tuple DirPathView (Name File) +viewRelDir :: Path Rel Dir Sandboxed -> DirPathView +viewRelDir = reverse <<< go + where + go = case _ of + Init -> Nil + ParentIn _ -> unsafeCrashWith "Impossible, ParentIn can't be in Sandboxed path" + In d n -> Cons n (go d) -data ViewAbsFile - = ViewAbsFileIn ViewAbsDir (Name File) +viewAbsDir :: Path Abs Dir Sandboxed -> DirPathView +viewAbsDir = reverse <<< go + where + go = case _ of + Init -> Nil + ParentIn _ -> unsafeCrashWith "Impossible, ParentIn can't be in Sandboxed path" + In d n -> Cons n (go d) -viewAbsFile :: Path Abs File Sandboxed -> ViewAbsFile +viewAbsFile :: Path Abs File Sandboxed -> FilePathView viewAbsFile = case _ of - Init -> unsafeCrashWith "Imposibl, Init can't be in File path" - ParentIn _ -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" - In d n -> ViewAbsFileIn (viewAbsDir d) n - - -data ViewRelFile - = ViewRelFileIn ViewRelDir (Name File) + Init -> unsafeCrashWith "Impossible, Init can't be in File path" + ParentIn _ -> unsafeCrashWith "Impossible, ParentIn can't be in Sandboxed path" + In d n -> Tuple (viewAbsDir d) n -viewRelFile :: Path Rel File Sandboxed -> ViewRelFile +viewRelFile :: Path Rel File Sandboxed -> FilePathView viewRelFile = case _ of Init -> unsafeCrashWith "Imposibl, Init can't be in File path" - ParentIn _ -> unsafeCrashWith "Imposible, ParentIn can't be in Sandboxed path" - In d n -> ViewRelFileIn (viewRelDir d) n + ParentIn _ -> unsafeCrashWith "Impossible, ParentIn can't be in Sandboxed path" + In d n -> Tuple (viewRelDir d) n From 40a3cb4c08917f603d7c9071fa24f7438a11bd03 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 16 Feb 2018 14:28:34 +0100 Subject: [PATCH 20/59] simplify relativify and absolutify --- src/Data/Path/Pathy.purs | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index bdd6abe..7e2310b 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -453,31 +453,11 @@ identicalPath => Path a b s -> Path a' b' s' -> Boolean identicalPath p1 p2 = show p1 == show p2 -relativify :: forall a. SplitDirOrFile a => Path Abs a Sandboxed -> Path Rel a Sandboxed -relativify p = case dirOrFile p of - Left d -> - joinSplit $ asRel $ viewAbsDir d - Right f -> - let (Tuple d name) = viewAbsFile f - in joinSplit $ asRel d file' name - where - joinSplit :: forall x. Path Rel x Sandboxed -> Path Rel a Sandboxed - joinSplit = unsafeCoerce - asRel :: DirPathView -> Path Rel Dir Sandboxed - asRel = foldl (\p n -> p dir' n) currentDir - -absolutify :: forall a. SplitDirOrFile a => Path Rel a Sandboxed -> Path Abs a Sandboxed -absolutify p = case dirOrFile p of - Left d -> - joinSplit $ asAbs $ viewRelDir d - Right f -> - let (Tuple d name) = viewRelFile f - in joinSplit $ asAbs d file' name - where - joinSplit :: forall x. Path Abs x Sandboxed -> Path Abs a Sandboxed - joinSplit = unsafeCoerce - asAbs :: DirPathView -> Path Abs Dir Sandboxed - asAbs = foldl (\p n -> p dir' n) rootDir +relativify :: forall a b. Path Abs a b -> Path Rel a b +relativify = unsafeCoerce + +absolutify :: forall a b. Path Rel a b -> Path Abs a b +absolutify = unsafeCoerce -- | Makes one path relative to another reference path, if possible, otherwise From 0c58cf31cdec7b1df4b51462203811747f4b9ff2 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 16 Feb 2018 14:29:20 +0100 Subject: [PATCH 21/59] refactor view*; add view*Unsandboxed --- src/Data/Path/Pathy.purs | 46 ++++++++++++++++++++++------------------ test/Main.purs | 22 ++++++++++++++++++- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 7e2310b..b870547 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -67,10 +67,11 @@ module Data.Path.Pathy , class AppendOutcome , DirPathView , FilePathView - , viewRelDir - , viewAbsDir - , viewAbsFile - , viewRelFile + , viewDir + , viewFile + , viewDirUnsandboxed + , viewFileUnsandboxed + , peelFile , relativify , absolutify ) @@ -79,7 +80,7 @@ module Data.Path.Pathy import Prelude import Data.Array (drop, dropEnd, filter, length) -import Data.Bifunctor (bimap) +import Data.Bifunctor (bimap, lmap) import Data.Either (Either(..)) import Data.FoldableWithIndex (foldlWithIndex) import Data.Identity (Identity(..)) @@ -599,30 +600,33 @@ derive instance ordName :: Ord (Name a) type DirPathView = List (Name Dir) type FilePathView = Tuple DirPathView (Name File) -viewRelDir :: Path Rel Dir Sandboxed -> DirPathView -viewRelDir = reverse <<< go +viewDir :: forall a. Path a Dir Sandboxed -> DirPathView +viewDir = reverse <<< go where go = case _ of Init -> Nil ParentIn _ -> unsafeCrashWith "Impossible, ParentIn can't be in Sandboxed path" In d n -> Cons n (go d) -viewAbsDir :: Path Abs Dir Sandboxed -> DirPathView -viewAbsDir = reverse <<< go +viewFile :: forall a. Path a File Sandboxed -> FilePathView +viewFile = peelFile >>> lmap viewDir + +type DirPathViewUnsandboxed = List (Maybe (Name Dir)) +type FilePathViewUnsandboxed = Tuple DirPathViewUnsandboxed (Name File) + +viewDirUnsandboxed :: forall a. Path a Dir Unsandboxed -> DirPathViewUnsandboxed +viewDirUnsandboxed = reverse <<< go where go = case _ of Init -> Nil - ParentIn _ -> unsafeCrashWith "Impossible, ParentIn can't be in Sandboxed path" - In d n -> Cons n (go d) + ParentIn p -> Cons Nothing (go p) + In d n -> Cons (Just n) (go d) + +viewFileUnsandboxed :: forall a. Path a File Unsandboxed -> FilePathViewUnsandboxed +viewFileUnsandboxed = peelFile >>> lmap viewDirUnsandboxed -viewAbsFile :: Path Abs File Sandboxed -> FilePathView -viewAbsFile = case _ of +peelFile :: forall a s. Path a File s -> Tuple (Path a Dir s) (Name File) +peelFile = case _ of Init -> unsafeCrashWith "Impossible, Init can't be in File path" - ParentIn _ -> unsafeCrashWith "Impossible, ParentIn can't be in Sandboxed path" - In d n -> Tuple (viewAbsDir d) n - -viewRelFile :: Path Rel File Sandboxed -> FilePathView -viewRelFile = case _ of - Init -> unsafeCrashWith "Imposibl, Init can't be in File path" - ParentIn _ -> unsafeCrashWith "Impossible, ParentIn can't be in Sandboxed path" - In d n -> Tuple (viewRelDir d) n + ParentIn _ -> unsafeCrashWith "Impossible, ParentIn can't be in File path" + In d n -> Tuple d n diff --git a/test/Main.purs b/test/Main.purs index 42b614b..dd72902 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -86,6 +86,26 @@ parsePrintRelFilePath = PG.genRelFilePath <#> \path -> main :: QC.QC () Unit main = do + -- let uppp = (parentDir currentDir) + -- info $ unsafePrintPath uppp + -- let doown = currentDir dirFoo dirFoo dirFoo dirFoo + -- info $ unsafePrintPath doown + -- let up = sandbox doown uppp + -- info $ maybe "oops" unsafePrintPath up + -- let pathA = currentDir dirFoo dirFoo + -- let pathB = currentDir dirFoo dirFoo dirFoo + -- let x = unsafePartial $ fromJust $ sandbox pathA pathB + -- info $ unsafePrintPath x + -- info $ maybe "" (show <<< bimap unsafePrintPath runName) (peel x) + -- info "========" + -- let appRoot = currentDir dirFoo dirFoo + -- let userData = appRoot currentDir + -- info $ maybe "" (show <<< bimap unsafePrintPath runName) (peel userData) + -- info "========" + -- let x = unsafePartial $ fromJust $ sandbox pathB pathA + -- info $ unsafePrintPath x + -- info $ maybe "" (show <<< bimap unsafePrintPath runName) (peel x) + info "checking `parse <<< print` for `AbsDir`" *> QC.quickCheck parsePrintAbsDirPath info "checking `parse <<< print` for `AbsFile`" *> QC.quickCheck parsePrintAbsFilePath info "checking `parse <<< print` for `RelDir`" *> QC.quickCheck parsePrintRelDirPath @@ -234,7 +254,7 @@ main = do test "parseAbsDir - /foo/bar" (parseAbsDir "/foo/bar/") (Just $ unsandbox $ rootDir dirFoo dirBar) - + info "Checking typeclass laws..." Laws.Data.checkEq (Proxy :: Proxy ArbPath) Laws.Data.checkOrd (Proxy :: Proxy ArbPath) From 734c8c379178f978faba7c1fad4ad3653ae159b4 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 16 Feb 2018 16:59:08 +0100 Subject: [PATCH 22/59] fix warning --- src/Data/Path/Pathy.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index b870547..9d1c868 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -90,7 +90,7 @@ import Data.Newtype (class Newtype, un) import Data.String as S import Data.String.NonEmpty (NonEmptyString, appendString) import Data.String.NonEmpty (fromString, toString) as NEString -import Data.Traversable (foldl, traverse) +import Data.Traversable (traverse) import Data.Tuple (Tuple(..), snd) import Partial.Unsafe (unsafeCrashWith) import Unsafe.Coerce (unsafeCoerce) From a95bcfb21c4bbb79c4b57c8c4e477059526f9623 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Mon, 19 Feb 2018 12:09:35 +0000 Subject: [PATCH 23/59] Remove sandboxing --- src/Data/Path/Pathy.purs | 348 ++++++++++------------------------- src/Data/Path/Pathy/Gen.purs | 14 +- 2 files changed, 104 insertions(+), 258 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 9d1c868..a3e96bc 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -6,7 +6,6 @@ module Data.Path.Pathy , AbsPath , Dir , Name(..) - , Escaper(..) , File , Path , AnyPath @@ -14,8 +13,6 @@ module Data.Path.Pathy , RelDir , RelFile , RelPath - , Sandboxed - , Unsandboxed , appendPath , () , setExtension @@ -39,47 +36,32 @@ module Data.Path.Pathy , identicalPath , parentDir , peel - , posixEscaper , parsePath , parseAbsDir , parseAbsFile , parseRelDir , parseRelFile - , printPath - , printPath' - , class SplitRelOrAbs + , class IsRelOrAbs , relOrAbs - , class SplitDirOrFile - , dirOrFileF + , class IsDirOrFile , dirOrFile - , dirOrFileName , refine , relativeTo , renameDir , renameFile , renameFile' , rootDir - , runEscaper - , sandbox - , unsandbox - , unsafePrintPath - , unsafePrintPath' - , class AppendOutcome , DirPathView , FilePathView , viewDir , viewFile - , viewDirUnsandboxed - , viewFileUnsandboxed , peelFile - , relativify - , absolutify ) where import Prelude -import Data.Array (drop, dropEnd, filter, length) +import Data.Array (drop, dropEnd, length) import Data.Bifunctor (bimap, lmap) import Data.Either (Either(..)) import Data.FoldableWithIndex (foldlWithIndex) @@ -99,8 +81,6 @@ foreign import kind RelOrAbs foreign import kind DirOrFile -foreign import kind SandboxedOrNot - -- | The (phantom) type of relative paths. foreign import data Rel :: RelOrAbs @@ -113,12 +93,6 @@ foreign import data File :: DirOrFile -- | The (phantom) type of directories. foreign import data Dir :: DirOrFile --- | The (phantom) type of unsandboxed paths. -foreign import data Unsandboxed :: SandboxedOrNot - --- | The (phantom) type of sandboxed paths. -foreign import data Sandboxed :: SandboxedOrNot - -- | A newtype around a file name. newtype Name (n :: DirOrFile) = Name NonEmptyString derive instance newtypeName :: Newtype (Name n) _ @@ -128,12 +102,11 @@ runName :: forall a. Name a -> String runName (Name name) = NEString.toString name -- | A type that describes a Path. All flavors of paths are described by this --- | type, whether they are absolute or relative paths, whether they --- | refer to files or directories, whether they are sandboxed or not. +-- | type, whether they are absolute or relative paths and whether they +-- | refer to files or directories. -- | -- | * The type parameter `a` describes whether the path is `Rel` or `Abs`. -- | * The type parameter `b` describes whether the path is `File` or `Dir`. --- | * The type parameter `s` describes whether the path is `Sandboxed` or `Unsandboxed`. -- | -- | To ensure type safety, there is no way for users to create a value of -- | this type directly. Instead, helpers should be used, such as `rootDir`, @@ -141,91 +114,60 @@ runName (Name name) = NEString.toString name -- | -- | This ADT allows invalid paths (e.g. paths inside files), but there is no -- | possible way for such paths to be constructed by user-land code. The only --- | "invalid path" that may be constructed is using the `parentDir` function, e.g. --- | `parentDir rootDir`, or by parsing an equivalent string such as `/../`, --- | but such paths are marked as unsandboxed, and may not be rendered to strings --- | until they are first sandboxed to some directory. -data Path (a :: RelOrAbs) (b :: DirOrFile) (s :: SandboxedOrNot) +-- | "invalid path" that may be constructed is using the `parentDir` function, +-- | e.g. `parentDir rootDir`, or by parsing an equivalent string such as +-- | `/../`, but such paths may not be rendered to strings until they are first +-- | sandboxed to some directory. +data Path (a :: RelOrAbs) (b :: DirOrFile) = Init - | ParentIn (Path a Dir s) - | In (Path a Dir s) (Name b) + | ParentIn (Path a Dir) + | In (Path a Dir) (Name b) -- | A type describing a file whose location is given relative to some other, -- | unspecified directory (referred to as the "current directory"). -type RelFile s = Path Rel File s +type RelFile = Path Rel File -- | A type describing a file whose location is absolutely specified. -type AbsFile s = Path Abs File s +type AbsFile = Path Abs File -- | A type describing a directory whose location is given relative to some -- | other, unspecified directory (referred to as the "current directory"). -type RelDir s = Path Rel Dir s +type RelDir = Path Rel Dir -- | A type describing a directory whose location is absolutely specified. -type AbsDir s = Path Abs Dir s +type AbsDir = Path Abs Dir -- | A type describing a file or directory path. -type AnyPath b s = Either (Path b Dir s) (Path b File s) +type AnyPath b = Either (Path b Dir) (Path b File) -- | A type describing a relative file or directory path. -type RelPath s = AnyPath Rel s +type RelPath = AnyPath Rel -- | A type describing an absolute file or directory path. -type AbsPath s = AnyPath Abs s - -newtype PathFlipped a s b = PathFlipped (Path a b s) -derive instance newtypePathFlipped ∷ Newtype (PathFlipped a s b) _ - -class SplitDirOrFile (x :: DirOrFile) where - dirOrFileF :: forall f. f x -> Either (f Dir) (f File) - -instance relSplitDirOrFile :: SplitDirOrFile Dir where dirOrFileF = Left -instance absSplitDirOrFile :: SplitDirOrFile File where dirOrFileF = Right - -dirOrFile :: forall a b s. SplitDirOrFile b => Path a b s -> AnyPath a s -dirOrFile p = bimap (un PathFlipped) (un PathFlipped) $ dirOrFileF (PathFlipped p) - -dirOrFileName :: forall b. SplitDirOrFile b => Name b -> Either (Name Dir) (Name File) -dirOrFileName = dirOrFileF - -class SplitRelOrAbs (a :: RelOrAbs) where - relOrAbs :: forall b s. Path a b s -> Either (Path Rel b s) (Path Abs b s) +type AbsPath = AnyPath Abs -instance relSplitRelOrAbs :: SplitRelOrAbs Rel where relOrAbs = Left -instance absSplitRelOrAbs :: SplitRelOrAbs Abs where relOrAbs = Right +class IsDirOrFile (x :: DirOrFile) where + dirOrFile :: forall f. f x -> Either (f Dir) (f File) --- | Escapers encode segments or characters which have reserved meaning. -newtype Escaper = Escaper (String -> String) +instance relIsDirOrFile :: IsDirOrFile Dir where dirOrFile = Left +instance absIsDirOrFile :: IsDirOrFile File where dirOrFile = Right --- | Given an escaper and a segment to encode, returns the encoded segment. -runEscaper :: Escaper -> String -> String -runEscaper (Escaper f) = f +class IsRelOrAbs (a :: RelOrAbs) where + relOrAbs :: forall f b. f a b -> Either (f Rel b) (f Abs b) --- | An escaper that does nothing except remove slashes (the bare minimum of --- | what must be done). -nonEscaper :: Escaper -nonEscaper = Escaper \s -> S.joinWith "" $ filter (_ /= "/") (S.split (S.Pattern "") s) - --- | An escaper that removes all slashes, converts ".." into "$dot$dot", and --- | converts "." into "$dot". -posixEscaper :: Escaper -posixEscaper = Escaper $ - runEscaper nonEscaper >>> - case _ of - ".." -> "$dot$dot" - "." -> "$dot" - s -> s +instance relIsRelOrAbs :: IsRelOrAbs Rel where relOrAbs = Left +instance absIsRelOrAbs :: IsRelOrAbs Abs where relOrAbs = Right -- | Creates a path which points to a relative file of the specified name. -file :: NonEmptyString -> Path Rel File Sandboxed +file :: NonEmptyString -> Path Rel File file = file' <<< Name -- | Creates a path which points to a relative file of the specified name. -file' :: Name File -> Path Rel File Sandboxed +file' :: Name File -> Path Rel File file' = In Init -- | Retrieves the name of a file path. -fileName :: forall a s. Path a File s -> Name File +fileName :: forall a. Path a File -> Name File fileName (In _ f) = f fileName _ = unsafeCrashWith """Hit unrechable path in Data.Pathy.fileName @@ -269,58 +211,47 @@ _updateExt ext = case _ of Nothing -> Name ext -- | Creates a path which points to a relative directory of the specified name. -dir :: NonEmptyString -> Path Rel Dir Sandboxed +dir :: NonEmptyString -> Path Rel Dir dir = dir' <<< Name -- | Creates a path which points to a relative directory of the specified name. -dir' :: Name Dir -> Path Rel Dir Sandboxed +dir' :: Name Dir -> Path Rel Dir dir' = In Init -- | Retrieves the name of a directory path. Not all paths have such a name, -- | for example, the root or current directory. -dirName :: forall a s. Path a Dir s -> Maybe (Name Dir) +dirName :: forall a. Path a Dir -> Maybe (Name Dir) dirName p = case canonicalize p of In _ d -> Just d _ -> Nothing -pathName :: forall b s. AnyPath b s -> Either (Maybe (Name Dir)) (Name File) +pathName :: forall b. AnyPath b -> Either (Maybe (Name Dir)) (Name File) pathName = bimap dirName fileName -- | Given a directory path, appends either a file or directory to the path. -appendPath :: forall a b is rs s. AppendOutcome is rs s => Path a Dir is -> Path Rel b rs -> Path a b s -appendPath Init Init = (Init :: Path a b s) -appendPath (ParentIn p) Init = ParentIn (p Init :: Path Rel Dir rs) -appendPath (In p (Name d)) Init = In (p Init :: Path Rel Dir rs) (Name d) +appendPath :: forall a b. Path a Dir -> Path Rel b -> Path a b +appendPath Init Init = Init +appendPath (ParentIn p) Init = ParentIn (p Init) +appendPath (In p (Name d)) Init = In (p Init) (Name d) appendPath p1 (ParentIn p2) = ParentIn (p1 p2) appendPath p1 (In p2 n2) = In (p1 p2) n2 infixl 6 appendPath as -class AppendOutcome (is :: SandboxedOrNot) (rs :: SandboxedOrNot) (s :: SandboxedOrNot) | is rs -> s -instance appendSSOutcome :: AppendOutcome Sandboxed Sandboxed Sandboxed -instance appendUSOutcome :: AppendOutcome Unsandboxed Sandboxed Unsandboxed -instance appendSUOutcome :: AppendOutcome Sandboxed Unsandboxed Unsandboxed -instance appendUUOutcome :: AppendOutcome Unsandboxed Unsandboxed Unsandboxed - -- | Sets the extension of the file to the specified extension. -- | -- | ```purescript -- | file "image" <.> "png" -- | ``` -setExtension :: forall a s. Path a File s -> NonEmptyString -> Path a File s +setExtension :: forall a s. Path a File -> NonEmptyString -> Path a File setExtension p ext = renameFile (changeExtension' $ const ext) p infixl 6 setExtension as <.> -- | Ascends into the parent of the specified directory, then descends into --- | the specified path. The result is always unsandboxed because it may escape --- | its previous sandbox. -parentAppend - :: forall a b s s' - . Path a Dir s - -> Path Rel b s' - -> Path a b Unsandboxed -parentAppend d p = parentDir d unsandbox p +-- | the specified path. +parentAppend :: forall a b. Path a Dir -> Path Rel b -> Path a b +parentAppend d p = parentDir d p infixl 6 parentAppend as <..> @@ -329,68 +260,57 @@ infixl 6 parentAppend as <..> -- | if the last path segment is root directory, current directory, or parent -- | directory). peel - :: forall a b s - . Path a b s - -> Maybe (Tuple (Path a Dir Unsandboxed) (Name b)) -peel = unsafeCoerce unsafePeel - -unsafePeel - :: forall a b s - . Path a b s - -> Maybe (Tuple (Path a Dir s) (Name b)) -unsafePeel Init = Nothing -unsafePeel p@(ParentIn _) = case canonicalize' p of - Tuple true p' -> unsafePeel p' + :: forall a b + . Path a b + -> Maybe (Tuple (Path a Dir) (Name b)) +peel Init = Nothing +peel p@(ParentIn _) = case canonicalize' p of + Tuple true p' -> peel p' _ -> Nothing -unsafePeel (In p n) = Just $ Tuple p n +peel (In p n) = Just $ Tuple p n -- | Returns the depth of the path. This may be negative in some cases, e.g. -- | `./../../../` has depth `-3`. -depth :: forall a b s. Path a b s -> Int +depth :: forall a b. Path a b -> Int depth Init = 0 depth (ParentIn p) = depth p - 1 depth (In p _) = depth p + 1 --- | Unsandboxes any path (whether sandboxed or not). -unsandbox :: forall a b s. Path a b s -> Path a b Unsandboxed -unsandbox = unsafeCoerce - -- | Creates a path that points to the parent directory of the specified path. --- | This function always unsandboxes the path. -parentDir :: forall a s. Path a Dir s -> Path a Dir Unsandboxed -parentDir = ParentIn <<< unsandbox +parentDir :: forall a. Path a Dir -> Path a Dir +parentDir = ParentIn -unsafeCoerceType :: forall a b b' s. Path a b s -> Path a b' s +unsafeCoerceType :: forall a b b'. Path a b -> Path a b' unsafeCoerceType = unsafeCoerce -- | The "current directory", which can be used to define relatively-located resources. -currentDir :: Path Rel Dir Sandboxed +currentDir :: Path Rel Dir currentDir = Init -- | The root directory, which can be used to define absolutely-located resources. -rootDir :: Path Abs Dir Sandboxed +rootDir :: Path Abs Dir rootDir = Init -- | Renames a file path. -renameFile :: forall a s. (Name File -> Name File) -> Path a File s -> Path a File s +renameFile :: forall a. (Name File -> Name File) -> Path a File -> Path a File renameFile f = un Identity <<< renameFile' (pure <<< f) -renameFile' :: forall f a s. Applicative f => (Name File -> f (Name File)) -> Path a File s -> f (Path a File s) +renameFile' :: forall f a s. Applicative f => (Name File -> f (Name File)) -> Path a File -> f (Path a File) renameFile' f (In p f0) = In p <$> f f0 renameFile' _ p = pure p -- | Renames a directory path. Note: This is a simple rename of the terminal -- | directory name, not a "move". -renameDir :: forall a s. (Name Dir -> Name Dir) -> Path a Dir s -> Path a Dir s +renameDir :: forall a. (Name Dir -> Name Dir) -> Path a Dir -> Path a Dir renameDir f (In p d) = In p (f d) renameDir _ p = p -- | Canonicalizes a path, by reducing things in the form `/x/../` to just `/x/`. -canonicalize :: forall a b s. Path a b s -> Path a b s +canonicalize :: forall a b. Path a b -> Path a b canonicalize = snd <<< canonicalize' -- | Canonicalizes a path and returns information on whether or not it actually changed. -canonicalize' :: forall a b s. Path a b s -> Tuple Boolean (Path a b s) +canonicalize' :: forall a b. Path a b -> Tuple Boolean (Path a b) canonicalize' Init = Tuple false Init canonicalize' (ParentIn (In p f)) = Tuple true (unsafeCoerceType $ snd $ canonicalize' p) canonicalize' (ParentIn p) = case canonicalize' p of @@ -399,107 +319,47 @@ canonicalize' (ParentIn p) = case canonicalize' p of in if changed then canonicalize' p'' else Tuple changed p'' canonicalize' (In p f) = flip In f <$> canonicalize' p -unsafePrintPath' :: forall a b s. SplitRelOrAbs a => SplitDirOrFile b => Escaper -> Path a b s -> String -unsafePrintPath' r = go - where - go :: forall a' b' s'. SplitRelOrAbs a' => SplitDirOrFile b' => Path a' b' s' -> String - go p@Init = case relOrAbs p of - Left _ -> "./" - Right _ -> "/" - go (ParentIn p) = go p <> "../" - go (In p n) = case dirOrFileName n of - Left dirN -> go p <> escape (runName dirN) <> "/" - Right fileN -> go p <> escape (runName fileN) - escape = runEscaper r - -unsafePrintPath - :: forall a b s - . SplitRelOrAbs a - => SplitDirOrFile b - => Path a b s - -> String -unsafePrintPath = unsafePrintPath' posixEscaper - --- | Prints a `Path` into its canonical `String` representation. For security --- | reasons, the path must be sandboxed before it can be rendered to a string. -printPath - :: forall a b - . SplitRelOrAbs a - => SplitDirOrFile b - => Path a b Sandboxed - -> String -printPath = unsafePrintPath - --- | Prints a `Path` into its canonical `String` representation, using the --- | specified escaper to escape special characters in path segments. For --- | security reasons, the path must be sandboxed before rendering to string. -printPath' - :: forall a b - . SplitRelOrAbs a - => SplitDirOrFile b - => Escaper - -> Path a b Sandboxed - -> String -printPath' = unsafePrintPath' - -- | Determines if two paths have the exact same representation. Note that -- | two paths may represent the same path even if they have different -- | representations! identicalPath - :: forall a a' b b' s s' - . SplitRelOrAbs a - => SplitRelOrAbs a' - => SplitDirOrFile b - => SplitDirOrFile b' - => Path a b s -> Path a' b' s' -> Boolean + :: forall a a' b b' + . IsRelOrAbs a + => IsRelOrAbs a' + => IsDirOrFile b + => IsDirOrFile b' + => Path a b -> Path a' b' -> Boolean identicalPath p1 p2 = show p1 == show p2 -relativify :: forall a b. Path Abs a b -> Path Rel a b -relativify = unsafeCoerce - -absolutify :: forall a b. Path Rel a b -> Path Abs a b -absolutify = unsafeCoerce - - -- | Makes one path relative to another reference path, if possible, otherwise --- | returns `Nothing`. The returned path inherits the sandbox settings of the --- | reference path. +-- | returns `Nothing`. -- | -- | Note there are some cases this function cannot handle. -relativeTo :: forall a b s s'. SplitRelOrAbs a => SplitDirOrFile b => AppendOutcome s' s' s' => Path a b s -> Path a Dir s' -> Maybe (Path Rel b s') +relativeTo :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> Path a Dir -> Maybe (Path Rel b) relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) where - relativeTo' :: forall b'. SplitDirOrFile b' => Path a b' s -> Path a Dir s' -> Maybe (Path Rel b' s') - relativeTo' Init Init = pure (Init :: Path Rel b' s') + relativeTo' :: forall b'. IsDirOrFile b' => Path a b' -> Path a Dir -> Maybe (Path Rel b') + relativeTo' Init Init = pure Init relativeTo' cp1 cp2 - | identicalPath cp1 cp2 = pure (Init :: Path Rel b' s') + | identicalPath cp1 cp2 = pure Init | otherwise = do - Tuple cp1Path name <- unsafePeel cp1 - rel <- relativeTo' cp1Path cp2 - pure case dirOrFileName name of - Left dirN -> joinSplit $ rel In (Init :: Path Rel Dir s') dirN - Right fileN -> joinSplit $ rel In (Init :: Path Rel Dir s') fileN + Tuple cp1Path name <- peel cp1 + rel <- relativeTo' cp1Path cp2 + pure case dirOrFile name of + Left dirN -> joinSplit $ rel In Init dirN + Right fileN -> joinSplit $ rel In Init fileN where - joinSplit :: forall a_ b_ s_. Path a_ b_ s_ -> Path a_ b' s_ + joinSplit :: forall a_ b_. Path a_ b_ -> Path a_ b' joinSplit = unsafeCoerce --- | Attempts to sandbox a path relative to some directory. If successful, the sandboxed --- | directory will be returned relative to the sandbox directory (although this can easily --- | be converted into an absolute path using ``). --- | --- | This combinator can be used to ensure that paths which originate from user-code --- | cannot access data outside a given directory. -sandbox :: forall a b s. SplitRelOrAbs a => SplitDirOrFile b => Path a Dir Sandboxed -> Path a b s -> Maybe (Path Rel b Sandboxed) -sandbox p1 p2 = p2 `relativeTo` p1 - -- | Refines path segments but does not change anything else. -refine :: forall a b s. SplitDirOrFile b => (Name File -> Name File) -> (Name Dir -> Name Dir) -> Path a b s -> Path a b s +refine :: forall a b. IsDirOrFile b => (Name File -> Name File) -> (Name Dir -> Name Dir) -> Path a b -> Path a b refine f d = go where - go :: forall a' b' s'. SplitDirOrFile b' => Path a' b' s' -> Path a' b' s' + go :: forall a' b'. IsDirOrFile b' => Path a' b' -> Path a' b' go Init = Init go (ParentIn p) = ParentIn (go p) - go (In p name) = case dirOrFileName name of + go (In p name) = case dirOrFile name of Left dirN -> -- We need to unwrap name so it compiles :(( let Name n = (d dirN) in In (go p) (Name n) @@ -514,10 +374,10 @@ type ParseError = Unit -- | marked with a trailing slash character (`'/'`). parsePath :: forall z - . (RelDir Unsandboxed -> z) - -> (AbsDir Unsandboxed -> z) - -> (RelFile Unsandboxed -> z) - -> (AbsFile Unsandboxed -> z) + . (RelDir -> z) + -> (AbsDir -> z) + -> (RelFile -> z) + -> (AbsFile -> z) -> (ParseError -> z) -> String -> z @@ -536,7 +396,7 @@ parsePath rd ad rf af err p = false, true -> segsRaw false, false -> dropEnd 1 segsRaw last = length segsDropped - 1 - folder :: forall a b s. SplitDirOrFile b => Int -> Path a b s -> NonEmptyString -> Path a b s + folder :: forall a b. IsDirOrFile b => Int -> Path a b -> NonEmptyString -> Path a b folder idx base seg = if NEString.toString seg == "." then base @@ -553,34 +413,34 @@ parsePath rd ad rf af err p = false, false -> rd $ foldlWithIndex folder Init segs -- | Attempts to parse a relative file from a string. -parseRelFile :: String -> Maybe (RelFile Unsandboxed) +parseRelFile :: String -> Maybe (RelFile) parseRelFile = parsePath (const Nothing) (const Nothing) Just (const Nothing) (const Nothing) -- | Attempts to parse an absolute file from a string. -parseAbsFile :: String -> Maybe (AbsFile Unsandboxed) +parseAbsFile :: String -> Maybe (AbsFile) parseAbsFile = parsePath (const Nothing) (const Nothing) (const Nothing) Just (const Nothing) -- | Attempts to parse a relative directory from a string. -parseRelDir :: String -> Maybe (RelDir Unsandboxed) +parseRelDir :: String -> Maybe (RelDir) parseRelDir = parsePath Just (const Nothing) (const Nothing) (const Nothing) (const Nothing) -- | Attempts to parse an absolute directory from a string. -parseAbsDir :: String -> Maybe (AbsDir Unsandboxed) +parseAbsDir :: String -> Maybe (AbsDir) parseAbsDir = parsePath (const Nothing) Just (const Nothing) (const Nothing) (const Nothing) -instance showPathRelDir :: (SplitRelOrAbs a, SplitDirOrFile b) => Show (Path a b s) where +instance showPathRelDir :: (IsRelOrAbs a, IsDirOrFile b) => Show (Path a b) where show p@Init = case relOrAbs p of Left _ -> "currentDir" Right _ -> "rootDir" show (ParentIn p) = "(parentDir " <> show p <> ")" - show (In p n ) = "(" <> show p <> " " <> case dirOrFileName n of + show (In p n ) = "(" <> show p <> " " <> case dirOrFile n of Left d -> "dir " <> show n <> ")" Right f -> "file " <> show f <> ")" -instance eqPath :: (SplitRelOrAbs a, SplitDirOrFile b) => Eq (Path a b s) where +instance eqPath :: (IsRelOrAbs a, IsDirOrFile b) => Eq (Path a b) where eq p1 p2 = canonicalize p1 `identicalPath` canonicalize p2 -instance ordPath :: (SplitRelOrAbs a, SplitDirOrFile b) => Ord (Path a b s) where +instance ordPath :: (IsRelOrAbs a, IsDirOrFile b) => Ord (Path a b) where compare p1 p2 = go (canonicalize p1) (canonicalize p2) where go Init Init = EQ @@ -600,32 +460,18 @@ derive instance ordName :: Ord (Name a) type DirPathView = List (Name Dir) type FilePathView = Tuple DirPathView (Name File) -viewDir :: forall a. Path a Dir Sandboxed -> DirPathView +viewDir :: forall a. Path a Dir -> DirPathView viewDir = reverse <<< go where go = case _ of Init -> Nil - ParentIn _ -> unsafeCrashWith "Impossible, ParentIn can't be in Sandboxed path" + ParentIn _ -> unsafeCrashWith "Impossible, ParentIn can't be in path" In d n -> Cons n (go d) -viewFile :: forall a. Path a File Sandboxed -> FilePathView +viewFile :: forall a. Path a File -> FilePathView viewFile = peelFile >>> lmap viewDir -type DirPathViewUnsandboxed = List (Maybe (Name Dir)) -type FilePathViewUnsandboxed = Tuple DirPathViewUnsandboxed (Name File) - -viewDirUnsandboxed :: forall a. Path a Dir Unsandboxed -> DirPathViewUnsandboxed -viewDirUnsandboxed = reverse <<< go - where - go = case _ of - Init -> Nil - ParentIn p -> Cons Nothing (go p) - In d n -> Cons (Just n) (go d) - -viewFileUnsandboxed :: forall a. Path a File Unsandboxed -> FilePathViewUnsandboxed -viewFileUnsandboxed = peelFile >>> lmap viewDirUnsandboxed - -peelFile :: forall a s. Path a File s -> Tuple (Path a Dir s) (Name File) +peelFile :: forall a. Path a File -> Tuple (Path a Dir) (Name File) peelFile = case _ of Init -> unsafeCrashWith "Impossible, Init can't be in File path" ParentIn _ -> unsafeCrashWith "Impossible, ParentIn can't be in File path" diff --git a/src/Data/Path/Pathy/Gen.purs b/src/Data/Path/Pathy/Gen.purs index dc01467..e7eecef 100644 --- a/src/Data/Path/Pathy/Gen.purs +++ b/src/Data/Path/Pathy/Gen.purs @@ -17,7 +17,7 @@ import Data.Either (Either(..)) import Data.Foldable (foldr) import Data.List as L import Data.NonEmpty ((:|)) -import Data.Path.Pathy (AbsPath, AbsFile, AbsDir, RelDir, RelFile, RelPath, Sandboxed, ()) +import Data.Path.Pathy (AbsPath, AbsFile, AbsDir, RelDir, RelFile, RelPath, ()) import Data.Path.Pathy as P import Data.String.Gen as SG import Data.String.NonEmpty (NonEmptyString, cons) @@ -28,34 +28,34 @@ genName = cons <$> genChar <*> SG.genString genChar genChar = Gen.oneOf $ CG.genDigitChar :| [CG.genAlpha] -genAbsDirPath :: forall m. MonadGen m => MonadRec m => m (AbsDir Sandboxed) +genAbsDirPath :: forall m. MonadGen m => MonadRec m => m AbsDir genAbsDirPath = Gen.sized \size → do newSize ← Gen.chooseInt 0 size Gen.resize (const newSize) do parts ∷ L.List NonEmptyString ← Gen.unfoldable genName pure $ foldr (flip P.appendPath <<< P.dir) P.rootDir parts -genAbsFilePath :: forall m. MonadGen m => MonadRec m => m (AbsFile Sandboxed) +genAbsFilePath :: forall m. MonadGen m => MonadRec m => m AbsFile genAbsFilePath = do dir ← genAbsDirPath file ← genName pure $ dir P.file file -genAbsAnyPath :: forall m. MonadGen m => MonadRec m => m (AbsPath Sandboxed) +genAbsAnyPath :: forall m. MonadGen m => MonadRec m => m AbsPath genAbsAnyPath = Gen.oneOf $ (Left <$> genAbsDirPath) :| [Right <$> genAbsFilePath] -genRelDirPath :: forall m. MonadGen m => MonadRec m => m (RelDir Sandboxed) +genRelDirPath :: forall m. MonadGen m => MonadRec m => m RelDir genRelDirPath = Gen.sized \size → do newSize ← Gen.chooseInt 0 size Gen.resize (const newSize) do parts ∷ L.List NonEmptyString ← Gen.unfoldable genName pure $ foldr (flip P.appendPath <<< P.dir) P.currentDir parts -genRelFilePath :: forall m. MonadGen m => MonadRec m => m (RelFile Sandboxed) +genRelFilePath :: forall m. MonadGen m => MonadRec m => m RelFile genRelFilePath = do dir ← genRelDirPath file ← genName pure $ dir P.file file -genRelAnyPath :: forall m. MonadGen m => MonadRec m => m (RelPath Sandboxed) +genRelAnyPath :: forall m. MonadGen m => MonadRec m => m RelPath genRelAnyPath = Gen.oneOf $ (Left <$> genRelDirPath) :| [Right <$> genRelFilePath] From c657b9b7fcdbfb2b1620d2938c20cb74d48843b3 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Mon, 19 Feb 2018 12:30:57 +0000 Subject: [PATCH 24/59] Make `dirOrFile` and `absOrRel` folds --- src/Data/Path/Pathy.purs | 51 ++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index a3e96bc..a97d2bc 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -19,7 +19,6 @@ module Data.Path.Pathy , (<.>) , parentAppend , (<..>) - , runName , canonicalize , changeExtension , currentDir @@ -43,8 +42,10 @@ module Data.Path.Pathy , parseRelFile , class IsRelOrAbs , relOrAbs + , overRelOrAbs , class IsDirOrFile , dirOrFile + , overDirOrFile , refine , relativeTo , renameDir @@ -63,7 +64,7 @@ import Prelude import Data.Array (drop, dropEnd, length) import Data.Bifunctor (bimap, lmap) -import Data.Either (Either(..)) +import Data.Either (Either) import Data.FoldableWithIndex (foldlWithIndex) import Data.Identity (Identity(..)) import Data.List (List(..), reverse) @@ -95,11 +96,8 @@ foreign import data Dir :: DirOrFile -- | A newtype around a file name. newtype Name (n :: DirOrFile) = Name NonEmptyString -derive instance newtypeName :: Newtype (Name n) _ --- | Unwraps the `Name` newtype. -runName :: forall a. Name a -> String -runName (Name name) = NEString.toString name +derive instance newtypeName :: Newtype (Name n) _ -- | A type that describes a Path. All flavors of paths are described by this -- | type, whether they are absolute or relative paths and whether they @@ -147,16 +145,22 @@ type RelPath = AnyPath Rel type AbsPath = AnyPath Abs class IsDirOrFile (x :: DirOrFile) where - dirOrFile :: forall f. f x -> Either (f Dir) (f File) + dirOrFile :: forall f r. (f Dir -> r) -> (f File -> r) -> f x -> r + +instance relIsDirOrFile :: IsDirOrFile Dir where dirOrFile f _ = f +instance absIsDirOrFile :: IsDirOrFile File where dirOrFile _ f = f -instance relIsDirOrFile :: IsDirOrFile Dir where dirOrFile = Left -instance absIsDirOrFile :: IsDirOrFile File where dirOrFile = Right +overDirOrFile :: forall f a. IsDirOrFile a => (f Dir -> f Dir) -> (f File -> f File) -> f a -> f a +overDirOrFile f g = dirOrFile (unsafeCoerce f) (unsafeCoerce g) class IsRelOrAbs (a :: RelOrAbs) where - relOrAbs :: forall f b. f a b -> Either (f Rel b) (f Abs b) + relOrAbs :: forall f b r. (f Rel b -> r) -> (f Abs b -> r) -> f a b -> r -instance relIsRelOrAbs :: IsRelOrAbs Rel where relOrAbs = Left -instance absIsRelOrAbs :: IsRelOrAbs Abs where relOrAbs = Right +instance relIsRelOrAbs :: IsRelOrAbs Rel where relOrAbs f _ = f +instance absIsRelOrAbs :: IsRelOrAbs Abs where relOrAbs _ f = f + +overRelOrAbs :: forall f a b. IsRelOrAbs a => (f Rel b -> f Rel b) -> (f Abs b -> f Abs b) -> f a b -> f a b +overRelOrAbs f g = relOrAbs (unsafeCoerce f) (unsafeCoerce g) -- | Creates a path which points to a relative file of the specified name. file :: NonEmptyString -> Path Rel File @@ -345,12 +349,7 @@ relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) | otherwise = do Tuple cp1Path name <- peel cp1 rel <- relativeTo' cp1Path cp2 - pure case dirOrFile name of - Left dirN -> joinSplit $ rel In Init dirN - Right fileN -> joinSplit $ rel In Init fileN - where - joinSplit :: forall a_ b_. Path a_ b_ -> Path a_ b' - joinSplit = unsafeCoerce + pure $ rel In Init name -- | Refines path segments but does not change anything else. refine :: forall a b. IsDirOrFile b => (Name File -> Name File) -> (Name Dir -> Name Dir) -> Path a b -> Path a b @@ -359,13 +358,7 @@ refine f d = go go :: forall a' b'. IsDirOrFile b' => Path a' b' -> Path a' b' go Init = Init go (ParentIn p) = ParentIn (go p) - go (In p name) = case dirOrFile name of - Left dirN -> - -- We need to unwrap name so it compiles :(( - let Name n = (d dirN) in In (go p) (Name n) - Right fileN -> - -- We need to unwrap name so it compiles :(( - let Name n = (f fileN) in In (go p) (Name n) + go (In p name) = In (go p) (overDirOrFile d f name) type ParseError = Unit @@ -429,13 +422,9 @@ parseAbsDir :: String -> Maybe (AbsDir) parseAbsDir = parsePath (const Nothing) Just (const Nothing) (const Nothing) (const Nothing) instance showPathRelDir :: (IsRelOrAbs a, IsDirOrFile b) => Show (Path a b) where - show p@Init = case relOrAbs p of - Left _ -> "currentDir" - Right _ -> "rootDir" + show p@Init = relOrAbs (const "currentDir") (const "rootDir") p show (ParentIn p) = "(parentDir " <> show p <> ")" - show (In p n ) = "(" <> show p <> " " <> case dirOrFile n of - Left d -> "dir " <> show n <> ")" - Right f -> "file " <> show f <> ")" + show (In p n) = "(" <> show p <> " " <> dirOrFile (("dir " <> _) <<< show) (("file " <> _) <<< show) n <> ")" instance eqPath :: (IsRelOrAbs a, IsDirOrFile b) => Eq (Path a b) where eq p1 p2 = canonicalize p1 `identicalPath` canonicalize p2 From 573bbbc125cca0ae6205a1b178457c33e679fcb1 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Mon, 19 Feb 2018 12:42:19 +0000 Subject: [PATCH 25/59] Add `foldPath` function --- src/Data/Path/Pathy.purs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index a97d2bc..47c676a 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -34,6 +34,7 @@ module Data.Path.Pathy , pathName , identicalPath , parentDir + , foldPath , peel , parsePath , parseAbsDir @@ -259,6 +260,18 @@ parentAppend d p = parentDir d p infixl 6 parentAppend as <..> +foldPath + :: forall a b r + . r + -> (Path a Dir -> r) + -> (Path a Dir -> Name b -> r) + -> Path a b + -> r +foldPath r f g = case _ of + Init -> r + ParentIn d -> f d + In d n -> g d n + -- | Peels off the last directory and the terminal file or directory name -- | from the path. Returns `Nothing` if there is no such pair (for example, -- | if the last path segment is root directory, current directory, or parent From 611bb79cb3eb262c2fb160d0080fccf2144813ac Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Mon, 19 Feb 2018 14:00:20 +0000 Subject: [PATCH 26/59] Pass proof of type along with `relOrAbs` / `dirOrFile` --- src/Data/Path/Pathy.purs | 56 ++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 47c676a..8f30de6 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -42,11 +42,11 @@ module Data.Path.Pathy , parseRelDir , parseRelFile , class IsRelOrAbs - , relOrAbs - , overRelOrAbs + , onRelOrAbs + , foldRelOrAbs , class IsDirOrFile - , dirOrFile - , overDirOrFile + , onDirOrFile + , foldDirOrFile , refine , relativeTo , renameDir @@ -145,23 +145,39 @@ type RelPath = AnyPath Rel -- | A type describing an absolute file or directory path. type AbsPath = AnyPath Abs -class IsDirOrFile (x :: DirOrFile) where - dirOrFile :: forall f r. (f Dir -> r) -> (f File -> r) -> f x -> r +class IsDirOrFile (b :: DirOrFile) where + onDirOrFile + :: forall f r + . ((f Dir -> f b) -> f Dir -> r) + -> ((f File -> f b) -> f File -> r) + -> f b + -> r -instance relIsDirOrFile :: IsDirOrFile Dir where dirOrFile f _ = f -instance absIsDirOrFile :: IsDirOrFile File where dirOrFile _ f = f +foldDirOrFile :: forall f b r. IsDirOrFile b => (f Dir -> r) -> (f File -> r) -> f b -> r +foldDirOrFile f g = onDirOrFile (const f) (const g) -overDirOrFile :: forall f a. IsDirOrFile a => (f Dir -> f Dir) -> (f File -> f File) -> f a -> f a -overDirOrFile f g = dirOrFile (unsafeCoerce f) (unsafeCoerce g) +instance relIsDirOrFile :: IsDirOrFile Dir where onDirOrFile f _ = f id +instance absIsDirOrFile :: IsDirOrFile File where onDirOrFile _ f = f id class IsRelOrAbs (a :: RelOrAbs) where - relOrAbs :: forall f b r. (f Rel b -> r) -> (f Abs b -> r) -> f a b -> r - -instance relIsRelOrAbs :: IsRelOrAbs Rel where relOrAbs f _ = f -instance absIsRelOrAbs :: IsRelOrAbs Abs where relOrAbs _ f = f - -overRelOrAbs :: forall f a b. IsRelOrAbs a => (f Rel b -> f Rel b) -> (f Abs b -> f Abs b) -> f a b -> f a b -overRelOrAbs f g = relOrAbs (unsafeCoerce f) (unsafeCoerce g) + onRelOrAbs + :: forall f b r + . ((f Rel b -> f a b) -> f Rel b -> r) + -> ((f Abs b -> f a b) -> f Abs b -> r) + -> f a b + -> r + +instance relIsRelOrAbs :: IsRelOrAbs Rel where onRelOrAbs f _ = f id +instance absIsRelOrAbs :: IsRelOrAbs Abs where onRelOrAbs _ f = f id + +foldRelOrAbs + :: forall f a b r + . IsRelOrAbs a + => (f Rel b -> r) + -> (f Abs b -> r) + -> f a b + -> r +foldRelOrAbs f g = onRelOrAbs (const f) (const g) -- | Creates a path which points to a relative file of the specified name. file :: NonEmptyString -> Path Rel File @@ -371,7 +387,7 @@ refine f d = go go :: forall a' b'. IsDirOrFile b' => Path a' b' -> Path a' b' go Init = Init go (ParentIn p) = ParentIn (go p) - go (In p name) = In (go p) (overDirOrFile d f name) + go (In p name) = In (go p) (onDirOrFile (\p -> p <<< d) (\p -> p <<< f) name) type ParseError = Unit @@ -435,9 +451,9 @@ parseAbsDir :: String -> Maybe (AbsDir) parseAbsDir = parsePath (const Nothing) Just (const Nothing) (const Nothing) (const Nothing) instance showPathRelDir :: (IsRelOrAbs a, IsDirOrFile b) => Show (Path a b) where - show p@Init = relOrAbs (const "currentDir") (const "rootDir") p + show p@Init = foldRelOrAbs (const "currentDir") (const "rootDir") p show (ParentIn p) = "(parentDir " <> show p <> ")" - show (In p n) = "(" <> show p <> " " <> dirOrFile (("dir " <> _) <<< show) (("file " <> _) <<< show) n <> ")" + show (In p n) = "(" <> show p <> " " <> foldDirOrFile (("dir " <> _) <<< show) (("file " <> _) <<< show) n <> ")" instance eqPath :: (IsRelOrAbs a, IsDirOrFile b) => Eq (Path a b) where eq p1 p2 = canonicalize p1 `identicalPath` canonicalize p2 From 821a76fc64e4ba9e70067f3fa8023e916378c8c8 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Mon, 19 Feb 2018 14:34:17 +0000 Subject: [PATCH 27/59] Rename `ParentIn` / `parentDir` to `parentOf` --- src/Data/Path/Pathy.purs | 46 ++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 8f30de6..b9a7126 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -33,7 +33,7 @@ module Data.Path.Pathy , fileName , pathName , identicalPath - , parentDir + , parentOf , foldPath , peel , parsePath @@ -113,13 +113,13 @@ derive instance newtypeName :: Newtype (Name n) _ -- | -- | This ADT allows invalid paths (e.g. paths inside files), but there is no -- | possible way for such paths to be constructed by user-land code. The only --- | "invalid path" that may be constructed is using the `parentDir` function, --- | e.g. `parentDir rootDir`, or by parsing an equivalent string such as +-- | "invalid path" that may be constructed is using the `parentOf` function, +-- | e.g. `parentOf rootDir`, or by parsing an equivalent string such as -- | `/../`, but such paths may not be rendered to strings until they are first -- | sandboxed to some directory. data Path (a :: RelOrAbs) (b :: DirOrFile) = Init - | ParentIn (Path a Dir) + | ParentOf (Path a Dir) | In (Path a Dir) (Name b) -- | A type describing a file whose location is given relative to some other, @@ -252,9 +252,9 @@ pathName = bimap dirName fileName -- | Given a directory path, appends either a file or directory to the path. appendPath :: forall a b. Path a Dir -> Path Rel b -> Path a b appendPath Init Init = Init -appendPath (ParentIn p) Init = ParentIn (p Init) +appendPath (ParentOf p) Init = ParentOf (p Init) appendPath (In p (Name d)) Init = In (p Init) (Name d) -appendPath p1 (ParentIn p2) = ParentIn (p1 p2) +appendPath p1 (ParentOf p2) = ParentOf (p1 p2) appendPath p1 (In p2 n2) = In (p1 p2) n2 infixl 6 appendPath as @@ -272,7 +272,7 @@ infixl 6 setExtension as <.> -- | Ascends into the parent of the specified directory, then descends into -- | the specified path. parentAppend :: forall a b. Path a Dir -> Path Rel b -> Path a b -parentAppend d p = parentDir d p +parentAppend d p = parentOf d p infixl 6 parentAppend as <..> @@ -285,7 +285,7 @@ foldPath -> r foldPath r f g = case _ of Init -> r - ParentIn d -> f d + ParentOf d -> f d In d n -> g d n -- | Peels off the last directory and the terminal file or directory name @@ -297,7 +297,7 @@ peel . Path a b -> Maybe (Tuple (Path a Dir) (Name b)) peel Init = Nothing -peel p@(ParentIn _) = case canonicalize' p of +peel p@(ParentOf _) = case canonicalize' p of Tuple true p' -> peel p' _ -> Nothing peel (In p n) = Just $ Tuple p n @@ -306,12 +306,12 @@ peel (In p n) = Just $ Tuple p n -- | `./../../../` has depth `-3`. depth :: forall a b. Path a b -> Int depth Init = 0 -depth (ParentIn p) = depth p - 1 +depth (ParentOf p) = depth p - 1 depth (In p _) = depth p + 1 -- | Creates a path that points to the parent directory of the specified path. -parentDir :: forall a. Path a Dir -> Path a Dir -parentDir = ParentIn +parentOf :: forall a. Path a Dir -> Path a Dir +parentOf = ParentOf unsafeCoerceType :: forall a b b'. Path a b -> Path a b' unsafeCoerceType = unsafeCoerce @@ -345,10 +345,10 @@ canonicalize = snd <<< canonicalize' -- | Canonicalizes a path and returns information on whether or not it actually changed. canonicalize' :: forall a b. Path a b -> Tuple Boolean (Path a b) canonicalize' Init = Tuple false Init -canonicalize' (ParentIn (In p f)) = Tuple true (unsafeCoerceType $ snd $ canonicalize' p) -canonicalize' (ParentIn p) = case canonicalize' p of +canonicalize' (ParentOf (In p f)) = Tuple true (unsafeCoerceType $ snd $ canonicalize' p) +canonicalize' (ParentOf p) = case canonicalize' p of Tuple changed p' -> - let p'' = ParentIn p' + let p'' = ParentOf p' in if changed then canonicalize' p'' else Tuple changed p'' canonicalize' (In p f) = flip In f <$> canonicalize' p @@ -386,7 +386,7 @@ refine f d = go where go :: forall a' b'. IsDirOrFile b' => Path a' b' -> Path a' b' go Init = Init - go (ParentIn p) = ParentIn (go p) + go (ParentOf p) = ParentOf (go p) go (In p name) = In (go p) (onDirOrFile (\p -> p <<< d) (\p -> p <<< f) name) type ParseError = Unit @@ -423,7 +423,7 @@ parsePath rd ad rf af err p = if NEString.toString seg == "." then base else if NEString.toString seg == ".." then - ParentIn $ unsafeCoerceType base + ParentOf $ unsafeCoerceType base else In (unsafeCoerceType base) (Name seg) in case traverse NEString.fromString segsDropped of @@ -452,7 +452,7 @@ parseAbsDir = parsePath (const Nothing) Just (const Nothing) (const Nothing) (co instance showPathRelDir :: (IsRelOrAbs a, IsDirOrFile b) => Show (Path a b) where show p@Init = foldRelOrAbs (const "currentDir") (const "rootDir") p - show (ParentIn p) = "(parentDir " <> show p <> ")" + show (ParentOf p) = "(parentOf " <> show p <> ")" show (In p n) = "(" <> show p <> " " <> foldDirOrFile (("dir " <> _) <<< show) (("file " <> _) <<< show) n <> ")" instance eqPath :: (IsRelOrAbs a, IsDirOrFile b) => Eq (Path a b) where @@ -464,9 +464,9 @@ instance ordPath :: (IsRelOrAbs a, IsDirOrFile b) => Ord (Path a b) where go Init Init = EQ go Init _ = LT go _ Init = GT - go (ParentIn p1') (ParentIn p2') = compare p1' p2' - go (ParentIn _) _ = LT - go _ (ParentIn _) = GT + go (ParentOf p1') (ParentOf p2') = compare p1' p2' + go (ParentOf _) _ = LT + go _ (ParentOf _) = GT go (In p1' d1) (In p2' d2) = compare p1' p2' <> compare d1 d2 instance showName :: Show (Name a) where @@ -483,7 +483,7 @@ viewDir = reverse <<< go where go = case _ of Init -> Nil - ParentIn _ -> unsafeCrashWith "Impossible, ParentIn can't be in path" + ParentOf _ -> unsafeCrashWith "Impossible, ParentOf can't be in path" In d n -> Cons n (go d) viewFile :: forall a. Path a File -> FilePathView @@ -492,5 +492,5 @@ viewFile = peelFile >>> lmap viewDir peelFile :: forall a. Path a File -> Tuple (Path a Dir) (Name File) peelFile = case _ of Init -> unsafeCrashWith "Impossible, Init can't be in File path" - ParentIn _ -> unsafeCrashWith "Impossible, ParentIn can't be in File path" + ParentOf _ -> unsafeCrashWith "Impossible, ParentOf can't be in File path" In d n -> Tuple d n From 41a31d487f04f1f26baf5f8ed657910a6444d07b Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Mon, 19 Feb 2018 15:54:18 +0000 Subject: [PATCH 28/59] Sandboxing & safe printing --- src/Data/Path/Pathy.purs | 50 ++++++--- src/Data/Path/Pathy/Printer.purs | 103 +++++++++++++++++++ src/Data/Path/Pathy/Sandboxed.purs | 93 +++++++++++++++++ test/Main.purs | 160 ++++++++++++----------------- 4 files changed, 299 insertions(+), 107 deletions(-) create mode 100644 src/Data/Path/Pathy/Printer.purs create mode 100644 src/Data/Path/Pathy/Sandboxed.purs diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index b9a7126..d2465a4 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -58,8 +58,10 @@ module Data.Path.Pathy , viewDir , viewFile , peelFile - ) - where + , unsafePrintPath + , unsafePrintPath' + , module Exports + ) where import Prelude @@ -71,9 +73,11 @@ import Data.Identity (Identity(..)) import Data.List (List(..), reverse) import Data.Maybe (Maybe(..)) import Data.Newtype (class Newtype, un) +import Data.Path.Pathy.Printer (Printer, posixPrinter, printSegment) +import Data.Path.Pathy.Printer (Printer, posixPrinter, windowsPrinter) as Exports import Data.String as S -import Data.String.NonEmpty (NonEmptyString, appendString) -import Data.String.NonEmpty (fromString, toString) as NEString +import Data.String.NonEmpty (NonEmptyString) +import Data.String.NonEmpty as NES import Data.Traversable (traverse) import Data.Tuple (Tuple(..), snd) import Partial.Unsafe (unsafeCrashWith) @@ -199,7 +203,7 @@ fileName _ = unsafeCrashWith -- | Retrieves the extension of a file name. extension :: Name File -> String extension (Name f) = - let s = NEString.toString f + let s = NES.toString f in case S.lastIndexOf (S.Pattern ".") s of Just x -> S.drop (x + 1) s Nothing -> "" @@ -208,16 +212,16 @@ extension (Name f) = dropExtension :: Name File -> Maybe (Name File) dropExtension (Name n) = let - s = NEString.toString n + s = NES.toString n in case S.lastIndexOf (S.Pattern ".") s of - Just x -> map Name $ NEString.fromString $ S.take x s + Just x -> map Name $ NES.fromString $ S.take x s Nothing -> Just (Name n) changeExtension :: (String -> String) -> Name File -> Maybe (Name File) changeExtension f nm = update (f $ extension nm) (dropExtension nm) where - update ext' name = case NEString.fromString ext' of + update ext' name = case NES.fromString ext' of Nothing -> name Just ext -> Just $ _updateExt ext name @@ -228,7 +232,7 @@ changeExtension' f nm = _updateExt :: NonEmptyString -> Maybe (Name File) -> Name File _updateExt ext = case _ of - Just (Name n) -> Name $ n `appendString` "." <> ext + Just (Name n) -> Name $ n `NES.appendString` "." <> ext Nothing -> Name ext -- | Creates a path which points to a relative directory of the specified name. @@ -420,13 +424,13 @@ parsePath rd ad rf af err p = last = length segsDropped - 1 folder :: forall a b. IsDirOrFile b => Int -> Path a b -> NonEmptyString -> Path a b folder idx base seg = - if NEString.toString seg == "." then + if NES.toString seg == "." then base - else if NEString.toString seg == ".." then + else if NES.toString seg == ".." then ParentOf $ unsafeCoerceType base else In (unsafeCoerceType base) (Name seg) in - case traverse NEString.fromString segsDropped of + case traverse NES.fromString segsDropped of Nothing -> err unit Just segs -> case isAbs, isFile of true, true -> af $ foldlWithIndex folder Init segs @@ -494,3 +498,25 @@ peelFile = case _ of Init -> unsafeCrashWith "Impossible, Init can't be in File path" ParentOf _ -> unsafeCrashWith "Impossible, ParentOf can't be in File path" In d n -> Tuple d n + +-- | Prints a path exactly as-is. This is unsafe as the path may refer to a +-- | location it should not have access to. Path printing should almost always +-- | be performed with a `SandboxedPath`. +unsafePrintPath :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> String +unsafePrintPath = unsafePrintPath' posixPrinter + +-- | Prints a path exactly as-is using the specified `Printer`. This is unsafe +-- | as the path may refer to a location it should not have access to. Path +-- | printing should almost always be performed with a `SandboxedPath`. +unsafePrintPath' :: forall a b. IsRelOrAbs a => IsDirOrFile b => Printer -> Path a b -> String +unsafePrintPath' printer p = go p + where + go :: forall b'. IsDirOrFile b' => Path a b' -> String + go = + foldPath + (NES.toString (foldRelOrAbs (const (printer.current <> printer.sep)) (const printer.sep) p)) + (\p' -> go p' <> NES.toString (printer.up <> printer.sep)) + (\p' -> + foldDirOrFile + (\d -> go p' <> printSegment printer d <> NES.toString printer.sep) + (\f -> go p' <> printSegment printer f)) diff --git a/src/Data/Path/Pathy/Printer.purs b/src/Data/Path/Pathy/Printer.purs new file mode 100644 index 0000000..92acd62 --- /dev/null +++ b/src/Data/Path/Pathy/Printer.purs @@ -0,0 +1,103 @@ +module Data.Path.Pathy.Printer where + +import Prelude + +import Data.Foldable (fold) +import Data.Maybe (Maybe, maybe) +import Data.Monoid (class Monoid) +import Data.Newtype (class Newtype, un, unwrap) +import Data.String as Str +import Data.String.NonEmpty (NonEmptyString) +import Data.String.NonEmpty as NES +import Partial.Unsafe (unsafePartial) + +-- | A `Printer` defines options for printing paths. +-- | +-- | - `root` is a function used to construct the initial segment of paths. +-- | - `current` is a representation of the current directory. +-- | - `up` is a representation of going up to the parent directory. +-- | - `sep` is the string to separate path segments by. +-- | - `escaper` specified how to deal with printing reserved names and +-- | characters. +type Printer = + { root :: Maybe NonEmptyString -> String + , current :: NonEmptyString + , up :: NonEmptyString + , sep :: NonEmptyString + , escaper :: Escaper + } + +-- | A printer for POSIX paths. +posixPrinter :: Printer +posixPrinter = + { root: maybe "/" (\name -> "/" <> NES.toString name) + , current: NES.singleton '.' + , up: NES.singleton '.' <> NES.singleton '.' + , sep: NES.singleton '/' + , escaper: posixEscaper + } + +-- | A printer for Windows paths. +windowsPrinter :: Printer +windowsPrinter = + { root: maybe "\\" (\drive -> NES.toString drive <> ":") + , current: NES.singleton '.' + , up: NES.singleton '.' <> NES.singleton '.' + , sep: NES.singleton '\\' + , escaper: windowsEscaper + } + +-- | Prints a name as a `String` using the escaper from the specified printer. +printSegment :: forall name. Newtype name NonEmptyString => Printer -> name -> String +printSegment printer = NES.toString <<< un Escaper printer.escaper <<< unwrap + +-- | An `Escaper` encodes segments or characters which have reserved meaning +-- | within names in a path. +newtype Escaper = Escaper (NonEmptyString -> NonEmptyString) + +derive instance newtypeEscaper :: Newtype Escaper _ + +instance semigroupEscaper :: Semigroup Escaper where + append (Escaper e1) (Escaper e2) = Escaper (e1 <<< e2) + +instance monoidEscaper :: Monoid Escaper where + mempty = Escaper id + +-- | An escaper that replaces all `'/'` characters in a name with `'-'`s. +slashEscaper :: Escaper +slashEscaper = Escaper (NES.replaceAll slash dash) + where + slash = Str.Pattern "/" + dash = NES.NonEmptyReplacement (NES.singleton '-') + +-- | An escaper that replaces names `"."` and `".."` with `"$dot"` and +-- | `"$dot$dot"`. +dotEscaper :: Escaper +dotEscaper = Escaper \s -> case NES.toString s of + ".." -> unsafePartial NES.unsafeFromString "$dot$dot" + "." -> unsafePartial NES.unsafeFromString "$dot" + _ -> s + +-- | An escaper that removes all slashes, converts ".." into "$dot$dot", and +-- | converts "." into "$dot". +posixEscaper :: Escaper +posixEscaper = slashEscaper <> dotEscaper + +-- | An escaper that attempts to encode all reserved names and characters for +-- | windows-style paths. +windowsEscaper :: Escaper +windowsEscaper = badCharEscaper <> badNameEscaper <> dotEscaper + where + badCharEscaper = + fold $ map + (\c -> Escaper (NES.replaceAll (Str.Pattern (Str.singleton c)) dash)) + ['\\', '/', ':', '*', '?', '"', '<', '>', '|'] + badNameEscaper = + fold $ map + (\n -> Escaper (NES.replaceAll (Str.Pattern n) (NES.NonEmptyReplacement (NES.cons '$' n)))) + ["CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"] + dash = NES.NonEmptyReplacement (NES.singleton '-') + +-- | Prints a name as a `String` using the specified escaper. +escape :: forall name. Newtype name NonEmptyString => Escaper -> name -> String +escape r = NES.toString <<< un Escaper r <<< unwrap diff --git a/src/Data/Path/Pathy/Sandboxed.purs b/src/Data/Path/Pathy/Sandboxed.purs new file mode 100644 index 0000000..5372307 --- /dev/null +++ b/src/Data/Path/Pathy/Sandboxed.purs @@ -0,0 +1,93 @@ +module Data.Path.Pathy.Sandboxed + ( SandboxedPath + , sandbox + , sandboxAny + , sandboxRoot + , unsandbox + , printPath + , printPath' + ) where + +import Prelude + +import Data.Maybe (Maybe(..), fromMaybe) +import Data.Path.Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Printer, Rel, canonicalize, onRelOrAbs, posixPrinter, relativeTo, rootDir, unsafePrintPath', ()) + +-- | The type for paths that have been sandboxed. +data SandboxedPath a b = SandboxedPath (Path Abs Dir) (Path a b) + +derive instance eqSandboxedPath :: (IsRelOrAbs a, IsDirOrFile b) => Eq (SandboxedPath a b) +derive instance ordSandboxedPath :: (IsRelOrAbs a, IsDirOrFile b) => Ord (SandboxedPath a b) +instance showSandboxedPath :: (IsRelOrAbs a, IsDirOrFile b) => Show (SandboxedPath a b) where + show (SandboxedPath root path) = "(SandboxedPath " <> show root <> " " <> show path <> ")" + +-- | Attempts to sandbox a path relative to an absolute directory ("sandbox +-- | root"). If the `Path a b` escapes the sandbox root `Nothing` will be +-- | returned. +sandbox + :: forall a b + . IsRelOrAbs a + => IsDirOrFile b + => Path Abs Dir + -> Path a b + -> Maybe (SandboxedPath a b) +sandbox root = onRelOrAbs goRel goAbs + where + goRel :: (Path Rel b -> Path a b) -> Path Rel b -> Maybe (SandboxedPath a b) + goRel coe p = + case (root p) `relativeTo` root of + Nothing -> Nothing + Just _ -> Just (SandboxedPath root (coe p)) + goAbs :: (Path Abs b -> Path a b) -> Path Abs b -> Maybe (SandboxedPath a b) + goAbs coe p = + case p `relativeTo` root of + Nothing -> Nothing + Just _ -> Just (SandboxedPath root (coe p)) + +-- | Sandboxes any path (a to `/`. +-- | +-- | This should only be used for situations where a path is already constrained +-- | within a system so that access to `/` is safe - for instance, in URIs. +sandboxAny + :: forall a b + . IsRelOrAbs a + => IsDirOrFile b + => Path a b + -> SandboxedPath a b +sandboxAny p = + fromMaybe (SandboxedPath rootDir (canonicalize p)) (sandbox rootDir p) + +-- | Returns the location a `SandboxedPath` was sandboxed to. +sandboxRoot :: forall a b. SandboxedPath a b -> Path Abs Dir +sandboxRoot (SandboxedPath root _) = root + +-- | Extracts the original path from a `SandboxedPath`. +unsandbox :: forall a b. SandboxedPath a b -> Path a b +unsandbox (SandboxedPath _ p) = p + +-- | Prints a `SandboxedPath` into its canonical `String` representation. The +-- | printed path will always be absolute, as this is the only way to ensure +-- | the path is safely referring to the intended location. +printPath + :: forall a b + . IsRelOrAbs a + => IsDirOrFile b + => SandboxedPath a b + -> String +printPath = printPath' posixPrinter + +-- | Prints a `SandboxedPath` into its canonical `String` representation, using +-- | the specified printer. The printed path will always be absolute, as this +-- | is the only way to ensure the path is safely referring to the intended +-- | location. +printPath' + :: forall a b + . IsRelOrAbs a + => IsDirOrFile b + => Printer + -> SandboxedPath a b + -> String +printPath' r (SandboxedPath root p) = + unsafePrintPath' + r + (onRelOrAbs (\_ p' -> canonicalize (root p')) (flip const) p) diff --git a/test/Main.purs b/test/Main.purs index dd72902..3325d3d 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -3,17 +3,17 @@ module Test.Main where import Prelude import Control.Monad.Eff (Eff) -import Control.Monad.Eff.Console (CONSOLE, info, infoShow) -import Data.Either (either) +import Control.Monad.Eff.Console (CONSOLE, info) +import Control.Monad.Eff.Exception (EXCEPTION, throw) import Data.Foldable (foldl) -import Data.Maybe (Maybe(..), fromJust) -import Data.Path.Pathy (class SplitDirOrFile, class SplitRelOrAbs, Abs, Dir, File, Path, Rel, Sandboxed, Unsandboxed, canonicalize, currentDir, depth, dir, dirOrFile, dropExtension, file, parentDir, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, relOrAbs, renameFile', rootDir, sandbox, unsafePrintPath, unsandbox, (<..>), (<.>), ()) +import Data.Maybe (Maybe(..)) +import Data.Path.Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Path, Rel, canonicalize, currentDir, depth, dir, dropExtension, file, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, renameFile', rootDir, unsafePrintPath, (<..>), (<.>), ()) import Data.Path.Pathy.Gen as PG +import Data.Path.Pathy.Sandboxed (printPath, sandbox, unsandbox) import Data.String as Str import Data.String.NonEmpty (NonEmptyString) import Data.Symbol (SProxy(..)) import Data.Symbol (class IsSymbol, reflectSymbol) as Symbol -import Partial.Unsafe (unsafePartial) import Test.QuickCheck as QC import Test.QuickCheck.Gen as Gen import Test.QuickCheck.Laws.Data as Laws.Data @@ -22,22 +22,22 @@ import Type.Data.Symbol (class Equals) as Symbol import Type.Proxy (Proxy(..)) import Unsafe.Coerce (unsafeCoerce) -test :: forall a eff. Show a => Eq a => String -> a -> a -> Eff (console :: CONSOLE | eff) Unit +test :: forall a eff. Show a => Eq a => String -> a -> a -> Eff (console :: CONSOLE, exception :: EXCEPTION | eff) Unit test name actual expected= do - infoShow $ "Test: " <> name + info $ "Test: " <> name if expected == actual - then infoShow $ "Passed: " <> (show expected) - else infoShow $ "Failed: Expected " <> (show expected) <> " but found " <> (show actual) + then info $ "Passed: " <> (show expected) + else throw $ "Failed:\n Expected: " <> (show expected) <> "\n Actual: " <> (show actual) -test' :: forall a b s eff. SplitRelOrAbs a => SplitDirOrFile b => String -> Path a b s -> String -> Eff (console :: CONSOLE | eff) Unit +test' :: forall a b eff. IsRelOrAbs a => IsDirOrFile b => String -> Path a b -> String -> Eff (console :: CONSOLE, exception :: EXCEPTION | eff) Unit test' n p s = test n (unsafePrintPath p) s -newtype ArbPath = ArbPath (Path Abs File Sandboxed) +newtype ArbPath = ArbPath (Path Abs File) derive newtype instance eqArbPath :: Eq ArbPath derive newtype instance ordArbPath :: Ord ArbPath -runArbPath ∷ ArbPath → (Path Abs File Sandboxed) +runArbPath ∷ ArbPath → (Path Abs File) runArbPath (ArbPath p) = p instance arbitraryArbPath ∷ QC.Arbitrary ArbPath where @@ -45,7 +45,7 @@ instance arbitraryArbPath ∷ QC.Arbitrary ArbPath where numDirs ← Gen.chooseInt 1 10 dirs ← map dir <$> Gen.vectorOf numDirs pathPart filename ← file <$> pathPart - pure $ ArbPath $ rootDir foldl (flip ()) filename (dirs ∷ Array (Path Rel Dir Sandboxed)) + pure $ ArbPath $ rootDir foldl (flip ()) filename (dirs ∷ Array (Path Rel Dir)) pathPart ∷ Gen.Gen NonEmptyString pathPart = asNonEmptyString <$> Gen.suchThat QC.arbitrary (not <<< Str.null) @@ -53,15 +53,15 @@ pathPart = asNonEmptyString <$> Gen.suchThat QC.arbitrary (not <<< Str.null) asNonEmptyString :: String -> NonEmptyString asNonEmptyString = unsafeCoerce -dirFoo :: Path Rel Dir Sandboxed +dirFoo :: Path Rel Dir dirFoo = dir (reflectNonEmpty $ SProxy :: SProxy "foo") -dirBar :: Path Rel Dir Sandboxed +dirBar :: Path Rel Dir dirBar = dir (reflectNonEmpty $ SProxy :: SProxy "bar") -parsePrintCheck :: forall a b. SplitRelOrAbs a => SplitDirOrFile b => Path a b Sandboxed -> Maybe (Path a b Unsandboxed) -> QC.Result +parsePrintCheck :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> Maybe (Path a b) -> QC.Result parsePrintCheck input parsed = - if parsed == Just (unsandbox input) + if parsed == Just input then QC.Success else QC.Failed $ "`parse (print path) != Just path` for path: `" <> show input <> "` which was re-parsed into `" <> show parsed <> "`" @@ -86,30 +86,11 @@ parsePrintRelFilePath = PG.genRelFilePath <#> \path -> main :: QC.QC () Unit main = do - -- let uppp = (parentDir currentDir) - -- info $ unsafePrintPath uppp - -- let doown = currentDir dirFoo dirFoo dirFoo dirFoo - -- info $ unsafePrintPath doown - -- let up = sandbox doown uppp - -- info $ maybe "oops" unsafePrintPath up - -- let pathA = currentDir dirFoo dirFoo - -- let pathB = currentDir dirFoo dirFoo dirFoo - -- let x = unsafePartial $ fromJust $ sandbox pathA pathB - -- info $ unsafePrintPath x - -- info $ maybe "" (show <<< bimap unsafePrintPath runName) (peel x) - -- info "========" - -- let appRoot = currentDir dirFoo dirFoo - -- let userData = appRoot currentDir - -- info $ maybe "" (show <<< bimap unsafePrintPath runName) (peel userData) - -- info "========" - -- let x = unsafePartial $ fromJust $ sandbox pathB pathA - -- info $ unsafePrintPath x - -- info $ maybe "" (show <<< bimap unsafePrintPath runName) (peel x) - info "checking `parse <<< print` for `AbsDir`" *> QC.quickCheck parsePrintAbsDirPath info "checking `parse <<< print` for `AbsFile`" *> QC.quickCheck parsePrintAbsFilePath info "checking `parse <<< print` for `RelDir`" *> QC.quickCheck parsePrintRelDirPath info "checking `parse <<< print` for `RelFile`" *> QC.quickCheck parsePrintRelFilePath + -- Should not compile: -- test -- "() - file in dir" @@ -128,12 +109,6 @@ main = do -- (printPath (currentDir rootDir)) -- "/" - -- Should not compile: - -- test - -- "printPath -- cannot print unsandboxed" - -- (printPath (parentDir currentDir)) - -- "./../" - test' "() - two directories" (dirFoo dirBar) "./foo/bar/" @@ -155,15 +130,15 @@ main = do "./image.png" test' "printPath - ./../" - (parentDir currentDir) + (parentOf currentDir) "./../" test' "() - ./../foo/" - (parentDir currentDir unsandbox (dirFoo)) + (parentOf currentDir dirFoo) "./../foo/" - test' "parentDir - ./../foo/../" - ((parentDir currentDir unsandbox (dirFoo)) (parentDir currentDir)) + test' "parentOf - ./../foo/../" + ((parentOf currentDir dirFoo) (parentOf currentDir)) "./../foo/../" test' "(<..>) - ./../" @@ -179,52 +154,74 @@ main = do "./../foo/../" test' "canonicalize - 1 down, 1 up" - (canonicalize $ parentDir $ dirFoo) + (canonicalize $ parentOf $ dirFoo) "./" test' "canonicalize - 2 down, 2 up" - (canonicalize (parentDir (parentDir (dirFoo dirBar)))) + (canonicalize (parentOf (parentOf (dirFoo dirBar)))) "./" test "renameFile - single level deep" (renameFile' dropExtension (file (reflectNonEmpty $ SProxy :: SProxy "image.png"))) + (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image") + + test "sandbox - fail when relative path lies outside sandbox (above)" + (sandbox (rootDir dirBar) (parentOf currentDir)) + Nothing + + test "sandbox - fail when relative path lies outside sandbox (neigbouring)" + (sandbox (rootDir dirBar) (parentOf currentDir dirFoo)) + Nothing + + test "sandbox - fail when absolute path lies outside sandbox" + (sandbox (rootDir dirBar) (rootDir dirFoo dirBar)) + Nothing + + test "sandbox - succeed when relative path goes above sandbox but returns to it" + (unsandbox <$> sandbox (rootDir dirBar) (parentOf currentDir dirBar)) + (Just (parentOf currentDir dirBar)) - (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image") + test "sandbox - succeed when absolute path lies inside sandbox" + (unsandbox <$> sandbox (rootDir dirBar) (rootDir dirBar dirFoo)) + (Just (rootDir dirBar dirFoo)) - test' "sandbox - sandbox absolute dir to one level higher" - (unsafePartial $ fromJust $ sandbox (rootDir dirFoo) (rootDir dirFoo dirBar)) - "./bar/" + test "sandbox - print relative path that goes above sandbox but returns to it" + (printPath <$> sandbox (rootDir dirBar) (parentOf currentDir dirBar)) + (Just "/bar/") + + test "sandbox - print absolute path that lies inside sandbox" + (printPath <$> sandbox (rootDir dirBar) (rootDir dirBar dirFoo)) + (Just "/bar/foo/") test "depth - negative" - (depth (parentDir $ parentDir $ parentDir $ currentDir)) (-3) + (depth (parentOf $ parentOf $ parentOf $ currentDir)) (-3) test "parseRelFile - image.png" (parseRelFile "image.png") - (Just $ unsandbox $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") + (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") test "parseRelFile - ./image.png" (parseRelFile "./image.png") - (Just $ unsandbox $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") + (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") test "parseRelFile - foo/image.png" (parseRelFile "foo/image.png") - (Just $ unsandbox $ dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) + (Just $ dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) test "parseRelFile - ../foo/image.png" (parseRelFile "../foo/image.png") - (Just $ unsandbox $ currentDir <..> dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) + (Just $ currentDir <..> dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) test "parseAbsFile - /image.png" (parseAbsFile "/image.png") - (Just $ unsandbox $ rootDir file (reflectNonEmpty $ SProxy :: SProxy "image.png")) + (Just $ rootDir file (reflectNonEmpty $ SProxy :: SProxy "image.png")) test "parseAbsFile - /foo/image.png" (parseAbsFile "/foo/image.png") - (Just $ unsandbox $ rootDir dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) + (Just $ rootDir dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) test "parseRelDir - empty string" (parseRelDir "") - -- (Just $ currentDir) Nothing test "parseRelDir - ./../" @@ -233,34 +230,32 @@ main = do test "parseRelDir - foo/" (parseRelDir "foo/") - (Just $ unsandbox dirFoo) + (Just dirFoo) test "parseRelDir - foo/bar" (parseRelDir "foo/bar/") - (Just $ unsandbox $ dirFoo dirBar) + (Just $ dirFoo dirBar) test "parseRelDir - ./foo/bar" (parseRelDir "./foo/bar/") - (Just $ unsandbox $ dirFoo dirBar) + (Just $ dirFoo dirBar) test "parseAbsDir - /" (parseAbsDir "/") - (Just $ unsandbox rootDir) + (Just $ rootDir) test "parseAbsDir - /foo/" (parseAbsDir "/foo/") - (Just $ unsandbox $ rootDir dirFoo) + (Just $ rootDir dirFoo) test "parseAbsDir - /foo/bar" (parseAbsDir "/foo/bar/") - (Just $ unsandbox $ rootDir dirFoo dirBar) - + (Just $ rootDir dirFoo dirBar) + info "Checking typeclass laws..." Laws.Data.checkEq (Proxy :: Proxy ArbPath) Laws.Data.checkOrd (Proxy :: Proxy ArbPath) - - class IsSymbolNonEmpty sym where reflectNonEmpty :: SProxy sym -> NonEmptyString @@ -269,28 +264,3 @@ instance isSymbolNonEmpty :: (Symbol.IsSymbol s, Symbol.Equals s "" Symbol.False where asNonEmpty :: String -> NonEmptyString asNonEmpty = unsafeCoerce - - --- | Determines if the path refers to a directory. -maybeDir :: forall a b s. SplitDirOrFile b => Path a b s -> Maybe (Path a Dir s) -maybeDir p = either Just (const Nothing) (dirOrFile p) - --- | Determines if the path refers to a file. -maybeFile :: forall a b s. SplitDirOrFile b => Path a b s -> Maybe (Path a File s) -maybeFile p = either (const Nothing) Just (dirOrFile p) - --- | Determines if the path is relatively specified. -maybeRel :: forall a b s. SplitRelOrAbs a => Path a b s -> Maybe (Path Rel b s) -maybeRel p = either Just (const Nothing) (relOrAbs p) - --- | Determines if the path is absolutely specified. -maybeAbs :: forall a b s. SplitRelOrAbs a => Path a b s -> Maybe (Path Abs b s) -maybeAbs p = either (const Nothing) Just (relOrAbs p) - --- | Determines if this path is absolutely located. -isAbsolute :: forall a b s. SplitRelOrAbs a => Path a b s -> Boolean -isAbsolute p = either (const false) (const true) (relOrAbs p) - --- | Determines if this path is relatively located. -isRelative :: forall a b s. SplitRelOrAbs a => Path a b s -> Boolean -isRelative p= either (const true) (const false) (relOrAbs p) From 9882545865e61d33e8c45776820a00dddc243a38 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Tue, 20 Feb 2018 21:26:33 +0000 Subject: [PATCH 29/59] Reorganisation and make `relativeTo` total --- bower.json | 3 +- src/Data/Path/Pathy.purs | 596 ++++++++++------------------- src/Data/Path/Pathy/Gen.purs | 3 +- src/Data/Path/Pathy/Name.purs | 46 +++ src/Data/Path/Pathy/Parser.purs | 88 +++++ src/Data/Path/Pathy/Phantom.purs | 73 ++++ src/Data/Path/Pathy/Sandboxed.purs | 58 +-- test/Main.purs | 225 ++++++----- 8 files changed, 586 insertions(+), 506 deletions(-) create mode 100644 src/Data/Path/Pathy/Name.purs create mode 100644 src/Data/Path/Pathy/Parser.purs create mode 100644 src/Data/Path/Pathy/Phantom.purs diff --git a/bower.json b/bower.json index b132dcb..2c405bb 100644 --- a/bower.json +++ b/bower.json @@ -27,7 +27,6 @@ "purescript-unsafe-coerce": "^3.0.0" }, "devDependencies": { - "purescript-quickcheck": "^4.0.0", - "purescript-quickcheck-laws": "^3.0.0" + "purescript-quickcheck": "^4.0.0" } } diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index d2465a4..88e0db8 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -1,109 +1,57 @@ module Data.Path.Pathy - ( Abs - , AbsDir - , ParseError - , AbsFile - , AbsPath - , Dir - , Name(..) - , File - , Path + ( Path , AnyPath - , Rel + , RelPath + , AbsPath , RelDir + , AbsDir , RelFile - , RelPath - , appendPath - , () - , setExtension - , (<.>) - , parentAppend - , (<..>) - , canonicalize - , changeExtension + , AbsFile + , rootDir , currentDir - , depth , dir , dir' - , dirName - , dropExtension - , extension , file , file' - , fileName - , pathName - , identicalPath , parentOf + , extendPath + , appendPath, () + , parentAppend, (<..>) + , canonicalize , foldPath , peel - , parsePath - , parseAbsDir - , parseAbsFile - , parseRelDir - , parseRelFile - , class IsRelOrAbs - , onRelOrAbs - , foldRelOrAbs - , class IsDirOrFile - , onDirOrFile - , foldDirOrFile - , refine - , relativeTo - , renameDir - , renameFile - , renameFile' - , rootDir - , DirPathView - , FilePathView - , viewDir - , viewFile , peelFile + , name + , fileName + , rename + , renameTraverse + , setExtension, (<.>) + , relativeTo + , refine , unsafePrintPath , unsafePrintPath' , module Exports + , module Data.Path.Pathy.Name + , module Data.Path.Pathy.Phantom ) where import Prelude -import Data.Array (drop, dropEnd, length) -import Data.Bifunctor (bimap, lmap) import Data.Either (Either) -import Data.FoldableWithIndex (foldlWithIndex) import Data.Identity (Identity(..)) -import Data.List (List(..), reverse) -import Data.Maybe (Maybe(..)) -import Data.Newtype (class Newtype, un) +import Data.Maybe (Maybe(..), fromMaybe) +import Data.Newtype (un) +import Data.Path.Pathy.Name (Name(..)) as Exports +import Data.Path.Pathy.Name (Name(..), alterExtension, extension) +import Data.Path.Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel, foldDirOrFile, foldRelOrAbs, onDirOrFile, onRelOrAbs, kind DirOrFile, kind RelOrAbs) import Data.Path.Pathy.Printer (Printer, posixPrinter, printSegment) import Data.Path.Pathy.Printer (Printer, posixPrinter, windowsPrinter) as Exports -import Data.String as S import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES -import Data.Traversable (traverse) -import Data.Tuple (Tuple(..), snd) +import Data.Tuple (Tuple(..)) import Partial.Unsafe (unsafeCrashWith) import Unsafe.Coerce (unsafeCoerce) -foreign import kind RelOrAbs - -foreign import kind DirOrFile - --- | The (phantom) type of relative paths. -foreign import data Rel :: RelOrAbs - --- | The (phantom) type of absolute paths. -foreign import data Abs :: RelOrAbs - --- | The (phantom) type of files. -foreign import data File :: DirOrFile - --- | The (phantom) type of directories. -foreign import data Dir :: DirOrFile - --- | A newtype around a file name. -newtype Name (n :: DirOrFile) = Name NonEmptyString - -derive instance newtypeName :: Newtype (Name n) _ - -- | A type that describes a Path. All flavors of paths are described by this -- | type, whether they are absolute or relative paths and whether they -- | refer to files or directories. @@ -126,12 +74,22 @@ data Path (a :: RelOrAbs) (b :: DirOrFile) | ParentOf (Path a Dir) | In (Path a Dir) (Name b) --- | A type describing a file whose location is given relative to some other, --- | unspecified directory (referred to as the "current directory"). -type RelFile = Path Rel File +derive instance eqPath :: Eq (Path a b) +derive instance ordPath :: Ord (Path a b) --- | A type describing a file whose location is absolutely specified. -type AbsFile = Path Abs File +instance showPathRelDir :: (IsRelOrAbs a, IsDirOrFile b) => Show (Path a b) where + show p@Init = foldRelOrAbs (const "currentDir") (const "rootDir") p + show (ParentOf p) = "(parentOf " <> show p <> ")" + show (In p n) = "(" <> show p <> " " <> foldDirOrFile (("dir " <> _) <<< show) (("file " <> _) <<< show) n <> ")" + +-- | A type describing a file or directory path. +type AnyPath a = Either (Path a Dir) (Path a File) + +-- | A type describing a relative file or directory path. +type RelPath = AnyPath Rel + +-- | A type describing an absolute file or directory path. +type AbsPath = AnyPath Abs -- | A type describing a directory whose location is given relative to some -- | other, unspecified directory (referred to as the "current directory"). @@ -140,48 +98,21 @@ type RelDir = Path Rel Dir -- | A type describing a directory whose location is absolutely specified. type AbsDir = Path Abs Dir --- | A type describing a file or directory path. -type AnyPath b = Either (Path b Dir) (Path b File) +-- | A type describing a file whose location is given relative to some other, +-- | unspecified directory (referred to as the "current directory"). +type RelFile = Path Rel File --- | A type describing a relative file or directory path. -type RelPath = AnyPath Rel +-- | A type describing a file whose location is absolutely specified. +type AbsFile = Path Abs File --- | A type describing an absolute file or directory path. -type AbsPath = AnyPath Abs +-- | The root directory, which can be used to define absolutely-located resources. +rootDir :: Path Abs Dir +rootDir = Init -class IsDirOrFile (b :: DirOrFile) where - onDirOrFile - :: forall f r - . ((f Dir -> f b) -> f Dir -> r) - -> ((f File -> f b) -> f File -> r) - -> f b - -> r - -foldDirOrFile :: forall f b r. IsDirOrFile b => (f Dir -> r) -> (f File -> r) -> f b -> r -foldDirOrFile f g = onDirOrFile (const f) (const g) - -instance relIsDirOrFile :: IsDirOrFile Dir where onDirOrFile f _ = f id -instance absIsDirOrFile :: IsDirOrFile File where onDirOrFile _ f = f id - -class IsRelOrAbs (a :: RelOrAbs) where - onRelOrAbs - :: forall f b r - . ((f Rel b -> f a b) -> f Rel b -> r) - -> ((f Abs b -> f a b) -> f Abs b -> r) - -> f a b - -> r - -instance relIsRelOrAbs :: IsRelOrAbs Rel where onRelOrAbs f _ = f id -instance absIsRelOrAbs :: IsRelOrAbs Abs where onRelOrAbs _ f = f id - -foldRelOrAbs - :: forall f a b r - . IsRelOrAbs a - => (f Rel b -> r) - -> (f Abs b -> r) - -> f a b - -> r -foldRelOrAbs f g = onRelOrAbs (const f) (const g) +-- | The "current directory", which can be used to define relatively-located +-- | resources. +currentDir :: Path Rel Dir +currentDir = Init -- | Creates a path which points to a relative file of the specified name. file :: NonEmptyString -> Path Rel File @@ -189,51 +120,7 @@ file = file' <<< Name -- | Creates a path which points to a relative file of the specified name. file' :: Name File -> Path Rel File -file' = In Init - --- | Retrieves the name of a file path. -fileName :: forall a. Path a File -> Name File -fileName (In _ f) = f -fileName _ = unsafeCrashWith - """Hit unrechable path in Data.Pathy.fileName - Based on type of this function, it must be called with a Path such that In node is a root node - The reason might be a bug in this module or incorrect unsafeCoerce in it's use site - """ - --- | Retrieves the extension of a file name. -extension :: Name File -> String -extension (Name f) = - let s = NES.toString f - in case S.lastIndexOf (S.Pattern ".") s of - Just x -> S.drop (x + 1) s - Nothing -> "" - --- | Drops the extension on a file name. -dropExtension :: Name File -> Maybe (Name File) -dropExtension (Name n) = - let - s = NES.toString n - in case S.lastIndexOf (S.Pattern ".") s of - Just x -> map Name $ NES.fromString $ S.take x s - Nothing -> Just (Name n) - -changeExtension :: (String -> String) -> Name File -> Maybe (Name File) -changeExtension f nm = - update (f $ extension nm) (dropExtension nm) - where - update ext' name = case NES.fromString ext' of - Nothing -> name - Just ext -> Just $ _updateExt ext name - -changeExtension' :: (String -> NonEmptyString) -> Name File -> Name File -changeExtension' f nm = - _updateExt (f $ extension nm) (dropExtension nm) - - -_updateExt :: NonEmptyString -> Maybe (Name File) -> Name File -_updateExt ext = case _ of - Just (Name n) -> Name $ n `NES.appendString` "." <> ext - Nothing -> Name ext +file' = In currentDir -- | Creates a path which points to a relative directory of the specified name. dir :: NonEmptyString -> Path Rel Dir @@ -241,45 +128,70 @@ dir = dir' <<< Name -- | Creates a path which points to a relative directory of the specified name. dir' :: Name Dir -> Path Rel Dir -dir' = In Init +dir' = In currentDir --- | Retrieves the name of a directory path. Not all paths have such a name, --- | for example, the root or current directory. -dirName :: forall a. Path a Dir -> Maybe (Name Dir) -dirName p = case canonicalize p of - In _ d -> Just d - _ -> Nothing +-- | Creates a path that points to the parent directory of the specified path. +parentOf :: forall a. Path a Dir -> Path a Dir +parentOf p = ParentOf p -pathName :: forall b. AnyPath b -> Either (Maybe (Name Dir)) (Name File) -pathName = bimap dirName fileName +-- | Extends a path with a file or directory under the current path. +extendPath :: forall a b. Path a Dir -> Name b -> Path a b +extendPath p = In p --- | Given a directory path, appends either a file or directory to the path. +-- | Given a directory path, appends a relative path to extend the original +-- | path. appendPath :: forall a b. Path a Dir -> Path Rel b -> Path a b -appendPath Init Init = Init -appendPath (ParentOf p) Init = ParentOf (p Init) -appendPath (In p (Name d)) Init = In (p Init) (Name d) -appendPath p1 (ParentOf p2) = ParentOf (p1 p2) -appendPath p1 (In p2 n2) = In (p1 p2) n2 +appendPath = case _, _ of + Init, Init -> Init + ParentOf p, Init -> ParentOf (p Init) + In p (Name d), Init -> In (p Init) (Name d) + p1, ParentOf p2 -> ParentOf (p1 p2) + p1, In p2 n -> In (p1 p2) n infixl 6 appendPath as --- | Sets the extension of the file to the specified extension. +-- | Ascends into the parent of the specified directory, then descends into +-- | the specified path. -- | -- | ```purescript --- | file "image" <.> "png" +-- | canonicalize (rootDir dir "foo" <..> dir "bar") = rootDir dir "bar" -- | ``` -setExtension :: forall a s. Path a File -> NonEmptyString -> Path a File -setExtension p ext = renameFile (changeExtension' $ const ext) p - -infixl 6 setExtension as <.> - --- | Ascends into the parent of the specified directory, then descends into --- | the specified path. parentAppend :: forall a b. Path a Dir -> Path Rel b -> Path a b parentAppend d p = parentOf d p infixl 6 parentAppend as <..> +-- | Canonicalizes a path, by reducing things in the form `/x/../` to just +-- | `/x/`. Paths like `/../` will be normalized to `/`. +canonicalize :: forall a b. IsRelOrAbs a => Path a b -> Path a b +canonicalize p = fromMaybe p (go p) + where + go :: forall b'. Path a b' -> Maybe (Path a b') + go = case _ of + Init -> + Nothing + p@(ParentOf Init) -> + foldRelOrAbs (const Nothing) (const (Just Init)) p + ParentOf (In p _) -> + -- Coercion is safe as `ParentOf` can only appear where `b' ~ Dir` + Just $ (unsafeCoerce :: Path a Dir -> Path a b') (canonicalize p) + ParentOf p -> + case go p of + Just p' -> Just $ canonicalize (ParentOf p') + Nothing -> Nothing + In p f -> + flip In f <$> go p + +-- | A fold over `Path`s. Since `Path` has private constructors, this allows for +-- | functions to be written over its constructors, similar to a total pattern +-- | match. +-- | +-- | - The first argument is the value to return for the `currentDir`/`rootDir` +-- | at the base of the path. +-- | - The second argument is a function for handling a step into the parent +-- | directory of the path it receives (eliminates `parentOf`). +-- | - The third argument is a function representing a file or directory within +-- | the directory of the path it receives (eliminates `extendPath`). foldPath :: forall a b r . r @@ -293,222 +205,134 @@ foldPath r f g = case _ of In d n -> g d n -- | Peels off the last directory and the terminal file or directory name --- | from the path. Returns `Nothing` if there is no such pair (for example, --- | if the last path segment is root directory, current directory, or parent --- | directory). -peel - :: forall a b - . Path a b - -> Maybe (Tuple (Path a Dir) (Name b)) -peel Init = Nothing -peel p@(ParentOf _) = case canonicalize' p of - Tuple true p' -> peel p' - _ -> Nothing -peel (In p n) = Just $ Tuple p n - --- | Returns the depth of the path. This may be negative in some cases, e.g. --- | `./../../../` has depth `-3`. -depth :: forall a b. Path a b -> Int -depth Init = 0 -depth (ParentOf p) = depth p - 1 -depth (In p _) = depth p + 1 - --- | Creates a path that points to the parent directory of the specified path. -parentOf :: forall a. Path a Dir -> Path a Dir -parentOf = ParentOf - -unsafeCoerceType :: forall a b b'. Path a b -> Path a b' -unsafeCoerceType = unsafeCoerce - - -- | The "current directory", which can be used to define relatively-located resources. -currentDir :: Path Rel Dir -currentDir = Init +-- | from the path. Returns `Nothing` if the path is `rootDir` / `currentDir` or +-- | some `parentOf p`. +peel :: forall a b. Path a b -> Maybe (Tuple (Path a Dir) (Name b)) +peel = foldPath Nothing (const Nothing) (\p n -> Just (Tuple p n)) + +-- | Peels off the last director and terminal file from a path. Unlike the +-- | general `peel` function this is guaranteed to return a result, as `File` +-- | paths are known to have a name. +peelFile :: forall a. Path a File -> Tuple (Path a Dir) (Name File) +peelFile = case _ of + Init -> unsafeCrashWith "`Init` in Pathy.peelFile (this should be impossible)" + ParentOf _ -> unsafeCrashWith "`ParentOf` in Pathy.peelFile (this should be impossible)" + In dir name -> Tuple dir name + +-- | Retrieves the name of the terminal segment in a path. Returns `Nothing` if +-- | the path is `rootDir` / `currentDir` or some `parentOf p`. +name :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> Maybe (Name b) +name = foldPath Nothing (const Nothing) (const Just) + +-- | Retrieves the name of a file path. Unlike the general `name` function, +-- | this is guaranteed to return a result, as `File` paths are known to have a +-- | name. +fileName :: forall a. Path a File -> Name File +fileName = case _ of + Init -> unsafeCrashWith "`Init` in Pathy.fileName (this should be impossible)" + ParentOf _ -> unsafeCrashWith "`ParentOf` in Pathy.fileName (this should be impossible)" + In _ name -> name + +-- | Attempts to rename the terminal segment of a path. If the path is +-- | `rootDir` / `currentDir` or some `parentOf p` this will have no effect. +rename :: forall a b. (Name b -> Name b) -> Path a b -> Path a b +rename f = un Identity <<< renameTraverse (pure <<< f) + +-- | Attempts to rename the terminal segment of a path using a function that +-- | returns the result in some `Applicative`. If the path is `rootDir` / +-- | `currentDir` or some `parentOf p` this will have no effect. +renameTraverse + :: forall f a b + . Applicative f + => (Name b -> f (Name b)) + -> Path a b + -> f (Path a b) +renameTraverse f = case _ of + In p name -> In p <$> f name + p -> pure p --- | The root directory, which can be used to define absolutely-located resources. -rootDir :: Path Abs Dir -rootDir = Init +-- | Sets the extension of a name. +-- | +-- | ```purescript +-- | file "image" <.> "png" +-- | ``` +setExtension :: forall a b. Path a b -> NonEmptyString -> Path a b +setExtension p ext = rename (alterExtension (const (Just ext))) p --- | Renames a file path. -renameFile :: forall a. (Name File -> Name File) -> Path a File -> Path a File -renameFile f = un Identity <<< renameFile' (pure <<< f) - -renameFile' :: forall f a s. Applicative f => (Name File -> f (Name File)) -> Path a File -> f (Path a File) -renameFile' f (In p f0) = In p <$> f f0 -renameFile' _ p = pure p - --- | Renames a directory path. Note: This is a simple rename of the terminal --- | directory name, not a "move". -renameDir :: forall a. (Name Dir -> Name Dir) -> Path a Dir -> Path a Dir -renameDir f (In p d) = In p (f d) -renameDir _ p = p - --- | Canonicalizes a path, by reducing things in the form `/x/../` to just `/x/`. -canonicalize :: forall a b. Path a b -> Path a b -canonicalize = snd <<< canonicalize' - --- | Canonicalizes a path and returns information on whether or not it actually changed. -canonicalize' :: forall a b. Path a b -> Tuple Boolean (Path a b) -canonicalize' Init = Tuple false Init -canonicalize' (ParentOf (In p f)) = Tuple true (unsafeCoerceType $ snd $ canonicalize' p) -canonicalize' (ParentOf p) = case canonicalize' p of - Tuple changed p' -> - let p'' = ParentOf p' - in if changed then canonicalize' p'' else Tuple changed p'' -canonicalize' (In p f) = flip In f <$> canonicalize' p - --- | Determines if two paths have the exact same representation. Note that --- | two paths may represent the same path even if they have different --- | representations! -identicalPath - :: forall a a' b b' - . IsRelOrAbs a - => IsRelOrAbs a' - => IsDirOrFile b - => IsDirOrFile b' - => Path a b -> Path a' b' -> Boolean -identicalPath p1 p2 = show p1 == show p2 +infixl 6 setExtension as <.> --- | Makes one path relative to another reference path, if possible, otherwise --- | returns `Nothing`. --- | --- | Note there are some cases this function cannot handle. -relativeTo :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> Path a Dir -> Maybe (Path Rel b) -relativeTo p1 p2 = relativeTo' (canonicalize p1) (canonicalize p2) +-- | Makes one path relative to another reference path. +relativeTo + :: forall b + . IsDirOrFile b + => Path Abs b + -> Path Abs Dir + -> Path Rel b +relativeTo p rp = + case canonicalize p, canonicalize rp of + Init, Init -> Init + p', Init -> + -- Coercion is safe as if the reference path is `/` the result is just + -- whatever the input was, but with a phantom type indicating it is + -- relative - the actual representation would be the same if it were + -- reconstructed with `Rel`. + (unsafeCoerce :: Path Abs b -> Path Rel b) p' + p'@Init, rp' -> + -- Coercion is safe as `Init` can only exist when `b ~ Dir` + (unsafeCoerce :: Path Rel Dir -> Path Rel b) $ step (ParentOf currentDir) Init rp' + In p' name, In rp' rname + | p' == rp' && foldDirOrFile (_ == rname) (const false) name -> Init + | otherwise -> In (step (ParentOf currentDir) p' rp') name + _, _ -> + unsafeCrashWith "`ParentOf` in Pathy.relativeTo [1] (this should be impossible)" where - relativeTo' :: forall b'. IsDirOrFile b' => Path a b' -> Path a Dir -> Maybe (Path Rel b') - relativeTo' Init Init = pure Init - relativeTo' cp1 cp2 - | identicalPath cp1 cp2 = pure Init - | otherwise = do - Tuple cp1Path name <- peel cp1 - rel <- relativeTo' cp1Path cp2 - pure $ rel In Init name + step :: Path Rel Dir -> Path Abs Dir -> Path Abs Dir -> Path Rel Dir + step acc = case _, _ of + Init, Init -> acc + Init, In rp' _ -> step (ParentOf acc) Init rp' + In p' name, Init -> In (step acc p' Init) name + In p' name, rp'@(In rp'' rname) + | p' == rp'' && name == rname -> acc + | otherwise -> In (step (ParentOf currentDir) p' rp') name + _, _ -> + unsafeCrashWith "`ParentOf` in Pathy.relativeTo [2] (this should be impossible)" -- | Refines path segments but does not change anything else. -refine :: forall a b. IsDirOrFile b => (Name File -> Name File) -> (Name Dir -> Name Dir) -> Path a b -> Path a b +refine + :: forall a b + . IsDirOrFile b + => (Name File -> Name File) + -> (Name Dir -> Name Dir) + -> Path a b + -> Path a b refine f d = go where go :: forall a' b'. IsDirOrFile b' => Path a' b' -> Path a' b' go Init = Init go (ParentOf p) = ParentOf (go p) - go (In p name) = In (go p) (onDirOrFile (\p -> p <<< d) (\p -> p <<< f) name) - -type ParseError = Unit - --- | Parses a canonical `String` representation of a path into a `Path` value. --- | Note that in order to be unambiguous, trailing directories should be --- | marked with a trailing slash character (`'/'`). -parsePath - :: forall z - . (RelDir -> z) - -> (AbsDir -> z) - -> (RelFile -> z) - -> (AbsFile -> z) - -> (ParseError -> z) - -> String - -> z -parsePath rd ad rf af err "" = err unit -parsePath rd ad rf af err "/" = ad Init -parsePath rd ad rf af err p = - let - isAbs = S.take 1 p == "/" - isFile = S.takeRight 1 p /= "/" - segsRaw = S.split (S.Pattern "/") p - segsDropped = - -- drop last or/and first empty segment(s) if any - case isAbs, isFile of - true, true -> drop 1 $ segsRaw - true, false -> drop 1 $ dropEnd 1 segsRaw - false, true -> segsRaw - false, false -> dropEnd 1 segsRaw - last = length segsDropped - 1 - folder :: forall a b. IsDirOrFile b => Int -> Path a b -> NonEmptyString -> Path a b - folder idx base seg = - if NES.toString seg == "." then - base - else if NES.toString seg == ".." then - ParentOf $ unsafeCoerceType base - else In (unsafeCoerceType base) (Name seg) - in - case traverse NES.fromString segsDropped of - Nothing -> err unit - Just segs -> case isAbs, isFile of - true, true -> af $ foldlWithIndex folder Init segs - true, false -> ad $ foldlWithIndex folder Init segs - false, true -> rf $ foldlWithIndex folder Init segs - false, false -> rd $ foldlWithIndex folder Init segs - --- | Attempts to parse a relative file from a string. -parseRelFile :: String -> Maybe (RelFile) -parseRelFile = parsePath (const Nothing) (const Nothing) Just (const Nothing) (const Nothing) - --- | Attempts to parse an absolute file from a string. -parseAbsFile :: String -> Maybe (AbsFile) -parseAbsFile = parsePath (const Nothing) (const Nothing) (const Nothing) Just (const Nothing) - --- | Attempts to parse a relative directory from a string. -parseRelDir :: String -> Maybe (RelDir) -parseRelDir = parsePath Just (const Nothing) (const Nothing) (const Nothing) (const Nothing) - --- | Attempts to parse an absolute directory from a string. -parseAbsDir :: String -> Maybe (AbsDir) -parseAbsDir = parsePath (const Nothing) Just (const Nothing) (const Nothing) (const Nothing) - -instance showPathRelDir :: (IsRelOrAbs a, IsDirOrFile b) => Show (Path a b) where - show p@Init = foldRelOrAbs (const "currentDir") (const "rootDir") p - show (ParentOf p) = "(parentOf " <> show p <> ")" - show (In p n) = "(" <> show p <> " " <> foldDirOrFile (("dir " <> _) <<< show) (("file " <> _) <<< show) n <> ")" - -instance eqPath :: (IsRelOrAbs a, IsDirOrFile b) => Eq (Path a b) where - eq p1 p2 = canonicalize p1 `identicalPath` canonicalize p2 - -instance ordPath :: (IsRelOrAbs a, IsDirOrFile b) => Ord (Path a b) where - compare p1 p2 = go (canonicalize p1) (canonicalize p2) - where - go Init Init = EQ - go Init _ = LT - go _ Init = GT - go (ParentOf p1') (ParentOf p2') = compare p1' p2' - go (ParentOf _) _ = LT - go _ (ParentOf _) = GT - go (In p1' d1) (In p2' d2) = compare p1' p2' <> compare d1 d2 - -instance showName :: Show (Name a) where - show (Name name) = "(Name " <> show name <> ")" - -derive instance eqName :: Eq (Name a) -derive instance ordName :: Ord (Name a) - -type DirPathView = List (Name Dir) -type FilePathView = Tuple DirPathView (Name File) - -viewDir :: forall a. Path a Dir -> DirPathView -viewDir = reverse <<< go - where - go = case _ of - Init -> Nil - ParentOf _ -> unsafeCrashWith "Impossible, ParentOf can't be in path" - In d n -> Cons n (go d) - -viewFile :: forall a. Path a File -> FilePathView -viewFile = peelFile >>> lmap viewDir - -peelFile :: forall a. Path a File -> Tuple (Path a Dir) (Name File) -peelFile = case _ of - Init -> unsafeCrashWith "Impossible, Init can't be in File path" - ParentOf _ -> unsafeCrashWith "Impossible, ParentOf can't be in File path" - In d n -> Tuple d n + go (In p name) = In (go p) (onDirOrFile (_ <<< d) (_ <<< f) name) -- | Prints a path exactly as-is. This is unsafe as the path may refer to a -- | location it should not have access to. Path printing should almost always -- | be performed with a `SandboxedPath`. -unsafePrintPath :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> String +unsafePrintPath + :: forall a b + . IsRelOrAbs a + => IsDirOrFile b + => Path a b + -> String unsafePrintPath = unsafePrintPath' posixPrinter -- | Prints a path exactly as-is using the specified `Printer`. This is unsafe -- | as the path may refer to a location it should not have access to. Path -- | printing should almost always be performed with a `SandboxedPath`. -unsafePrintPath' :: forall a b. IsRelOrAbs a => IsDirOrFile b => Printer -> Path a b -> String +unsafePrintPath' + :: forall a b + . IsRelOrAbs a + => IsDirOrFile b + => Printer + -> Path a b + -> String unsafePrintPath' printer p = go p where go :: forall b'. IsDirOrFile b' => Path a b' -> String diff --git a/src/Data/Path/Pathy/Gen.purs b/src/Data/Path/Pathy/Gen.purs index e7eecef..3b90175 100644 --- a/src/Data/Path/Pathy/Gen.purs +++ b/src/Data/Path/Pathy/Gen.purs @@ -5,7 +5,7 @@ module Data.Path.Pathy.Gen , genRelDirPath , genRelFilePath , genRelAnyPath - )where + ) where import Prelude @@ -27,7 +27,6 @@ genName = cons <$> genChar <*> SG.genString genChar where genChar = Gen.oneOf $ CG.genDigitChar :| [CG.genAlpha] - genAbsDirPath :: forall m. MonadGen m => MonadRec m => m AbsDir genAbsDirPath = Gen.sized \size → do newSize ← Gen.chooseInt 0 size diff --git a/src/Data/Path/Pathy/Name.purs b/src/Data/Path/Pathy/Name.purs new file mode 100644 index 0000000..0ea5856 --- /dev/null +++ b/src/Data/Path/Pathy/Name.purs @@ -0,0 +1,46 @@ +module Data.Path.Pathy.Name where + +import Prelude + +import Data.Maybe (Maybe(..), maybe) +import Data.Newtype (class Newtype) +import Data.Path.Pathy.Phantom (kind DirOrFile) +import Data.String as S +import Data.String.NonEmpty (NonEmptyString) +import Data.String.NonEmpty as NES + +-- | A type used for both directory and file names, indexed by `DirOrFile`. +newtype Name (n :: DirOrFile) = Name NonEmptyString + +derive instance newtypeName :: Newtype (Name n) _ +derive newtype instance eqName :: Eq (Name a) +derive newtype instance ordName :: Ord (Name a) + +instance showName :: Show (Name a) where + show (Name name) = "(Name " <> show name <> ")" + +-- | Retrieves the extension of a name. +extension :: forall n. Name n -> Maybe NonEmptyString +extension (Name name) = + flip NES.drop name <<< (_ + 1) =<< NES.lastIndexOf (S.Pattern ".") name + +-- | Alters an extension of a name. This allows extensions to be added, removed, +-- | or modified. +alterExtension + :: forall n + . (Maybe NonEmptyString -> Maybe NonEmptyString) + -> Name n + -> Name n +alterExtension f (Name name) = + case NES.lastIndexOf (S.Pattern ".") name of + Nothing -> extend name Nothing + Just i -> + case NES.splitAt i name of + Just { before: Just n, after } -> extend n (NES.drop 1 =<< after) + _ -> extend name Nothing + where + extend name' ext = + maybe + (Name name') + (\ext' -> Name (name' <> NES.singleton '.' <> ext')) + (f ext) diff --git a/src/Data/Path/Pathy/Parser.purs b/src/Data/Path/Pathy/Parser.purs new file mode 100644 index 0000000..41c7525 --- /dev/null +++ b/src/Data/Path/Pathy/Parser.purs @@ -0,0 +1,88 @@ +module Data.Path.Pathy.Parser + ( parsePosixPath + , parsePosixRelFile + , parsePosixAbsFile + , parsePosixRelDir + , parsePosixAbsDir + ) where + +import Prelude + +import Data.Array as A +import Data.Either (Either(..), either) +import Data.List (List(..), (:)) +import Data.List as L +import Data.Maybe (Maybe(..)) +import Data.Path.Pathy (AbsDir, AbsFile, Path, RelDir, RelFile, currentDir, extendPath, parentOf, rootDir) +import Data.Path.Pathy.Name (Name(..)) +import Data.Path.Pathy.Phantom (Dir) +import Data.String as S +import Data.String.NonEmpty (NonEmptyString) +import Data.String.NonEmpty as NES + +-- | Parses a canonical `String` representation of a path into a `Path` value. +-- | Note that in order to be unambiguous, trailing directories should be +-- | marked with a trailing slash character (`'/'`). +parsePosixPath + :: forall z + . (RelDir -> z) + -> (AbsDir -> z) + -> (RelFile -> z) + -> (AbsFile -> z) + -> z + -> String + -> z +parsePosixPath relDir absDir relFile absFile z p + | p == "" = z + | p == "/" = absDir rootDir + | otherwise = + let + isAbs = S.take 1 p == "/" + isFile = S.takeRight 1 p /= "/" + segs = L.fromFoldable $ A.reverse $ A.mapMaybe NES.fromString $ S.split (S.Pattern "/") p + in + case isAbs, isFile of + true, true -> buildPath z rootDir (either (const z) absFile) segs + true, false -> buildPath z rootDir (either absDir absDir) segs + false, true -> buildPath z currentDir (either (const z) relFile) segs + false, false -> buildPath z currentDir (either relDir relDir) segs + + +-- | Attempts to parse a relative file from a string. +parsePosixRelFile :: String -> Maybe RelFile +parsePosixRelFile = parsePosixPath (const Nothing) (const Nothing) Just (const Nothing) Nothing + +-- | Attempts to parse an absolute file from a string. +parsePosixAbsFile :: String -> Maybe AbsFile +parsePosixAbsFile = parsePosixPath (const Nothing) (const Nothing) (const Nothing) Just Nothing + +-- | Attempts to parse a relative directory from a string. +parsePosixRelDir :: String -> Maybe RelDir +parsePosixRelDir = parsePosixPath Just (const Nothing) (const Nothing) (const Nothing) Nothing + +-- | Attempts to parse an absolute directory from a string. +parsePosixAbsDir :: String -> Maybe AbsDir +parsePosixAbsDir = parsePosixPath (const Nothing) Just (const Nothing) (const Nothing) Nothing + +buildPath + :: forall z a b + . z + -> Path a Dir + -> (Either (Path a Dir) (Path a b) -> z) + -> List NonEmptyString + -> z +buildPath z init k segs = + case segs of + Nil -> z + name : segs' + | NES.toString name == ".." -> k $ Left (parentOf (go segs')) + | NES.toString name == "." -> k $ Left (go segs') + | otherwise -> k $ Right (extendPath (go segs') (Name name)) + where + go :: List NonEmptyString -> Path a Dir + go = case _ of + Nil -> init + name : segs' + | NES.toString name == ".." -> parentOf (go segs') + | NES.toString name == "." -> go segs' + | otherwise -> extendPath (go segs') (Name name) diff --git a/src/Data/Path/Pathy/Phantom.purs b/src/Data/Path/Pathy/Phantom.purs new file mode 100644 index 0000000..fd6f1ba --- /dev/null +++ b/src/Data/Path/Pathy/Phantom.purs @@ -0,0 +1,73 @@ +module Data.Path.Pathy.Phantom where + +import Prelude + +-- | The kind for the relative/absolute phantom type. +foreign import kind RelOrAbs + +-- | The phantom type of relative paths. +foreign import data Rel :: RelOrAbs + +-- | The phantom type of absolute paths. +foreign import data Abs :: RelOrAbs + +-- | A class that enables writing operations that abstract over `RelOrAbs`. +-- | +-- | The provided `onRelOrAbs` function folds over a value indexed by +-- | `RelOrAbs` to produce a new result, passing proof/coercion functions to +-- | allow the inner functions to unify their return types if remapping. +class IsRelOrAbs (a :: RelOrAbs) where + onRelOrAbs + :: forall f b r + . ((f Rel b -> f a b) -> f Rel b -> r) + -> ((f Abs b -> f a b) -> f Abs b -> r) + -> f a b + -> r + +instance relIsRelOrAbs :: IsRelOrAbs Rel where onRelOrAbs f _ = f id +instance absIsRelOrAbs :: IsRelOrAbs Abs where onRelOrAbs _ f = f id + +-- | Folds over a value that uses `RelOrAbs` to produce a new result. +foldRelOrAbs + :: forall f a b r + . IsRelOrAbs a + => (f Rel b -> r) + -> (f Abs b -> r) + -> f a b + -> r +foldRelOrAbs f g = onRelOrAbs (const f) (const g) + +-- | The kind for the directory/file phantom type. +foreign import kind DirOrFile + +-- | The phantom type of directories. +foreign import data Dir :: DirOrFile + +-- | The phantom type of files. +foreign import data File :: DirOrFile + +-- | A class that enables writing operations that abstract over `DirOrFile`. +-- | +-- | The provided `onDirOrFile` function folds over a value indexed by +-- | `DirOrFile` to produce a new result, passing proof/coercion functions to +-- | allow the inner functions to unify their return types if remapping. +class IsDirOrFile (b :: DirOrFile) where + onDirOrFile + :: forall f r + . ((f Dir -> f b) -> f Dir -> r) + -> ((f File -> f b) -> f File -> r) + -> f b + -> r + +instance isDirOrFileDir :: IsDirOrFile Dir where onDirOrFile f _ = f id +instance isDirOrFileFile :: IsDirOrFile File where onDirOrFile _ f = f id + +-- | Folds over a value that uses `DirOrFile` to produce a new result. +foldDirOrFile + :: forall f b r + . IsDirOrFile b + => (f Dir -> r) + -> (f File -> r) + -> f b + -> r +foldDirOrFile f g = onDirOrFile (const f) (const g) diff --git a/src/Data/Path/Pathy/Sandboxed.purs b/src/Data/Path/Pathy/Sandboxed.purs index 5372307..b3e8715 100644 --- a/src/Data/Path/Pathy/Sandboxed.purs +++ b/src/Data/Path/Pathy/Sandboxed.purs @@ -1,7 +1,7 @@ module Data.Path.Pathy.Sandboxed ( SandboxedPath - , sandbox - , sandboxAny + -- , sandbox + -- , sandboxAny , sandboxRoot , unsandbox , printPath @@ -24,38 +24,38 @@ instance showSandboxedPath :: (IsRelOrAbs a, IsDirOrFile b) => Show (SandboxedPa -- | Attempts to sandbox a path relative to an absolute directory ("sandbox -- | root"). If the `Path a b` escapes the sandbox root `Nothing` will be -- | returned. -sandbox - :: forall a b - . IsRelOrAbs a - => IsDirOrFile b - => Path Abs Dir - -> Path a b - -> Maybe (SandboxedPath a b) -sandbox root = onRelOrAbs goRel goAbs - where - goRel :: (Path Rel b -> Path a b) -> Path Rel b -> Maybe (SandboxedPath a b) - goRel coe p = - case (root p) `relativeTo` root of - Nothing -> Nothing - Just _ -> Just (SandboxedPath root (coe p)) - goAbs :: (Path Abs b -> Path a b) -> Path Abs b -> Maybe (SandboxedPath a b) - goAbs coe p = - case p `relativeTo` root of - Nothing -> Nothing - Just _ -> Just (SandboxedPath root (coe p)) +-- sandbox +-- :: forall a b +-- . IsRelOrAbs a +-- => IsDirOrFile b +-- => Path Abs Dir +-- -> Path a b +-- -> Maybe (SandboxedPath a b) +-- sandbox root = onRelOrAbs goRel goAbs +-- where +-- goRel :: (Path Rel b -> Path a b) -> Path Rel b -> Maybe (SandboxedPath a b) +-- goRel coe p = +-- case (root p) `relativeTo` root of +-- Nothing -> Nothing +-- Just _ -> Just (SandboxedPath root (coe p)) +-- goAbs :: (Path Abs b -> Path a b) -> Path Abs b -> Maybe (SandboxedPath a b) +-- goAbs coe p = +-- case p `relativeTo` root of +-- Nothing -> Nothing +-- Just _ -> Just (SandboxedPath root (coe p)) -- | Sandboxes any path (a to `/`. -- | -- | This should only be used for situations where a path is already constrained -- | within a system so that access to `/` is safe - for instance, in URIs. -sandboxAny - :: forall a b - . IsRelOrAbs a - => IsDirOrFile b - => Path a b - -> SandboxedPath a b -sandboxAny p = - fromMaybe (SandboxedPath rootDir (canonicalize p)) (sandbox rootDir p) +-- sandboxAny +-- :: forall a b +-- . IsRelOrAbs a +-- => IsDirOrFile b +-- => Path a b +-- -> SandboxedPath a b +-- sandboxAny p = +-- fromMaybe (SandboxedPath rootDir (canonicalize p)) (sandbox rootDir p) -- | Returns the location a `SandboxedPath` was sandboxed to. sandboxRoot :: forall a b. SandboxedPath a b -> Path Abs Dir diff --git a/test/Main.purs b/test/Main.purs index 3325d3d..75fb0a8 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -5,21 +5,18 @@ import Prelude import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, info) import Control.Monad.Eff.Exception (EXCEPTION, throw) -import Data.Foldable (foldl) import Data.Maybe (Maybe(..)) -import Data.Path.Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Path, Rel, canonicalize, currentDir, depth, dir, dropExtension, file, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, renameFile', rootDir, unsafePrintPath, (<..>), (<.>), ()) +import Data.Path.Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Rel, alterExtension, canonicalize, currentDir, dir, file, parentOf, relativeTo, rename, rootDir, unsafePrintPath, (<..>), (<.>), ()) import Data.Path.Pathy.Gen as PG -import Data.Path.Pathy.Sandboxed (printPath, sandbox, unsandbox) +import Data.Path.Pathy.Parser (parsePosixAbsDir, parsePosixAbsFile, parsePosixRelDir, parsePosixRelFile) import Data.String as Str import Data.String.NonEmpty (NonEmptyString) -import Data.Symbol (SProxy(..)) import Data.Symbol (class IsSymbol, reflectSymbol) as Symbol +import Data.Symbol (SProxy(..)) import Test.QuickCheck as QC import Test.QuickCheck.Gen as Gen -import Test.QuickCheck.Laws.Data as Laws.Data import Type.Data.Boolean (False) as Symbol import Type.Data.Symbol (class Equals) as Symbol -import Type.Proxy (Proxy(..)) import Unsafe.Coerce (unsafeCoerce) test :: forall a eff. Show a => Eq a => String -> a -> a -> Eff (console :: CONSOLE, exception :: EXCEPTION | eff) Unit @@ -32,21 +29,6 @@ test name actual expected= do test' :: forall a b eff. IsRelOrAbs a => IsDirOrFile b => String -> Path a b -> String -> Eff (console :: CONSOLE, exception :: EXCEPTION | eff) Unit test' n p s = test n (unsafePrintPath p) s -newtype ArbPath = ArbPath (Path Abs File) - -derive newtype instance eqArbPath :: Eq ArbPath -derive newtype instance ordArbPath :: Ord ArbPath - -runArbPath ∷ ArbPath → (Path Abs File) -runArbPath (ArbPath p) = p - -instance arbitraryArbPath ∷ QC.Arbitrary ArbPath where - arbitrary = do - numDirs ← Gen.chooseInt 1 10 - dirs ← map dir <$> Gen.vectorOf numDirs pathPart - filename ← file <$> pathPart - pure $ ArbPath $ rootDir foldl (flip ()) filename (dirs ∷ Array (Path Rel Dir)) - pathPart ∷ Gen.Gen NonEmptyString pathPart = asNonEmptyString <$> Gen.suchThat QC.arbitrary (not <<< Str.null) where @@ -59,6 +41,9 @@ dirFoo = dir (reflectNonEmpty $ SProxy :: SProxy "foo") dirBar :: Path Rel Dir dirBar = dir (reflectNonEmpty $ SProxy :: SProxy "bar") +dirBaz :: Path Rel Dir +dirBaz = dir (reflectNonEmpty $ SProxy :: SProxy "baz") + parsePrintCheck :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> Maybe (Path a b) -> QC.Result parsePrintCheck input parsed = if parsed == Just input @@ -70,19 +55,38 @@ parsePrintCheck input parsed = parsePrintAbsDirPath :: Gen.Gen QC.Result parsePrintAbsDirPath = PG.genAbsDirPath <#> \path -> - parsePrintCheck path (parseAbsDir $ unsafePrintPath path) + parsePrintCheck path (parsePosixAbsDir $ unsafePrintPath path) parsePrintAbsFilePath :: Gen.Gen QC.Result parsePrintAbsFilePath = PG.genAbsFilePath <#> \path -> - parsePrintCheck path (parseAbsFile $ unsafePrintPath path) + parsePrintCheck path (parsePosixAbsFile $ unsafePrintPath path) parsePrintRelDirPath :: Gen.Gen QC.Result parsePrintRelDirPath = PG.genRelDirPath <#> \path -> - parsePrintCheck path (parseRelDir $ unsafePrintPath path) + parsePrintCheck path (parsePosixRelDir $ unsafePrintPath path) parsePrintRelFilePath :: Gen.Gen QC.Result parsePrintRelFilePath = PG.genRelFilePath <#> \path -> - parsePrintCheck path (parseRelFile $ unsafePrintPath path) + parsePrintCheck path (parsePosixRelFile $ unsafePrintPath path) + +checkRelative :: forall b. IsDirOrFile b => Gen.Gen (Path Abs b) -> Gen.Gen QC.Result +checkRelative gen = do + p1 <- gen + p2 <- PG.genAbsDirPath + let cp1 = canonicalize p1 + let cp2 = canonicalize p2 + let rel = cp1 `relativeTo` cp2 + let cp1' = canonicalize (cp2 rel) + pure + if cp1 == cp1' + then QC.Success + else + QC.Failed + $ "`relativeTo` property did not hold:" + <> "\n\tcp1: " <> unsafePrintPath cp1 + <> "\n\tcp2: " <> unsafePrintPath cp2 + <> "\n\trel: " <> unsafePrintPath rel + <> "\n\tcp1': " <> unsafePrintPath cp1' main :: QC.QC () Unit main = do @@ -90,6 +94,8 @@ main = do info "checking `parse <<< print` for `AbsFile`" *> QC.quickCheck parsePrintAbsFilePath info "checking `parse <<< print` for `RelDir`" *> QC.quickCheck parsePrintRelDirPath info "checking `parse <<< print` for `RelFile`" *> QC.quickCheck parsePrintRelFilePath + info "checking `relativeTo` for `AbsDir`" *> QC.quickCheck (checkRelative PG.genAbsDirPath) + info "checking `relativeTo` for `AbsFile`" *> QC.quickCheck (checkRelative PG.genAbsFilePath) -- Should not compile: -- test @@ -161,101 +167,146 @@ main = do (canonicalize (parentOf (parentOf (dirFoo dirBar)))) "./" - test "renameFile - single level deep" - (renameFile' dropExtension (file (reflectNonEmpty $ SProxy :: SProxy "image.png"))) - (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image") - - test "sandbox - fail when relative path lies outside sandbox (above)" - (sandbox (rootDir dirBar) (parentOf currentDir)) - Nothing - - test "sandbox - fail when relative path lies outside sandbox (neigbouring)" - (sandbox (rootDir dirBar) (parentOf currentDir dirFoo)) - Nothing - - test "sandbox - fail when absolute path lies outside sandbox" - (sandbox (rootDir dirBar) (rootDir dirFoo dirBar)) - Nothing - - test "sandbox - succeed when relative path goes above sandbox but returns to it" - (unsandbox <$> sandbox (rootDir dirBar) (parentOf currentDir dirBar)) - (Just (parentOf currentDir dirBar)) + test' "canonicalize - 2 up from root" + (canonicalize (parentOf (parentOf rootDir))) + "/" - test "sandbox - succeed when absolute path lies inside sandbox" - (unsandbox <$> sandbox (rootDir dirBar) (rootDir dirBar dirFoo)) - (Just (rootDir dirBar dirFoo)) + test "canonicalize /foo/../bar/ = /bar" + (canonicalize (rootDir dirFoo <..> dirBar)) + (rootDir dirBar) - test "sandbox - print relative path that goes above sandbox but returns to it" - (printPath <$> sandbox (rootDir dirBar) (parentOf currentDir dirBar)) - (Just "/bar/") + test "relativeTo rootDir rootDir = currentDir" + (relativeTo rootDir rootDir) + (currentDir) - test "sandbox - print absolute path that lies inside sandbox" - (printPath <$> sandbox (rootDir dirBar) (rootDir dirBar dirFoo)) - (Just "/bar/foo/") + test' "(rootDir dirFoo) `relativeTo` rootDir = currentDir dirFoo" + ((rootDir dirFoo) `relativeTo` rootDir) + "./foo/" - test "depth - negative" - (depth (parentOf $ parentOf $ parentOf $ currentDir)) (-3) + test' "(rootDir dirFoo) `relativeTo` (rootDir dirBar) = currentDir <..> dirFoo" + ((rootDir dirFoo) `relativeTo` (rootDir dirBar)) + "./../foo/" - test "parseRelFile - image.png" - (parseRelFile "image.png") + test' "(rootDir dirBar) `relativeTo` (rootDir dirFoo) = ./../bar/" + ((rootDir dirBar) `relativeTo` (rootDir dirFoo)) + "./../bar/" + + test' "(rootDir dirBar) `relativeTo` (rootDir dirFoo dirFoo) = ./../../bar/" + ((rootDir dirBar) `relativeTo` (rootDir dirFoo dirFoo)) + "./../../bar/" + + test' "(rootDir dirBar) `relativeTo` (rootDir dirFoo dirFoo dirFoo) = ./../../../bar/" + ((rootDir dirBar) `relativeTo` (rootDir dirFoo dirFoo dirFoo)) + "./../../../bar/" + + test' "(rootDir dirBar dirBar) `relativeTo` (rootDir dirFoo) = ./../bar/bar/" + ((rootDir dirBar dirBar) `relativeTo` (rootDir dirFoo)) + "./../bar/bar/" + + test' "(rootDir dirBar dirBar) `relativeTo` (rootDir dirFoo dirFoo) = ./../../bar/bar/" + ((rootDir dirBar dirBar) `relativeTo` (rootDir dirFoo dirFoo)) + "./../../bar/bar/" + + test' "(rootDir dirBar dirFoo dirFoo) `relativeTo` (rootDir dirFoo dirFoo dirFoo) = ./../../../bar/foo/foo" + ((rootDir dirBar dirFoo dirFoo) `relativeTo` (rootDir dirFoo dirFoo dirFoo)) + "./../../../bar/foo/foo/" + + test' "(rootDir dirFoo dirBar dirBaz) `relativeTo` rootDir = ./foo/bar/baz/" + ((rootDir dirFoo dirBar dirBaz) `relativeTo` rootDir) + "./foo/bar/baz/" + + test' "(rootDir dirFoo dirBar dirBaz) `relativeTo` (rootDir dirFoo) = ./../foo/bar/baz/" + ((rootDir dirFoo dirBar dirBaz) `relativeTo` (rootDir dirFoo)) + "./../foo/bar/baz/" + + test "rename - single level deep" + (rename (alterExtension (const Nothing)) (file (reflectNonEmpty $ SProxy :: SProxy "image.png"))) + (file $ reflectNonEmpty $ SProxy :: SProxy "image") + + -- test "sandbox - fail when relative path lies outside sandbox (above)" + -- (sandbox (rootDir dirBar) (parentOf currentDir)) + -- Nothing + -- + -- test "sandbox - fail when relative path lies outside sandbox (neigbouring)" + -- (sandbox (rootDir dirBar) (parentOf currentDir dirFoo)) + -- Nothing + -- + -- test "sandbox - fail when absolute path lies outside sandbox" + -- (sandbox (rootDir dirBar) (rootDir dirFoo dirBar)) + -- Nothing + -- + -- test "sandbox - succeed when relative path goes above sandbox but returns to it" + -- (unsandbox <$> sandbox (rootDir dirBar) (parentOf currentDir dirBar)) + -- (Just (parentOf currentDir dirBar)) + -- + -- test "sandbox - succeed when absolute path lies inside sandbox" + -- (unsandbox <$> sandbox (rootDir dirBar) (rootDir dirBar dirFoo)) + -- (Just (rootDir dirBar dirFoo)) + -- + -- test "sandbox - print relative path that goes above sandbox but returns to it" + -- (printPath <$> sandbox (rootDir dirBar) (parentOf currentDir dirBar)) + -- (Just "/bar/") + -- + -- test "sandbox - print absolute path that lies inside sandbox" + -- (printPath <$> sandbox (rootDir dirBar) (rootDir dirBar dirFoo)) + -- (Just "/bar/foo/") + + test "parsePosixRelFile - image.png" + (parsePosixRelFile "image.png") (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") - test "parseRelFile - ./image.png" - (parseRelFile "./image.png") + test "parsePosixRelFile - ./image.png" + (parsePosixRelFile "./image.png") (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") - test "parseRelFile - foo/image.png" - (parseRelFile "foo/image.png") + test "parsePosixRelFile - foo/image.png" + (parsePosixRelFile "foo/image.png") (Just $ dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) - test "parseRelFile - ../foo/image.png" - (parseRelFile "../foo/image.png") + test "parsePosixRelFile - ../foo/image.png" + (parsePosixRelFile "../foo/image.png") (Just $ currentDir <..> dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) - test "parseAbsFile - /image.png" - (parseAbsFile "/image.png") + test "parsePosixAbsFile - /image.png" + (parsePosixAbsFile "/image.png") (Just $ rootDir file (reflectNonEmpty $ SProxy :: SProxy "image.png")) - test "parseAbsFile - /foo/image.png" - (parseAbsFile "/foo/image.png") + test "parsePosixAbsFile - /foo/image.png" + (parsePosixAbsFile "/foo/image.png") (Just $ rootDir dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) - test "parseRelDir - empty string" - (parseRelDir "") + test "parsePosixRelDir - empty string" + (parsePosixRelDir "") Nothing - test "parseRelDir - ./../" - (parseRelDir "./../") + test "parsePosixRelDir - ./../" + (parsePosixRelDir "./../") (Just $ currentDir <..> currentDir) - test "parseRelDir - foo/" - (parseRelDir "foo/") + test "parsePosixRelDir - foo/" + (parsePosixRelDir "foo/") (Just dirFoo) - test "parseRelDir - foo/bar" - (parseRelDir "foo/bar/") + test "parsePosixRelDir - foo/bar" + (parsePosixRelDir "foo/bar/") (Just $ dirFoo dirBar) - test "parseRelDir - ./foo/bar" - (parseRelDir "./foo/bar/") + test "parsePosixRelDir - ./foo/bar" + (parsePosixRelDir "./foo/bar/") (Just $ dirFoo dirBar) - test "parseAbsDir - /" - (parseAbsDir "/") + test "parsePosixAbsDir - /" + (parsePosixAbsDir "/") (Just $ rootDir) - test "parseAbsDir - /foo/" - (parseAbsDir "/foo/") + test "parsePosixAbsDir - /foo/" + (parsePosixAbsDir "/foo/") (Just $ rootDir dirFoo) - test "parseAbsDir - /foo/bar" - (parseAbsDir "/foo/bar/") + test "parsePosixAbsDir - /foo/bar" + (parsePosixAbsDir "/foo/bar/") (Just $ rootDir dirFoo dirBar) - info "Checking typeclass laws..." - Laws.Data.checkEq (Proxy :: Proxy ArbPath) - Laws.Data.checkOrd (Proxy :: Proxy ArbPath) - class IsSymbolNonEmpty sym where reflectNonEmpty :: SProxy sym -> NonEmptyString From cf8e92f54026caff04cda8ac0da577f2ab68c7bd Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Wed, 21 Feb 2018 11:20:32 +0000 Subject: [PATCH 30/59] Fix some issues in `relativeTo` and restore sandboxing again --- src/Data/Path/Pathy.purs | 45 +++++++------------ src/Data/Path/Pathy/Sandboxed.purs | 54 +++++++++-------------- test/Main.purs | 71 ++++++++++++++++++------------ 3 files changed, 80 insertions(+), 90 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 88e0db8..5e9976e 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -261,41 +261,28 @@ setExtension p ext = rename (alterExtension (const (Just ext))) p infixl 6 setExtension as <.> --- | Makes one path relative to another reference path. -relativeTo - :: forall b - . IsDirOrFile b - => Path Abs b - -> Path Abs Dir - -> Path Rel b -relativeTo p rp = - case canonicalize p, canonicalize rp of - Init, Init -> Init - p', Init -> - -- Coercion is safe as if the reference path is `/` the result is just - -- whatever the input was, but with a phantom type indicating it is - -- relative - the actual representation would be the same if it were - -- reconstructed with `Rel`. - (unsafeCoerce :: Path Abs b -> Path Rel b) p' - p'@Init, rp' -> - -- Coercion is safe as `Init` can only exist when `b ~ Dir` - (unsafeCoerce :: Path Rel Dir -> Path Rel b) $ step (ParentOf currentDir) Init rp' - In p' name, In rp' rname - | p' == rp' && foldDirOrFile (_ == rname) (const false) name -> Init - | otherwise -> In (step (ParentOf currentDir) p' rp') name - _, _ -> - unsafeCrashWith "`ParentOf` in Pathy.relativeTo [1] (this should be impossible)" +-- | Makes a path relative to a reference path. +relativeTo :: forall b. Path Abs b -> Path Abs Dir -> Path Rel b +relativeTo p rp = coeB $ step Init (canonicalize (coeD p)) (canonicalize rp) where step :: Path Rel Dir -> Path Abs Dir -> Path Abs Dir -> Path Rel Dir step acc = case _, _ of - Init, Init -> acc + p', rp' | p' == rp' -> acc Init, In rp' _ -> step (ParentOf acc) Init rp' In p' name, Init -> In (step acc p' Init) name - In p' name, rp'@(In rp'' rname) - | p' == rp'' && name == rname -> acc - | otherwise -> In (step (ParentOf currentDir) p' rp') name + In p' name, rp' + | p' == rp' -> In acc name + | otherwise -> In (step acc p' rp') name _, _ -> - unsafeCrashWith "`ParentOf` in Pathy.relativeTo [2] (this should be impossible)" + unsafeCrashWith "`ParentOf` in Pathy.relativeTo (this should be impossible)" + -- Unfortunately we can't avoid some coercions in this function unless + -- we actually write two different verions of `relativeTo` for file/dir + -- paths. Since the actual data representation is same either way the + -- coercions are safe. + coeD :: forall a. Path a b -> Path a Dir + coeD = unsafeCoerce + coeB :: forall a. Path a Dir -> Path a b + coeB = unsafeCoerce -- | Refines path segments but does not change anything else. refine diff --git a/src/Data/Path/Pathy/Sandboxed.purs b/src/Data/Path/Pathy/Sandboxed.purs index b3e8715..d791180 100644 --- a/src/Data/Path/Pathy/Sandboxed.purs +++ b/src/Data/Path/Pathy/Sandboxed.purs @@ -1,7 +1,7 @@ module Data.Path.Pathy.Sandboxed ( SandboxedPath - -- , sandbox - -- , sandboxAny + , sandbox + , sandboxAny , sandboxRoot , unsandbox , printPath @@ -10,8 +10,8 @@ module Data.Path.Pathy.Sandboxed import Prelude -import Data.Maybe (Maybe(..), fromMaybe) -import Data.Path.Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Printer, Rel, canonicalize, onRelOrAbs, posixPrinter, relativeTo, rootDir, unsafePrintPath', ()) +import Data.Maybe (Maybe(..)) +import Data.Path.Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Printer, canonicalize, foldPath, onRelOrAbs, posixPrinter, relativeTo, rootDir, unsafePrintPath', ()) -- | The type for paths that have been sandboxed. data SandboxedPath a b = SandboxedPath (Path Abs Dir) (Path a b) @@ -24,38 +24,28 @@ instance showSandboxedPath :: (IsRelOrAbs a, IsDirOrFile b) => Show (SandboxedPa -- | Attempts to sandbox a path relative to an absolute directory ("sandbox -- | root"). If the `Path a b` escapes the sandbox root `Nothing` will be -- | returned. --- sandbox --- :: forall a b --- . IsRelOrAbs a --- => IsDirOrFile b --- => Path Abs Dir --- -> Path a b --- -> Maybe (SandboxedPath a b) --- sandbox root = onRelOrAbs goRel goAbs --- where --- goRel :: (Path Rel b -> Path a b) -> Path Rel b -> Maybe (SandboxedPath a b) --- goRel coe p = --- case (root p) `relativeTo` root of --- Nothing -> Nothing --- Just _ -> Just (SandboxedPath root (coe p)) --- goAbs :: (Path Abs b -> Path a b) -> Path Abs b -> Maybe (SandboxedPath a b) --- goAbs coe p = --- case p `relativeTo` root of --- Nothing -> Nothing --- Just _ -> Just (SandboxedPath root (coe p)) +sandbox + :: forall a b + . IsRelOrAbs a + => Path Abs Dir + -> Path a b + -> Maybe (SandboxedPath a b) +sandbox root = map (SandboxedPath root) <<< onRelOrAbs (go (root _)) (go id) + where + go :: forall p. (p -> Path Abs b) -> (p -> Path a b) -> p -> Maybe (Path a b) + go f coe p = + if goesUp (f p `relativeTo` root) + then Nothing + else Just (coe p) + goesUp :: forall x y. Path x y -> Boolean + goesUp = foldPath false (const true) (\p _ -> goesUp p) --- | Sandboxes any path (a to `/`. +-- | Sandboxes any path to `/`. -- | -- | This should only be used for situations where a path is already constrained -- | within a system so that access to `/` is safe - for instance, in URIs. --- sandboxAny --- :: forall a b --- . IsRelOrAbs a --- => IsDirOrFile b --- => Path a b --- -> SandboxedPath a b --- sandboxAny p = --- fromMaybe (SandboxedPath rootDir (canonicalize p)) (sandbox rootDir p) +sandboxAny :: forall a b. IsRelOrAbs a => Path a b -> SandboxedPath a b +sandboxAny p = SandboxedPath rootDir (canonicalize p) -- | Returns the location a `SandboxedPath` was sandboxed to. sandboxRoot :: forall a b. SandboxedPath a b -> Path Abs Dir diff --git a/test/Main.purs b/test/Main.purs index 75fb0a8..bd502ac 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -9,10 +9,11 @@ import Data.Maybe (Maybe(..)) import Data.Path.Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Rel, alterExtension, canonicalize, currentDir, dir, file, parentOf, relativeTo, rename, rootDir, unsafePrintPath, (<..>), (<.>), ()) import Data.Path.Pathy.Gen as PG import Data.Path.Pathy.Parser (parsePosixAbsDir, parsePosixAbsFile, parsePosixRelDir, parsePosixRelFile) +import Data.Path.Pathy.Sandboxed (printPath, sandbox, unsandbox) import Data.String as Str import Data.String.NonEmpty (NonEmptyString) -import Data.Symbol (class IsSymbol, reflectSymbol) as Symbol import Data.Symbol (SProxy(..)) +import Data.Symbol (class IsSymbol, reflectSymbol) as Symbol import Test.QuickCheck as QC import Test.QuickCheck.Gen as Gen import Type.Data.Boolean (False) as Symbol @@ -179,6 +180,10 @@ main = do (relativeTo rootDir rootDir) (currentDir) + test' "(rootDir dirFoo) `relativeTo` (rootDir dirFoo) = ./" + ((rootDir dirFoo) `relativeTo` (rootDir dirFoo)) + "./" + test' "(rootDir dirFoo) `relativeTo` rootDir = currentDir dirFoo" ((rootDir dirFoo) `relativeTo` rootDir) "./foo/" @@ -215,41 +220,49 @@ main = do ((rootDir dirFoo dirBar dirBaz) `relativeTo` rootDir) "./foo/bar/baz/" - test' "(rootDir dirFoo dirBar dirBaz) `relativeTo` (rootDir dirFoo) = ./../foo/bar/baz/" + test' "(rootDir dirFoo dirBar dirBaz) `relativeTo` (rootDir dirFoo) = ./bar/baz/" ((rootDir dirFoo dirBar dirBaz) `relativeTo` (rootDir dirFoo)) + "./bar/baz/" + + test' "(rootDir dirFoo dirBar dirBaz) `relativeTo` (rootDir dirBaz) = ./../foo/bar/baz/" + ((rootDir dirFoo dirBar dirBaz) `relativeTo` (rootDir dirBaz)) "./../foo/bar/baz/" + test' "(rootDir dirBar dirFoo) `relativeTo` (rootDir dirBar) = ./foo/" + ((rootDir dirBar dirFoo) `relativeTo` (rootDir dirBar)) + "./foo/" + test "rename - single level deep" (rename (alterExtension (const Nothing)) (file (reflectNonEmpty $ SProxy :: SProxy "image.png"))) (file $ reflectNonEmpty $ SProxy :: SProxy "image") - -- test "sandbox - fail when relative path lies outside sandbox (above)" - -- (sandbox (rootDir dirBar) (parentOf currentDir)) - -- Nothing - -- - -- test "sandbox - fail when relative path lies outside sandbox (neigbouring)" - -- (sandbox (rootDir dirBar) (parentOf currentDir dirFoo)) - -- Nothing - -- - -- test "sandbox - fail when absolute path lies outside sandbox" - -- (sandbox (rootDir dirBar) (rootDir dirFoo dirBar)) - -- Nothing - -- - -- test "sandbox - succeed when relative path goes above sandbox but returns to it" - -- (unsandbox <$> sandbox (rootDir dirBar) (parentOf currentDir dirBar)) - -- (Just (parentOf currentDir dirBar)) - -- - -- test "sandbox - succeed when absolute path lies inside sandbox" - -- (unsandbox <$> sandbox (rootDir dirBar) (rootDir dirBar dirFoo)) - -- (Just (rootDir dirBar dirFoo)) - -- - -- test "sandbox - print relative path that goes above sandbox but returns to it" - -- (printPath <$> sandbox (rootDir dirBar) (parentOf currentDir dirBar)) - -- (Just "/bar/") - -- - -- test "sandbox - print absolute path that lies inside sandbox" - -- (printPath <$> sandbox (rootDir dirBar) (rootDir dirBar dirFoo)) - -- (Just "/bar/foo/") + test "sandbox - fail when relative path lies outside sandbox (above)" + (sandbox (rootDir dirBar) (parentOf currentDir)) + Nothing + + test "sandbox - fail when relative path lies outside sandbox (neigbouring)" + (sandbox (rootDir dirBar) (parentOf currentDir dirFoo)) + Nothing + + test "sandbox - fail when absolute path lies outside sandbox" + (sandbox (rootDir dirBar) (rootDir dirFoo dirBar)) + Nothing + + test "sandbox - succeed when relative path goes above sandbox but returns to it" + (unsandbox <$> sandbox (rootDir dirBar) (parentOf currentDir dirBar)) + (Just (parentOf currentDir dirBar)) + + test "sandbox - succeed when absolute path lies inside sandbox" + (unsandbox <$> sandbox (rootDir dirBar) (rootDir dirBar dirFoo)) + (Just (rootDir dirBar dirFoo)) + + test "sandbox - print relative path that goes above sandbox but returns to it" + (printPath <$> sandbox (rootDir dirBar) (parentOf currentDir dirBar)) + (Just "/bar/") + + test "sandbox - print absolute path that lies inside sandbox" + (printPath <$> sandbox (rootDir dirBar) (rootDir dirBar dirFoo)) + (Just "/bar/foo/") test "parsePosixRelFile - image.png" (parsePosixRelFile "image.png") From 8b31da95d0fa97e83c377afdcf89d3a8cf504502 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Wed, 21 Feb 2018 11:23:58 +0000 Subject: [PATCH 31/59] Fix warnings --- src/Data/Path/Pathy.purs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index 5e9976e..c1ae2bb 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -170,17 +170,17 @@ canonicalize p = fromMaybe p (go p) go = case _ of Init -> Nothing - p@(ParentOf Init) -> - foldRelOrAbs (const Nothing) (const (Just Init)) p - ParentOf (In p _) -> + p'@(ParentOf Init) -> + foldRelOrAbs (const Nothing) (const (Just Init)) p' + ParentOf (In p' _) -> -- Coercion is safe as `ParentOf` can only appear where `b' ~ Dir` - Just $ (unsafeCoerce :: Path a Dir -> Path a b') (canonicalize p) - ParentOf p -> - case go p of - Just p' -> Just $ canonicalize (ParentOf p') + Just $ (unsafeCoerce :: Path a Dir -> Path a b') (canonicalize p') + ParentOf p' -> + case go p' of + Just p'' -> Just $ canonicalize (ParentOf p'') Nothing -> Nothing - In p f -> - flip In f <$> go p + In p' n -> + flip In n <$> go p' -- | A fold over `Path`s. Since `Path` has private constructors, this allows for -- | functions to be written over its constructors, similar to a total pattern @@ -217,7 +217,7 @@ peelFile :: forall a. Path a File -> Tuple (Path a Dir) (Name File) peelFile = case _ of Init -> unsafeCrashWith "`Init` in Pathy.peelFile (this should be impossible)" ParentOf _ -> unsafeCrashWith "`ParentOf` in Pathy.peelFile (this should be impossible)" - In dir name -> Tuple dir name + In p n -> Tuple p n -- | Retrieves the name of the terminal segment in a path. Returns `Nothing` if -- | the path is `rootDir` / `currentDir` or some `parentOf p`. @@ -231,7 +231,7 @@ fileName :: forall a. Path a File -> Name File fileName = case _ of Init -> unsafeCrashWith "`Init` in Pathy.fileName (this should be impossible)" ParentOf _ -> unsafeCrashWith "`ParentOf` in Pathy.fileName (this should be impossible)" - In _ name -> name + In _ n -> n -- | Attempts to rename the terminal segment of a path. If the path is -- | `rootDir` / `currentDir` or some `parentOf p` this will have no effect. @@ -248,7 +248,7 @@ renameTraverse -> Path a b -> f (Path a b) renameTraverse f = case _ of - In p name -> In p <$> f name + In p n -> In p <$> f n p -> pure p -- | Sets the extension of a name. @@ -269,10 +269,10 @@ relativeTo p rp = coeB $ step Init (canonicalize (coeD p)) (canonicalize rp) step acc = case _, _ of p', rp' | p' == rp' -> acc Init, In rp' _ -> step (ParentOf acc) Init rp' - In p' name, Init -> In (step acc p' Init) name - In p' name, rp' - | p' == rp' -> In acc name - | otherwise -> In (step acc p' rp') name + In p' n, Init -> In (step acc p' Init) n + In p' n, rp' + | p' == rp' -> In acc n + | otherwise -> In (step acc p' rp') n _, _ -> unsafeCrashWith "`ParentOf` in Pathy.relativeTo (this should be impossible)" -- Unfortunately we can't avoid some coercions in this function unless @@ -297,7 +297,7 @@ refine f d = go go :: forall a' b'. IsDirOrFile b' => Path a' b' -> Path a' b' go Init = Init go (ParentOf p) = ParentOf (go p) - go (In p name) = In (go p) (onDirOrFile (_ <<< d) (_ <<< f) name) + go (In p n) = In (go p) (onDirOrFile (_ <<< d) (_ <<< f) n) -- | Prints a path exactly as-is. This is unsafe as the path may refer to a -- | location it should not have access to. Path printing should almost always From 62dfcc5127e652ff718226d42c657f69c92c85a3 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Wed, 21 Feb 2018 11:48:21 +0000 Subject: [PATCH 32/59] Use similar interface for printing and parsing --- src/Data/Path/Pathy.purs | 18 ++----- src/Data/Path/Pathy/Parser.purs | 82 +++++++++++++++------------- src/Data/Path/Pathy/Sandboxed.purs | 20 ++----- test/Main.purs | 86 +++++++++++++++--------------- 4 files changed, 94 insertions(+), 112 deletions(-) diff --git a/src/Data/Path/Pathy.purs b/src/Data/Path/Pathy.purs index c1ae2bb..88c7de9 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Data/Path/Pathy.purs @@ -29,7 +29,6 @@ module Data.Path.Pathy , relativeTo , refine , unsafePrintPath - , unsafePrintPath' , module Exports , module Data.Path.Pathy.Name , module Data.Path.Pathy.Phantom @@ -44,7 +43,7 @@ import Data.Newtype (un) import Data.Path.Pathy.Name (Name(..)) as Exports import Data.Path.Pathy.Name (Name(..), alterExtension, extension) import Data.Path.Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel, foldDirOrFile, foldRelOrAbs, onDirOrFile, onRelOrAbs, kind DirOrFile, kind RelOrAbs) -import Data.Path.Pathy.Printer (Printer, posixPrinter, printSegment) +import Data.Path.Pathy.Printer (Printer, printSegment) import Data.Path.Pathy.Printer (Printer, posixPrinter, windowsPrinter) as Exports import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES @@ -299,28 +298,17 @@ refine f d = go go (ParentOf p) = ParentOf (go p) go (In p n) = In (go p) (onDirOrFile (_ <<< d) (_ <<< f) n) --- | Prints a path exactly as-is. This is unsafe as the path may refer to a --- | location it should not have access to. Path printing should almost always --- | be performed with a `SandboxedPath`. -unsafePrintPath - :: forall a b - . IsRelOrAbs a - => IsDirOrFile b - => Path a b - -> String -unsafePrintPath = unsafePrintPath' posixPrinter - -- | Prints a path exactly as-is using the specified `Printer`. This is unsafe -- | as the path may refer to a location it should not have access to. Path -- | printing should almost always be performed with a `SandboxedPath`. -unsafePrintPath' +unsafePrintPath :: forall a b . IsRelOrAbs a => IsDirOrFile b => Printer -> Path a b -> String -unsafePrintPath' printer p = go p +unsafePrintPath printer p = go p where go :: forall b'. IsDirOrFile b' => Path a b' -> String go = diff --git a/src/Data/Path/Pathy/Parser.purs b/src/Data/Path/Pathy/Parser.purs index 41c7525..48a2423 100644 --- a/src/Data/Path/Pathy/Parser.purs +++ b/src/Data/Path/Pathy/Parser.purs @@ -1,9 +1,11 @@ module Data.Path.Pathy.Parser - ( parsePosixPath - , parsePosixRelFile - , parsePosixAbsFile - , parsePosixRelDir - , parsePosixAbsDir + ( Parser(..) + , posixParser + , parsePath + , parseRelFile + , parseAbsFile + , parseRelDir + , parseAbsDir ) where import Prelude @@ -20,22 +22,15 @@ import Data.String as S import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES --- | Parses a canonical `String` representation of a path into a `Path` value. --- | Note that in order to be unambiguous, trailing directories should be --- | marked with a trailing slash character (`'/'`). -parsePosixPath - :: forall z - . (RelDir -> z) - -> (AbsDir -> z) - -> (RelFile -> z) - -> (AbsFile -> z) - -> z - -> String - -> z -parsePosixPath relDir absDir relFile absFile z p - | p == "" = z - | p == "/" = absDir rootDir - | otherwise = +newtype Parser = Parser (forall z. (RelDir -> z) -> (AbsDir -> z) -> (RelFile -> z) -> (AbsFile -> z) -> z -> String -> z) + +-- | A parser for POSIX paths. +posixParser :: Parser +posixParser = Parser \relDir absDir relFile absFile z -> + case _ of + "" -> z + "/" -> absDir rootDir + p -> let isAbs = S.take 1 p == "/" isFile = S.takeRight 1 p /= "/" @@ -47,23 +42,6 @@ parsePosixPath relDir absDir relFile absFile z p false, true -> buildPath z currentDir (either (const z) relFile) segs false, false -> buildPath z currentDir (either relDir relDir) segs - --- | Attempts to parse a relative file from a string. -parsePosixRelFile :: String -> Maybe RelFile -parsePosixRelFile = parsePosixPath (const Nothing) (const Nothing) Just (const Nothing) Nothing - --- | Attempts to parse an absolute file from a string. -parsePosixAbsFile :: String -> Maybe AbsFile -parsePosixAbsFile = parsePosixPath (const Nothing) (const Nothing) (const Nothing) Just Nothing - --- | Attempts to parse a relative directory from a string. -parsePosixRelDir :: String -> Maybe RelDir -parsePosixRelDir = parsePosixPath Just (const Nothing) (const Nothing) (const Nothing) Nothing - --- | Attempts to parse an absolute directory from a string. -parsePosixAbsDir :: String -> Maybe AbsDir -parsePosixAbsDir = parsePosixPath (const Nothing) Just (const Nothing) (const Nothing) Nothing - buildPath :: forall z a b . z @@ -86,3 +64,31 @@ buildPath z init k segs = | NES.toString name == ".." -> parentOf (go segs') | NES.toString name == "." -> go segs' | otherwise -> extendPath (go segs') (Name name) + +parsePath + :: forall z + . Parser + -> (RelDir -> z) + -> (AbsDir -> z) + -> (RelFile -> z) + -> (AbsFile -> z) + -> z + -> String + -> z +parsePath (Parser p) = p + +-- | Attempts to parse a relative file. +parseRelFile :: Parser -> String -> Maybe RelFile +parseRelFile p = parsePath p (const Nothing) (const Nothing) Just (const Nothing) Nothing + +-- | Attempts to parse an absolute file. +parseAbsFile :: Parser -> String -> Maybe AbsFile +parseAbsFile p = parsePath p (const Nothing) (const Nothing) (const Nothing) Just Nothing + +-- | Attempts to parse a relative directory. +parseRelDir :: Parser -> String -> Maybe RelDir +parseRelDir p = parsePath p Just (const Nothing) (const Nothing) (const Nothing) Nothing + +-- | Attempts to parse an absolute directory. +parseAbsDir :: Parser -> String -> Maybe AbsDir +parseAbsDir p = parsePath p (const Nothing) Just (const Nothing) (const Nothing) Nothing diff --git a/src/Data/Path/Pathy/Sandboxed.purs b/src/Data/Path/Pathy/Sandboxed.purs index d791180..77fb4f4 100644 --- a/src/Data/Path/Pathy/Sandboxed.purs +++ b/src/Data/Path/Pathy/Sandboxed.purs @@ -5,13 +5,12 @@ module Data.Path.Pathy.Sandboxed , sandboxRoot , unsandbox , printPath - , printPath' ) where import Prelude import Data.Maybe (Maybe(..)) -import Data.Path.Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Printer, canonicalize, foldPath, onRelOrAbs, posixPrinter, relativeTo, rootDir, unsafePrintPath', ()) +import Data.Path.Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Printer, canonicalize, foldPath, onRelOrAbs, relativeTo, rootDir, unsafePrintPath, ()) -- | The type for paths that have been sandboxed. data SandboxedPath a b = SandboxedPath (Path Abs Dir) (Path a b) @@ -55,29 +54,18 @@ sandboxRoot (SandboxedPath root _) = root unsandbox :: forall a b. SandboxedPath a b -> Path a b unsandbox (SandboxedPath _ p) = p --- | Prints a `SandboxedPath` into its canonical `String` representation. The --- | printed path will always be absolute, as this is the only way to ensure --- | the path is safely referring to the intended location. -printPath - :: forall a b - . IsRelOrAbs a - => IsDirOrFile b - => SandboxedPath a b - -> String -printPath = printPath' posixPrinter - -- | Prints a `SandboxedPath` into its canonical `String` representation, using -- | the specified printer. The printed path will always be absolute, as this -- | is the only way to ensure the path is safely referring to the intended -- | location. -printPath' +printPath :: forall a b . IsRelOrAbs a => IsDirOrFile b => Printer -> SandboxedPath a b -> String -printPath' r (SandboxedPath root p) = - unsafePrintPath' +printPath r (SandboxedPath root p) = + unsafePrintPath r (onRelOrAbs (\_ p' -> canonicalize (root p')) (flip const) p) diff --git a/test/Main.purs b/test/Main.purs index bd502ac..d38bcef 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -6,9 +6,9 @@ import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, info) import Control.Monad.Eff.Exception (EXCEPTION, throw) import Data.Maybe (Maybe(..)) -import Data.Path.Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Rel, alterExtension, canonicalize, currentDir, dir, file, parentOf, relativeTo, rename, rootDir, unsafePrintPath, (<..>), (<.>), ()) +import Data.Path.Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Rel, alterExtension, canonicalize, currentDir, dir, file, parentOf, posixPrinter, relativeTo, rename, rootDir, unsafePrintPath, (<..>), (<.>), ()) import Data.Path.Pathy.Gen as PG -import Data.Path.Pathy.Parser (parsePosixAbsDir, parsePosixAbsFile, parsePosixRelDir, parsePosixRelFile) +import Data.Path.Pathy.Parser (parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser) import Data.Path.Pathy.Sandboxed (printPath, sandbox, unsandbox) import Data.String as Str import Data.String.NonEmpty (NonEmptyString) @@ -28,7 +28,7 @@ test name actual expected= do else throw $ "Failed:\n Expected: " <> (show expected) <> "\n Actual: " <> (show actual) test' :: forall a b eff. IsRelOrAbs a => IsDirOrFile b => String -> Path a b -> String -> Eff (console :: CONSOLE, exception :: EXCEPTION | eff) Unit -test' n p s = test n (unsafePrintPath p) s +test' n p s = test n (unsafePrintPath posixPrinter p) s pathPart ∷ Gen.Gen NonEmptyString pathPart = asNonEmptyString <$> Gen.suchThat QC.arbitrary (not <<< Str.null) @@ -51,24 +51,24 @@ parsePrintCheck input parsed = then QC.Success else QC.Failed $ "`parse (print path) != Just path` for path: `" <> show input <> "` which was re-parsed into `" <> show parsed <> "`" - <> "\n\tPrinted path: " <> show (unsafePrintPath input) - <> "\n\tPrinted path': `" <> show (map unsafePrintPath parsed) <> "`" + <> "\n\tPrinted path: " <> show (unsafePrintPath posixPrinter input) + <> "\n\tPrinted path': `" <> show (map (unsafePrintPath posixPrinter) parsed) <> "`" parsePrintAbsDirPath :: Gen.Gen QC.Result parsePrintAbsDirPath = PG.genAbsDirPath <#> \path -> - parsePrintCheck path (parsePosixAbsDir $ unsafePrintPath path) + parsePrintCheck path (parseAbsDir posixParser $ unsafePrintPath posixPrinter path) parsePrintAbsFilePath :: Gen.Gen QC.Result parsePrintAbsFilePath = PG.genAbsFilePath <#> \path -> - parsePrintCheck path (parsePosixAbsFile $ unsafePrintPath path) + parsePrintCheck path (parseAbsFile posixParser $ unsafePrintPath posixPrinter path) parsePrintRelDirPath :: Gen.Gen QC.Result parsePrintRelDirPath = PG.genRelDirPath <#> \path -> - parsePrintCheck path (parsePosixRelDir $ unsafePrintPath path) + parsePrintCheck path (parseRelDir posixParser $ unsafePrintPath posixPrinter path) parsePrintRelFilePath :: Gen.Gen QC.Result parsePrintRelFilePath = PG.genRelFilePath <#> \path -> - parsePrintCheck path (parsePosixRelFile $ unsafePrintPath path) + parsePrintCheck path (parseRelFile posixParser $ unsafePrintPath posixPrinter path) checkRelative :: forall b. IsDirOrFile b => Gen.Gen (Path Abs b) -> Gen.Gen QC.Result checkRelative gen = do @@ -84,10 +84,10 @@ checkRelative gen = do else QC.Failed $ "`relativeTo` property did not hold:" - <> "\n\tcp1: " <> unsafePrintPath cp1 - <> "\n\tcp2: " <> unsafePrintPath cp2 - <> "\n\trel: " <> unsafePrintPath rel - <> "\n\tcp1': " <> unsafePrintPath cp1' + <> "\n\tcp1: " <> unsafePrintPath posixPrinter cp1 + <> "\n\tcp2: " <> unsafePrintPath posixPrinter cp2 + <> "\n\trel: " <> unsafePrintPath posixPrinter rel + <> "\n\tcp1': " <> unsafePrintPath posixPrinter cp1' main :: QC.QC () Unit main = do @@ -257,67 +257,67 @@ main = do (Just (rootDir dirBar dirFoo)) test "sandbox - print relative path that goes above sandbox but returns to it" - (printPath <$> sandbox (rootDir dirBar) (parentOf currentDir dirBar)) + (printPath posixPrinter <$> sandbox (rootDir dirBar) (parentOf currentDir dirBar)) (Just "/bar/") test "sandbox - print absolute path that lies inside sandbox" - (printPath <$> sandbox (rootDir dirBar) (rootDir dirBar dirFoo)) + (printPath posixPrinter <$> sandbox (rootDir dirBar) (rootDir dirBar dirFoo)) (Just "/bar/foo/") - test "parsePosixRelFile - image.png" - (parsePosixRelFile "image.png") + test "parseRelFile - image.png" + (parseRelFile posixParser "image.png") (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") - test "parsePosixRelFile - ./image.png" - (parsePosixRelFile "./image.png") + test "parseRelFile - ./image.png" + (parseRelFile posixParser "./image.png") (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") - test "parsePosixRelFile - foo/image.png" - (parsePosixRelFile "foo/image.png") + test "parseRelFile - foo/image.png" + (parseRelFile posixParser "foo/image.png") (Just $ dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) - test "parsePosixRelFile - ../foo/image.png" - (parsePosixRelFile "../foo/image.png") + test "parseRelFile - ../foo/image.png" + (parseRelFile posixParser "../foo/image.png") (Just $ currentDir <..> dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) - test "parsePosixAbsFile - /image.png" - (parsePosixAbsFile "/image.png") + test "parseAbsFile - /image.png" + (parseAbsFile posixParser "/image.png") (Just $ rootDir file (reflectNonEmpty $ SProxy :: SProxy "image.png")) - test "parsePosixAbsFile - /foo/image.png" - (parsePosixAbsFile "/foo/image.png") + test "parseAbsFile - /foo/image.png" + (parseAbsFile posixParser "/foo/image.png") (Just $ rootDir dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) - test "parsePosixRelDir - empty string" - (parsePosixRelDir "") + test "parseRelDir - empty string" + (parseRelDir posixParser "") Nothing - test "parsePosixRelDir - ./../" - (parsePosixRelDir "./../") + test "parseRelDir - ./../" + (parseRelDir posixParser "./../") (Just $ currentDir <..> currentDir) - test "parsePosixRelDir - foo/" - (parsePosixRelDir "foo/") + test "parseRelDir - foo/" + (parseRelDir posixParser "foo/") (Just dirFoo) - test "parsePosixRelDir - foo/bar" - (parsePosixRelDir "foo/bar/") + test "parseRelDir - foo/bar" + (parseRelDir posixParser "foo/bar/") (Just $ dirFoo dirBar) - test "parsePosixRelDir - ./foo/bar" - (parsePosixRelDir "./foo/bar/") + test "parseRelDir - ./foo/bar" + (parseRelDir posixParser "./foo/bar/") (Just $ dirFoo dirBar) - test "parsePosixAbsDir - /" - (parsePosixAbsDir "/") + test "parseAbsDir - /" + (parseAbsDir posixParser "/") (Just $ rootDir) - test "parsePosixAbsDir - /foo/" - (parsePosixAbsDir "/foo/") + test "parseAbsDir - /foo/" + (parseAbsDir posixParser "/foo/") (Just $ rootDir dirFoo) - test "parsePosixAbsDir - /foo/bar" - (parsePosixAbsDir "/foo/bar/") + test "parseAbsDir - /foo/bar" + (parseAbsDir posixParser "/foo/bar/") (Just $ rootDir dirFoo dirBar) class IsSymbolNonEmpty sym where From 36cb263cc13f3a6cebc7186b9f89e4f94681e7e8 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Wed, 21 Feb 2018 11:50:30 +0000 Subject: [PATCH 33/59] Reduce namespace to just `Pathy` --- src/{Data/Path => }/Pathy.purs | 16 ++++++++-------- src/{Data/Path => }/Pathy/Gen.purs | 6 +++--- src/{Data/Path => }/Pathy/Name.purs | 4 ++-- src/{Data/Path => }/Pathy/Parser.purs | 8 ++++---- src/{Data/Path => }/Pathy/Phantom.purs | 2 +- src/{Data/Path => }/Pathy/Printer.purs | 2 +- src/{Data/Path => }/Pathy/Sandboxed.purs | 4 ++-- test/Main.purs | 10 +++++----- 8 files changed, 26 insertions(+), 26 deletions(-) rename src/{Data/Path => }/Pathy.purs (95%) rename src/{Data/Path => }/Pathy/Gen.purs (93%) rename src/{Data/Path => }/Pathy/Name.purs (94%) rename src/{Data/Path => }/Pathy/Parser.purs (92%) rename src/{Data/Path => }/Pathy/Phantom.purs (98%) rename src/{Data/Path => }/Pathy/Printer.purs (99%) rename src/{Data/Path => }/Pathy/Sandboxed.purs (92%) diff --git a/src/Data/Path/Pathy.purs b/src/Pathy.purs similarity index 95% rename from src/Data/Path/Pathy.purs rename to src/Pathy.purs index 88c7de9..c363f06 100644 --- a/src/Data/Path/Pathy.purs +++ b/src/Pathy.purs @@ -1,4 +1,4 @@ -module Data.Path.Pathy +module Pathy ( Path , AnyPath , RelPath @@ -30,8 +30,8 @@ module Data.Path.Pathy , refine , unsafePrintPath , module Exports - , module Data.Path.Pathy.Name - , module Data.Path.Pathy.Phantom + , module Pathy.Name + , module Pathy.Phantom ) where import Prelude @@ -40,15 +40,15 @@ import Data.Either (Either) import Data.Identity (Identity(..)) import Data.Maybe (Maybe(..), fromMaybe) import Data.Newtype (un) -import Data.Path.Pathy.Name (Name(..)) as Exports -import Data.Path.Pathy.Name (Name(..), alterExtension, extension) -import Data.Path.Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel, foldDirOrFile, foldRelOrAbs, onDirOrFile, onRelOrAbs, kind DirOrFile, kind RelOrAbs) -import Data.Path.Pathy.Printer (Printer, printSegment) -import Data.Path.Pathy.Printer (Printer, posixPrinter, windowsPrinter) as Exports import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES import Data.Tuple (Tuple(..)) import Partial.Unsafe (unsafeCrashWith) +import Pathy.Name (Name(..), alterExtension, extension) +import Pathy.Name (Name(..)) as Exports +import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel, foldDirOrFile, foldRelOrAbs, onDirOrFile, onRelOrAbs, kind DirOrFile, kind RelOrAbs) +import Pathy.Printer (Printer, posixPrinter, windowsPrinter) as Exports +import Pathy.Printer (Printer, printSegment) import Unsafe.Coerce (unsafeCoerce) -- | A type that describes a Path. All flavors of paths are described by this diff --git a/src/Data/Path/Pathy/Gen.purs b/src/Pathy/Gen.purs similarity index 93% rename from src/Data/Path/Pathy/Gen.purs rename to src/Pathy/Gen.purs index 3b90175..adec86b 100644 --- a/src/Data/Path/Pathy/Gen.purs +++ b/src/Pathy/Gen.purs @@ -1,4 +1,4 @@ -module Data.Path.Pathy.Gen +module Pathy.Gen ( genAbsDirPath , genAbsFilePath , genAbsAnyPath @@ -17,10 +17,10 @@ import Data.Either (Either(..)) import Data.Foldable (foldr) import Data.List as L import Data.NonEmpty ((:|)) -import Data.Path.Pathy (AbsPath, AbsFile, AbsDir, RelDir, RelFile, RelPath, ()) -import Data.Path.Pathy as P import Data.String.Gen as SG import Data.String.NonEmpty (NonEmptyString, cons) +import Pathy (AbsPath, AbsFile, AbsDir, RelDir, RelFile, RelPath, ()) +import Pathy as P genName ∷ ∀ m. MonadGen m ⇒ MonadRec m ⇒ m NonEmptyString genName = cons <$> genChar <*> SG.genString genChar diff --git a/src/Data/Path/Pathy/Name.purs b/src/Pathy/Name.purs similarity index 94% rename from src/Data/Path/Pathy/Name.purs rename to src/Pathy/Name.purs index 0ea5856..2466c14 100644 --- a/src/Data/Path/Pathy/Name.purs +++ b/src/Pathy/Name.purs @@ -1,13 +1,13 @@ -module Data.Path.Pathy.Name where +module Pathy.Name where import Prelude import Data.Maybe (Maybe(..), maybe) import Data.Newtype (class Newtype) -import Data.Path.Pathy.Phantom (kind DirOrFile) import Data.String as S import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES +import Pathy.Phantom (kind DirOrFile) -- | A type used for both directory and file names, indexed by `DirOrFile`. newtype Name (n :: DirOrFile) = Name NonEmptyString diff --git a/src/Data/Path/Pathy/Parser.purs b/src/Pathy/Parser.purs similarity index 92% rename from src/Data/Path/Pathy/Parser.purs rename to src/Pathy/Parser.purs index 48a2423..5a4fc55 100644 --- a/src/Data/Path/Pathy/Parser.purs +++ b/src/Pathy/Parser.purs @@ -1,4 +1,4 @@ -module Data.Path.Pathy.Parser +module Pathy.Parser ( Parser(..) , posixParser , parsePath @@ -15,12 +15,12 @@ import Data.Either (Either(..), either) import Data.List (List(..), (:)) import Data.List as L import Data.Maybe (Maybe(..)) -import Data.Path.Pathy (AbsDir, AbsFile, Path, RelDir, RelFile, currentDir, extendPath, parentOf, rootDir) -import Data.Path.Pathy.Name (Name(..)) -import Data.Path.Pathy.Phantom (Dir) import Data.String as S import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES +import Pathy (AbsDir, AbsFile, Path, RelDir, RelFile, currentDir, extendPath, parentOf, rootDir) +import Pathy.Name (Name(..)) +import Pathy.Phantom (Dir) newtype Parser = Parser (forall z. (RelDir -> z) -> (AbsDir -> z) -> (RelFile -> z) -> (AbsFile -> z) -> z -> String -> z) diff --git a/src/Data/Path/Pathy/Phantom.purs b/src/Pathy/Phantom.purs similarity index 98% rename from src/Data/Path/Pathy/Phantom.purs rename to src/Pathy/Phantom.purs index fd6f1ba..a17fac2 100644 --- a/src/Data/Path/Pathy/Phantom.purs +++ b/src/Pathy/Phantom.purs @@ -1,4 +1,4 @@ -module Data.Path.Pathy.Phantom where +module Pathy.Phantom where import Prelude diff --git a/src/Data/Path/Pathy/Printer.purs b/src/Pathy/Printer.purs similarity index 99% rename from src/Data/Path/Pathy/Printer.purs rename to src/Pathy/Printer.purs index 92acd62..8807276 100644 --- a/src/Data/Path/Pathy/Printer.purs +++ b/src/Pathy/Printer.purs @@ -1,4 +1,4 @@ -module Data.Path.Pathy.Printer where +module Pathy.Printer where import Prelude diff --git a/src/Data/Path/Pathy/Sandboxed.purs b/src/Pathy/Sandboxed.purs similarity index 92% rename from src/Data/Path/Pathy/Sandboxed.purs rename to src/Pathy/Sandboxed.purs index 77fb4f4..5e16797 100644 --- a/src/Data/Path/Pathy/Sandboxed.purs +++ b/src/Pathy/Sandboxed.purs @@ -1,4 +1,4 @@ -module Data.Path.Pathy.Sandboxed +module Pathy.Sandboxed ( SandboxedPath , sandbox , sandboxAny @@ -10,7 +10,7 @@ module Data.Path.Pathy.Sandboxed import Prelude import Data.Maybe (Maybe(..)) -import Data.Path.Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Printer, canonicalize, foldPath, onRelOrAbs, relativeTo, rootDir, unsafePrintPath, ()) +import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Printer, canonicalize, foldPath, onRelOrAbs, relativeTo, rootDir, unsafePrintPath, ()) -- | The type for paths that have been sandboxed. data SandboxedPath a b = SandboxedPath (Path Abs Dir) (Path a b) diff --git a/test/Main.purs b/test/Main.purs index d38bcef..fc39645 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -6,14 +6,14 @@ import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, info) import Control.Monad.Eff.Exception (EXCEPTION, throw) import Data.Maybe (Maybe(..)) -import Data.Path.Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Rel, alterExtension, canonicalize, currentDir, dir, file, parentOf, posixPrinter, relativeTo, rename, rootDir, unsafePrintPath, (<..>), (<.>), ()) -import Data.Path.Pathy.Gen as PG -import Data.Path.Pathy.Parser (parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser) -import Data.Path.Pathy.Sandboxed (printPath, sandbox, unsandbox) import Data.String as Str import Data.String.NonEmpty (NonEmptyString) -import Data.Symbol (SProxy(..)) import Data.Symbol (class IsSymbol, reflectSymbol) as Symbol +import Data.Symbol (SProxy(..)) +import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Rel, alterExtension, canonicalize, currentDir, dir, file, parentOf, posixPrinter, relativeTo, rename, rootDir, unsafePrintPath, (<..>), (<.>), ()) +import Pathy.Gen as PG +import Pathy.Parser (parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser) +import Pathy.Sandboxed (printPath, sandbox, unsandbox) import Test.QuickCheck as QC import Test.QuickCheck.Gen as Gen import Type.Data.Boolean (False) as Symbol From 60f97ce799f24e078c0762934142fc02bb4c02cb Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Wed, 21 Feb 2018 11:59:40 +0000 Subject: [PATCH 34/59] Organize a bit for a main re-exports module --- src/Pathy.purs | 324 ++------------------------------------- src/Pathy/Parser.purs | 2 +- src/Pathy/Path.purs | 291 +++++++++++++++++++++++++++++++++++ src/Pathy/Printer.purs | 24 +++ src/Pathy/Sandboxed.purs | 4 +- test/Main.purs | 4 +- 6 files changed, 329 insertions(+), 320 deletions(-) create mode 100644 src/Pathy/Path.purs diff --git a/src/Pathy.purs b/src/Pathy.purs index c363f06..8889236 100644 --- a/src/Pathy.purs +++ b/src/Pathy.purs @@ -1,321 +1,15 @@ module Pathy - ( Path - , AnyPath - , RelPath - , AbsPath - , RelDir - , AbsDir - , RelFile - , AbsFile - , rootDir - , currentDir - , dir - , dir' - , file - , file' - , parentOf - , extendPath - , appendPath, () - , parentAppend, (<..>) - , canonicalize - , foldPath - , peel - , peelFile - , name - , fileName - , rename - , renameTraverse - , setExtension, (<.>) - , relativeTo - , refine - , unsafePrintPath - , module Exports + ( module Pathy.Path , module Pathy.Name + , module Pathy.Printer + , module Pathy.Parser , module Pathy.Phantom + , module Pathy.Sandboxed ) where -import Prelude - -import Data.Either (Either) -import Data.Identity (Identity(..)) -import Data.Maybe (Maybe(..), fromMaybe) -import Data.Newtype (un) -import Data.String.NonEmpty (NonEmptyString) -import Data.String.NonEmpty as NES -import Data.Tuple (Tuple(..)) -import Partial.Unsafe (unsafeCrashWith) +import Pathy.Path (AbsDir, AbsFile, AbsPath, AnyPath, Path, RelDir, RelFile, RelPath, appendPath, canonicalize, currentDir, dir, dir', extendPath, file, file', fileName, foldPath, name, parentAppend, parentOf, peel, peelFile, refine, relativeTo, rename, renameTraverse, rootDir, setExtension, (<..>), (<.>), ()) import Pathy.Name (Name(..), alterExtension, extension) -import Pathy.Name (Name(..)) as Exports -import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel, foldDirOrFile, foldRelOrAbs, onDirOrFile, onRelOrAbs, kind DirOrFile, kind RelOrAbs) -import Pathy.Printer (Printer, posixPrinter, windowsPrinter) as Exports -import Pathy.Printer (Printer, printSegment) -import Unsafe.Coerce (unsafeCoerce) - --- | A type that describes a Path. All flavors of paths are described by this --- | type, whether they are absolute or relative paths and whether they --- | refer to files or directories. --- | --- | * The type parameter `a` describes whether the path is `Rel` or `Abs`. --- | * The type parameter `b` describes whether the path is `File` or `Dir`. --- | --- | To ensure type safety, there is no way for users to create a value of --- | this type directly. Instead, helpers should be used, such as `rootDir`, --- | `currentDir`, `file`, `dir`, `()`, and `parsePath`. --- | --- | This ADT allows invalid paths (e.g. paths inside files), but there is no --- | possible way for such paths to be constructed by user-land code. The only --- | "invalid path" that may be constructed is using the `parentOf` function, --- | e.g. `parentOf rootDir`, or by parsing an equivalent string such as --- | `/../`, but such paths may not be rendered to strings until they are first --- | sandboxed to some directory. -data Path (a :: RelOrAbs) (b :: DirOrFile) - = Init - | ParentOf (Path a Dir) - | In (Path a Dir) (Name b) - -derive instance eqPath :: Eq (Path a b) -derive instance ordPath :: Ord (Path a b) - -instance showPathRelDir :: (IsRelOrAbs a, IsDirOrFile b) => Show (Path a b) where - show p@Init = foldRelOrAbs (const "currentDir") (const "rootDir") p - show (ParentOf p) = "(parentOf " <> show p <> ")" - show (In p n) = "(" <> show p <> " " <> foldDirOrFile (("dir " <> _) <<< show) (("file " <> _) <<< show) n <> ")" - --- | A type describing a file or directory path. -type AnyPath a = Either (Path a Dir) (Path a File) - --- | A type describing a relative file or directory path. -type RelPath = AnyPath Rel - --- | A type describing an absolute file or directory path. -type AbsPath = AnyPath Abs - --- | A type describing a directory whose location is given relative to some --- | other, unspecified directory (referred to as the "current directory"). -type RelDir = Path Rel Dir - --- | A type describing a directory whose location is absolutely specified. -type AbsDir = Path Abs Dir - --- | A type describing a file whose location is given relative to some other, --- | unspecified directory (referred to as the "current directory"). -type RelFile = Path Rel File - --- | A type describing a file whose location is absolutely specified. -type AbsFile = Path Abs File - --- | The root directory, which can be used to define absolutely-located resources. -rootDir :: Path Abs Dir -rootDir = Init - --- | The "current directory", which can be used to define relatively-located --- | resources. -currentDir :: Path Rel Dir -currentDir = Init - --- | Creates a path which points to a relative file of the specified name. -file :: NonEmptyString -> Path Rel File -file = file' <<< Name - --- | Creates a path which points to a relative file of the specified name. -file' :: Name File -> Path Rel File -file' = In currentDir - --- | Creates a path which points to a relative directory of the specified name. -dir :: NonEmptyString -> Path Rel Dir -dir = dir' <<< Name - --- | Creates a path which points to a relative directory of the specified name. -dir' :: Name Dir -> Path Rel Dir -dir' = In currentDir - --- | Creates a path that points to the parent directory of the specified path. -parentOf :: forall a. Path a Dir -> Path a Dir -parentOf p = ParentOf p - --- | Extends a path with a file or directory under the current path. -extendPath :: forall a b. Path a Dir -> Name b -> Path a b -extendPath p = In p - --- | Given a directory path, appends a relative path to extend the original --- | path. -appendPath :: forall a b. Path a Dir -> Path Rel b -> Path a b -appendPath = case _, _ of - Init, Init -> Init - ParentOf p, Init -> ParentOf (p Init) - In p (Name d), Init -> In (p Init) (Name d) - p1, ParentOf p2 -> ParentOf (p1 p2) - p1, In p2 n -> In (p1 p2) n - -infixl 6 appendPath as - --- | Ascends into the parent of the specified directory, then descends into --- | the specified path. --- | --- | ```purescript --- | canonicalize (rootDir dir "foo" <..> dir "bar") = rootDir dir "bar" --- | ``` -parentAppend :: forall a b. Path a Dir -> Path Rel b -> Path a b -parentAppend d p = parentOf d p - -infixl 6 parentAppend as <..> - --- | Canonicalizes a path, by reducing things in the form `/x/../` to just --- | `/x/`. Paths like `/../` will be normalized to `/`. -canonicalize :: forall a b. IsRelOrAbs a => Path a b -> Path a b -canonicalize p = fromMaybe p (go p) - where - go :: forall b'. Path a b' -> Maybe (Path a b') - go = case _ of - Init -> - Nothing - p'@(ParentOf Init) -> - foldRelOrAbs (const Nothing) (const (Just Init)) p' - ParentOf (In p' _) -> - -- Coercion is safe as `ParentOf` can only appear where `b' ~ Dir` - Just $ (unsafeCoerce :: Path a Dir -> Path a b') (canonicalize p') - ParentOf p' -> - case go p' of - Just p'' -> Just $ canonicalize (ParentOf p'') - Nothing -> Nothing - In p' n -> - flip In n <$> go p' - --- | A fold over `Path`s. Since `Path` has private constructors, this allows for --- | functions to be written over its constructors, similar to a total pattern --- | match. --- | --- | - The first argument is the value to return for the `currentDir`/`rootDir` --- | at the base of the path. --- | - The second argument is a function for handling a step into the parent --- | directory of the path it receives (eliminates `parentOf`). --- | - The third argument is a function representing a file or directory within --- | the directory of the path it receives (eliminates `extendPath`). -foldPath - :: forall a b r - . r - -> (Path a Dir -> r) - -> (Path a Dir -> Name b -> r) - -> Path a b - -> r -foldPath r f g = case _ of - Init -> r - ParentOf d -> f d - In d n -> g d n - --- | Peels off the last directory and the terminal file or directory name --- | from the path. Returns `Nothing` if the path is `rootDir` / `currentDir` or --- | some `parentOf p`. -peel :: forall a b. Path a b -> Maybe (Tuple (Path a Dir) (Name b)) -peel = foldPath Nothing (const Nothing) (\p n -> Just (Tuple p n)) - --- | Peels off the last director and terminal file from a path. Unlike the --- | general `peel` function this is guaranteed to return a result, as `File` --- | paths are known to have a name. -peelFile :: forall a. Path a File -> Tuple (Path a Dir) (Name File) -peelFile = case _ of - Init -> unsafeCrashWith "`Init` in Pathy.peelFile (this should be impossible)" - ParentOf _ -> unsafeCrashWith "`ParentOf` in Pathy.peelFile (this should be impossible)" - In p n -> Tuple p n - --- | Retrieves the name of the terminal segment in a path. Returns `Nothing` if --- | the path is `rootDir` / `currentDir` or some `parentOf p`. -name :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> Maybe (Name b) -name = foldPath Nothing (const Nothing) (const Just) - --- | Retrieves the name of a file path. Unlike the general `name` function, --- | this is guaranteed to return a result, as `File` paths are known to have a --- | name. -fileName :: forall a. Path a File -> Name File -fileName = case _ of - Init -> unsafeCrashWith "`Init` in Pathy.fileName (this should be impossible)" - ParentOf _ -> unsafeCrashWith "`ParentOf` in Pathy.fileName (this should be impossible)" - In _ n -> n - --- | Attempts to rename the terminal segment of a path. If the path is --- | `rootDir` / `currentDir` or some `parentOf p` this will have no effect. -rename :: forall a b. (Name b -> Name b) -> Path a b -> Path a b -rename f = un Identity <<< renameTraverse (pure <<< f) - --- | Attempts to rename the terminal segment of a path using a function that --- | returns the result in some `Applicative`. If the path is `rootDir` / --- | `currentDir` or some `parentOf p` this will have no effect. -renameTraverse - :: forall f a b - . Applicative f - => (Name b -> f (Name b)) - -> Path a b - -> f (Path a b) -renameTraverse f = case _ of - In p n -> In p <$> f n - p -> pure p - --- | Sets the extension of a name. --- | --- | ```purescript --- | file "image" <.> "png" --- | ``` -setExtension :: forall a b. Path a b -> NonEmptyString -> Path a b -setExtension p ext = rename (alterExtension (const (Just ext))) p - -infixl 6 setExtension as <.> - --- | Makes a path relative to a reference path. -relativeTo :: forall b. Path Abs b -> Path Abs Dir -> Path Rel b -relativeTo p rp = coeB $ step Init (canonicalize (coeD p)) (canonicalize rp) - where - step :: Path Rel Dir -> Path Abs Dir -> Path Abs Dir -> Path Rel Dir - step acc = case _, _ of - p', rp' | p' == rp' -> acc - Init, In rp' _ -> step (ParentOf acc) Init rp' - In p' n, Init -> In (step acc p' Init) n - In p' n, rp' - | p' == rp' -> In acc n - | otherwise -> In (step acc p' rp') n - _, _ -> - unsafeCrashWith "`ParentOf` in Pathy.relativeTo (this should be impossible)" - -- Unfortunately we can't avoid some coercions in this function unless - -- we actually write two different verions of `relativeTo` for file/dir - -- paths. Since the actual data representation is same either way the - -- coercions are safe. - coeD :: forall a. Path a b -> Path a Dir - coeD = unsafeCoerce - coeB :: forall a. Path a Dir -> Path a b - coeB = unsafeCoerce - --- | Refines path segments but does not change anything else. -refine - :: forall a b - . IsDirOrFile b - => (Name File -> Name File) - -> (Name Dir -> Name Dir) - -> Path a b - -> Path a b -refine f d = go - where - go :: forall a' b'. IsDirOrFile b' => Path a' b' -> Path a' b' - go Init = Init - go (ParentOf p) = ParentOf (go p) - go (In p n) = In (go p) (onDirOrFile (_ <<< d) (_ <<< f) n) - --- | Prints a path exactly as-is using the specified `Printer`. This is unsafe --- | as the path may refer to a location it should not have access to. Path --- | printing should almost always be performed with a `SandboxedPath`. -unsafePrintPath - :: forall a b - . IsRelOrAbs a - => IsDirOrFile b - => Printer - -> Path a b - -> String -unsafePrintPath printer p = go p - where - go :: forall b'. IsDirOrFile b' => Path a b' -> String - go = - foldPath - (NES.toString (foldRelOrAbs (const (printer.current <> printer.sep)) (const printer.sep) p)) - (\p' -> go p' <> NES.toString (printer.up <> printer.sep)) - (\p' -> - foldDirOrFile - (\d -> go p' <> printSegment printer d <> NES.toString printer.sep) - (\f -> go p' <> printSegment printer f)) +import Pathy.Printer (Escaper(..), Printer, dotEscaper, escape, posixEscaper, posixPrinter, printSegment, slashEscaper, unsafePrintPath, windowsEscaper, windowsPrinter) +import Pathy.Parser (Parser(..), parseAbsDir, parseAbsFile, parsePath, parseRelDir, parseRelFile, posixParser) +import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel) +import Pathy.Sandboxed (SandboxedPath, printPath, sandbox, sandboxAny, sandboxRoot, unsandbox) diff --git a/src/Pathy/Parser.purs b/src/Pathy/Parser.purs index 5a4fc55..012ec3d 100644 --- a/src/Pathy/Parser.purs +++ b/src/Pathy/Parser.purs @@ -18,7 +18,7 @@ import Data.Maybe (Maybe(..)) import Data.String as S import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES -import Pathy (AbsDir, AbsFile, Path, RelDir, RelFile, currentDir, extendPath, parentOf, rootDir) +import Pathy.Path (AbsDir, AbsFile, Path, RelDir, RelFile, currentDir, extendPath, parentOf, rootDir) import Pathy.Name (Name(..)) import Pathy.Phantom (Dir) diff --git a/src/Pathy/Path.purs b/src/Pathy/Path.purs new file mode 100644 index 0000000..7470d97 --- /dev/null +++ b/src/Pathy/Path.purs @@ -0,0 +1,291 @@ +module Pathy.Path + ( Path + , AnyPath + , RelPath + , AbsPath + , RelDir + , AbsDir + , RelFile + , AbsFile + , rootDir + , currentDir + , dir + , dir' + , file + , file' + , parentOf + , extendPath + , appendPath, () + , parentAppend, (<..>) + , canonicalize + , foldPath + , peel + , peelFile + , name + , fileName + , rename + , renameTraverse + , setExtension, (<.>) + , relativeTo + , refine + ) where + +import Prelude + +import Data.Either (Either) +import Data.Identity (Identity(..)) +import Data.Maybe (Maybe(..), fromMaybe) +import Data.Newtype (un) +import Data.String.NonEmpty (NonEmptyString) +import Data.Tuple (Tuple(..)) +import Partial.Unsafe (unsafeCrashWith) +import Pathy.Name (Name(Name), alterExtension) +import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel, foldDirOrFile, foldRelOrAbs, onDirOrFile, kind DirOrFile, kind RelOrAbs) +import Unsafe.Coerce (unsafeCoerce) + +-- | A type that describes a Path. All flavors of paths are described by this +-- | type, whether they are absolute or relative paths and whether they +-- | refer to files or directories. +-- | +-- | * The type parameter `a` describes whether the path is `Rel` or `Abs`. +-- | * The type parameter `b` describes whether the path is `File` or `Dir`. +-- | +-- | To ensure type safety, there is no way for users to create a value of +-- | this type directly. Instead, helpers should be used, such as `rootDir`, +-- | `currentDir`, `file`, `dir`, `()`, and `parsePath`. +-- | +-- | This ADT allows invalid paths (e.g. paths inside files), but there is no +-- | possible way for such paths to be constructed by user-land code. The only +-- | "invalid path" that may be constructed is using the `parentOf` function, +-- | e.g. `parentOf rootDir`, or by parsing an equivalent string such as +-- | `/../`, but such paths may not be rendered to strings until they are first +-- | sandboxed to some directory. +data Path (a :: RelOrAbs) (b :: DirOrFile) + = Init + | ParentOf (Path a Dir) + | In (Path a Dir) (Name b) + +derive instance eqPath :: Eq (Path a b) +derive instance ordPath :: Ord (Path a b) + +instance showPathRelDir :: (IsRelOrAbs a, IsDirOrFile b) => Show (Path a b) where + show p@Init = foldRelOrAbs (const "currentDir") (const "rootDir") p + show (ParentOf p) = "(parentOf " <> show p <> ")" + show (In p n) = "(" <> show p <> " " <> foldDirOrFile (("dir " <> _) <<< show) (("file " <> _) <<< show) n <> ")" + +-- | A type describing a file or directory path. +type AnyPath a = Either (Path a Dir) (Path a File) + +-- | A type describing a relative file or directory path. +type RelPath = AnyPath Rel + +-- | A type describing an absolute file or directory path. +type AbsPath = AnyPath Abs + +-- | A type describing a directory whose location is given relative to some +-- | other, unspecified directory (referred to as the "current directory"). +type RelDir = Path Rel Dir + +-- | A type describing a directory whose location is absolutely specified. +type AbsDir = Path Abs Dir + +-- | A type describing a file whose location is given relative to some other, +-- | unspecified directory (referred to as the "current directory"). +type RelFile = Path Rel File + +-- | A type describing a file whose location is absolutely specified. +type AbsFile = Path Abs File + +-- | The root directory, which can be used to define absolutely-located resources. +rootDir :: Path Abs Dir +rootDir = Init + +-- | The "current directory", which can be used to define relatively-located +-- | resources. +currentDir :: Path Rel Dir +currentDir = Init + +-- | Creates a path which points to a relative file of the specified name. +file :: NonEmptyString -> Path Rel File +file = file' <<< Name + +-- | Creates a path which points to a relative file of the specified name. +file' :: Name File -> Path Rel File +file' = In currentDir + +-- | Creates a path which points to a relative directory of the specified name. +dir :: NonEmptyString -> Path Rel Dir +dir = dir' <<< Name + +-- | Creates a path which points to a relative directory of the specified name. +dir' :: Name Dir -> Path Rel Dir +dir' = In currentDir + +-- | Creates a path that points to the parent directory of the specified path. +parentOf :: forall a. Path a Dir -> Path a Dir +parentOf p = ParentOf p + +-- | Extends a path with a file or directory under the current path. +extendPath :: forall a b. Path a Dir -> Name b -> Path a b +extendPath p = In p + +-- | Given a directory path, appends a relative path to extend the original +-- | path. +appendPath :: forall a b. Path a Dir -> Path Rel b -> Path a b +appendPath = case _, _ of + Init, Init -> Init + ParentOf p, Init -> ParentOf (p Init) + In p (Name d), Init -> In (p Init) (Name d) + p1, ParentOf p2 -> ParentOf (p1 p2) + p1, In p2 n -> In (p1 p2) n + +infixl 6 appendPath as + +-- | Ascends into the parent of the specified directory, then descends into +-- | the specified path. +-- | +-- | ```purescript +-- | canonicalize (rootDir dir "foo" <..> dir "bar") = rootDir dir "bar" +-- | ``` +parentAppend :: forall a b. Path a Dir -> Path Rel b -> Path a b +parentAppend d p = parentOf d p + +infixl 6 parentAppend as <..> + +-- | Canonicalizes a path, by reducing things in the form `/x/../` to just +-- | `/x/`. Paths like `/../` will be normalized to `/`. +canonicalize :: forall a b. IsRelOrAbs a => Path a b -> Path a b +canonicalize p = fromMaybe p (go p) + where + go :: forall b'. Path a b' -> Maybe (Path a b') + go = case _ of + Init -> + Nothing + p'@(ParentOf Init) -> + foldRelOrAbs (const Nothing) (const (Just Init)) p' + ParentOf (In p' _) -> + -- Coercion is safe as `ParentOf` can only appear where `b' ~ Dir` + Just $ (unsafeCoerce :: Path a Dir -> Path a b') (canonicalize p') + ParentOf p' -> + case go p' of + Just p'' -> Just $ canonicalize (ParentOf p'') + Nothing -> Nothing + In p' n -> + flip In n <$> go p' + +-- | A fold over `Path`s. Since `Path` has private constructors, this allows for +-- | functions to be written over its constructors, similar to a total pattern +-- | match. +-- | +-- | - The first argument is the value to return for the `currentDir`/`rootDir` +-- | at the base of the path. +-- | - The second argument is a function for handling a step into the parent +-- | directory of the path it receives (eliminates `parentOf`). +-- | - The third argument is a function representing a file or directory within +-- | the directory of the path it receives (eliminates `extendPath`). +foldPath + :: forall a b r + . r + -> (Path a Dir -> r) + -> (Path a Dir -> Name b -> r) + -> Path a b + -> r +foldPath r f g = case _ of + Init -> r + ParentOf d -> f d + In d n -> g d n + +-- | Peels off the last directory and the terminal file or directory name +-- | from the path. Returns `Nothing` if the path is `rootDir` / `currentDir` or +-- | some `parentOf p`. +peel :: forall a b. Path a b -> Maybe (Tuple (Path a Dir) (Name b)) +peel = foldPath Nothing (const Nothing) (\p n -> Just (Tuple p n)) + +-- | Peels off the last director and terminal file from a path. Unlike the +-- | general `peel` function this is guaranteed to return a result, as `File` +-- | paths are known to have a name. +peelFile :: forall a. Path a File -> Tuple (Path a Dir) (Name File) +peelFile = case _ of + Init -> unsafeCrashWith "`Init` in Pathy.peelFile (this should be impossible)" + ParentOf _ -> unsafeCrashWith "`ParentOf` in Pathy.peelFile (this should be impossible)" + In p n -> Tuple p n + +-- | Retrieves the name of the terminal segment in a path. Returns `Nothing` if +-- | the path is `rootDir` / `currentDir` or some `parentOf p`. +name :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> Maybe (Name b) +name = foldPath Nothing (const Nothing) (const Just) + +-- | Retrieves the name of a file path. Unlike the general `name` function, +-- | this is guaranteed to return a result, as `File` paths are known to have a +-- | name. +fileName :: forall a. Path a File -> Name File +fileName = case _ of + Init -> unsafeCrashWith "`Init` in Pathy.fileName (this should be impossible)" + ParentOf _ -> unsafeCrashWith "`ParentOf` in Pathy.fileName (this should be impossible)" + In _ n -> n + +-- | Attempts to rename the terminal segment of a path. If the path is +-- | `rootDir` / `currentDir` or some `parentOf p` this will have no effect. +rename :: forall a b. (Name b -> Name b) -> Path a b -> Path a b +rename f = un Identity <<< renameTraverse (pure <<< f) + +-- | Attempts to rename the terminal segment of a path using a function that +-- | returns the result in some `Applicative`. If the path is `rootDir` / +-- | `currentDir` or some `parentOf p` this will have no effect. +renameTraverse + :: forall f a b + . Applicative f + => (Name b -> f (Name b)) + -> Path a b + -> f (Path a b) +renameTraverse f = case _ of + In p n -> In p <$> f n + p -> pure p + +-- | Sets the extension of a name. +-- | +-- | ```purescript +-- | file "image" <.> "png" +-- | ``` +setExtension :: forall a b. Path a b -> NonEmptyString -> Path a b +setExtension p ext = rename (alterExtension (const (Just ext))) p + +infixl 6 setExtension as <.> + +-- | Makes a path relative to a reference path. +relativeTo :: forall b. Path Abs b -> Path Abs Dir -> Path Rel b +relativeTo p rp = coeB $ step Init (canonicalize (coeD p)) (canonicalize rp) + where + step :: Path Rel Dir -> Path Abs Dir -> Path Abs Dir -> Path Rel Dir + step acc = case _, _ of + p', rp' | p' == rp' -> acc + Init, In rp' _ -> step (ParentOf acc) Init rp' + In p' n, Init -> In (step acc p' Init) n + In p' n, rp' + | p' == rp' -> In acc n + | otherwise -> In (step acc p' rp') n + _, _ -> + unsafeCrashWith "`ParentOf` in Pathy.relativeTo (this should be impossible)" + -- Unfortunately we can't avoid some coercions in this function unless + -- we actually write two different verions of `relativeTo` for file/dir + -- paths. Since the actual data representation is same either way the + -- coercions are safe. + coeD :: forall a. Path a b -> Path a Dir + coeD = unsafeCoerce + coeB :: forall a. Path a Dir -> Path a b + coeB = unsafeCoerce + +-- | Refines path segments but does not change anything else. +refine + :: forall a b + . IsDirOrFile b + => (Name File -> Name File) + -> (Name Dir -> Name Dir) + -> Path a b + -> Path a b +refine f d = go + where + go :: forall a' b'. IsDirOrFile b' => Path a' b' -> Path a' b' + go Init = Init + go (ParentOf p) = ParentOf (go p) + go (In p n) = In (go p) (onDirOrFile (_ <<< d) (_ <<< f) n) diff --git a/src/Pathy/Printer.purs b/src/Pathy/Printer.purs index 8807276..688e140 100644 --- a/src/Pathy/Printer.purs +++ b/src/Pathy/Printer.purs @@ -10,6 +10,8 @@ import Data.String as Str import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES import Partial.Unsafe (unsafePartial) +import Pathy.Path (Path, foldPath) +import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, foldDirOrFile, foldRelOrAbs) -- | A `Printer` defines options for printing paths. -- | @@ -47,6 +49,28 @@ windowsPrinter = , escaper: windowsEscaper } +-- | Prints a path exactly as-is using the specified `Printer`. This is unsafe +-- | as the path may refer to a location it should not have access to. Path +-- | printing should almost always be performed with a `SandboxedPath`. +unsafePrintPath + :: forall a b + . IsRelOrAbs a + => IsDirOrFile b + => Printer + -> Path a b + -> String +unsafePrintPath printer p = go p + where + go :: forall b'. IsDirOrFile b' => Path a b' -> String + go = + foldPath + (NES.toString (foldRelOrAbs (const (printer.current <> printer.sep)) (const printer.sep) p)) + (\p' -> go p' <> NES.toString (printer.up <> printer.sep)) + (\p' -> + foldDirOrFile + (\d -> go p' <> printSegment printer d <> NES.toString printer.sep) + (\f -> go p' <> printSegment printer f)) + -- | Prints a name as a `String` using the escaper from the specified printer. printSegment :: forall name. Newtype name NonEmptyString => Printer -> name -> String printSegment printer = NES.toString <<< un Escaper printer.escaper <<< unwrap diff --git a/src/Pathy/Sandboxed.purs b/src/Pathy/Sandboxed.purs index 5e16797..1507572 100644 --- a/src/Pathy/Sandboxed.purs +++ b/src/Pathy/Sandboxed.purs @@ -10,7 +10,9 @@ module Pathy.Sandboxed import Prelude import Data.Maybe (Maybe(..)) -import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Printer, canonicalize, foldPath, onRelOrAbs, relativeTo, rootDir, unsafePrintPath, ()) +import Pathy.Path (Path, canonicalize, foldPath, relativeTo, rootDir, ()) +import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, onRelOrAbs) +import Pathy.Printer (Printer, unsafePrintPath) -- | The type for paths that have been sandboxed. data SandboxedPath a b = SandboxedPath (Path Abs Dir) (Path a b) diff --git a/test/Main.purs b/test/Main.purs index fc39645..6be722e 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -10,10 +10,8 @@ import Data.String as Str import Data.String.NonEmpty (NonEmptyString) import Data.Symbol (class IsSymbol, reflectSymbol) as Symbol import Data.Symbol (SProxy(..)) -import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Rel, alterExtension, canonicalize, currentDir, dir, file, parentOf, posixPrinter, relativeTo, rename, rootDir, unsafePrintPath, (<..>), (<.>), ()) +import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Rel, alterExtension, canonicalize, currentDir, dir, file, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, unsafePrintPath, unsandbox, (<..>), (<.>), ()) import Pathy.Gen as PG -import Pathy.Parser (parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser) -import Pathy.Sandboxed (printPath, sandbox, unsandbox) import Test.QuickCheck as QC import Test.QuickCheck.Gen as Gen import Type.Data.Boolean (False) as Symbol From 43e11bcac32f38c13e13679596c2953f0d23e897 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Wed, 21 Feb 2018 13:00:11 +0000 Subject: [PATCH 35/59] Add warning when using totally unsafe path printing --- src/Pathy.purs | 4 +-- src/Pathy/Printer.purs | 69 ++++++++++++++++++++++++++++++++++++---- src/Pathy/Sandboxed.purs | 18 ----------- test/Main.purs | 27 +++++++++------- 4 files changed, 79 insertions(+), 39 deletions(-) diff --git a/src/Pathy.purs b/src/Pathy.purs index 8889236..d676524 100644 --- a/src/Pathy.purs +++ b/src/Pathy.purs @@ -9,7 +9,7 @@ module Pathy import Pathy.Path (AbsDir, AbsFile, AbsPath, AnyPath, Path, RelDir, RelFile, RelPath, appendPath, canonicalize, currentDir, dir, dir', extendPath, file, file', fileName, foldPath, name, parentAppend, parentOf, peel, peelFile, refine, relativeTo, rename, renameTraverse, rootDir, setExtension, (<..>), (<.>), ()) import Pathy.Name (Name(..), alterExtension, extension) -import Pathy.Printer (Escaper(..), Printer, dotEscaper, escape, posixEscaper, posixPrinter, printSegment, slashEscaper, unsafePrintPath, windowsEscaper, windowsPrinter) +import Pathy.Printer (Escaper(..), Printer, debugPrintPath, posixPrinter, printPath, unsafePrintPath, windowsPrinter) import Pathy.Parser (Parser(..), parseAbsDir, parseAbsFile, parsePath, parseRelDir, parseRelFile, posixParser) import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel) -import Pathy.Sandboxed (SandboxedPath, printPath, sandbox, sandboxAny, sandboxRoot, unsandbox) +import Pathy.Sandboxed (SandboxedPath, sandbox, sandboxAny, sandboxRoot, unsandbox) diff --git a/src/Pathy/Printer.purs b/src/Pathy/Printer.purs index 688e140..b3fb1eb 100644 --- a/src/Pathy/Printer.purs +++ b/src/Pathy/Printer.purs @@ -1,4 +1,16 @@ -module Pathy.Printer where +module Pathy.Printer + ( Printer + , posixPrinter + , windowsPrinter + , printPath + , unsafePrintPath + , debugPrintPath + , Escaper(..) + , slashEscaper + , dotEscaper + , posixEscaper + , windowsEscaper + ) where import Prelude @@ -10,8 +22,9 @@ import Data.String as Str import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES import Partial.Unsafe (unsafePartial) -import Pathy.Path (Path, foldPath) -import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, foldDirOrFile, foldRelOrAbs) +import Pathy.Path (Path, canonicalize, foldPath, ()) +import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, foldDirOrFile, foldRelOrAbs, onRelOrAbs) +import Pathy.Sandboxed (SandboxedPath, sandboxRoot, unsandbox) -- | A `Printer` defines options for printing paths. -- | @@ -49,17 +62,59 @@ windowsPrinter = , escaper: windowsEscaper } --- | Prints a path exactly as-is using the specified `Printer`. This is unsafe --- | as the path may refer to a location it should not have access to. Path --- | printing should almost always be performed with a `SandboxedPath`. +-- | Prints a `SandboxedPath` into its canonical `String` representation, using +-- | the specified printer. The printed path will always be absolute, as this +-- | is the only way to ensure the path is safely referring to the intended +-- | location. +printPath + :: forall a b + . IsRelOrAbs a + => IsDirOrFile b + => Printer + -> SandboxedPath a b + -> String +printPath r sp = + let + root = sandboxRoot sp + p = unsandbox sp + in + printPathRep + r + (onRelOrAbs (\_ p' -> canonicalize (root p')) (flip const) p) + +-- | Prints a `SandboxedPath` into its canonical `String` representation, using +-- | the specified printer. This will print a relative path if `b ~ Rel`, which +-- | depending on how the resulting string is used, may be unsafe. unsafePrintPath + :: forall a b + . IsRelOrAbs a + => IsDirOrFile b + => Printer + -> SandboxedPath a b + -> String +unsafePrintPath r sp = printPathRep r (unsandbox sp) + +-- | Prints a path exactly according to its representation. This should only be +-- | used for debug purposes. Using this function will raise a warning at +-- | compile time as a reminder! +debugPrintPath + :: forall a b + . Warn "debugPrintPath usage" + => IsRelOrAbs a + => IsDirOrFile b + => Printer + -> Path a b + -> String +debugPrintPath = printPathRep + +printPathRep :: forall a b . IsRelOrAbs a => IsDirOrFile b => Printer -> Path a b -> String -unsafePrintPath printer p = go p +printPathRep printer p = go p where go :: forall b'. IsDirOrFile b' => Path a b' -> String go = diff --git a/src/Pathy/Sandboxed.purs b/src/Pathy/Sandboxed.purs index 1507572..001a3de 100644 --- a/src/Pathy/Sandboxed.purs +++ b/src/Pathy/Sandboxed.purs @@ -4,7 +4,6 @@ module Pathy.Sandboxed , sandboxAny , sandboxRoot , unsandbox - , printPath ) where import Prelude @@ -12,7 +11,6 @@ import Prelude import Data.Maybe (Maybe(..)) import Pathy.Path (Path, canonicalize, foldPath, relativeTo, rootDir, ()) import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, onRelOrAbs) -import Pathy.Printer (Printer, unsafePrintPath) -- | The type for paths that have been sandboxed. data SandboxedPath a b = SandboxedPath (Path Abs Dir) (Path a b) @@ -55,19 +53,3 @@ sandboxRoot (SandboxedPath root _) = root -- | Extracts the original path from a `SandboxedPath`. unsandbox :: forall a b. SandboxedPath a b -> Path a b unsandbox (SandboxedPath _ p) = p - --- | Prints a `SandboxedPath` into its canonical `String` representation, using --- | the specified printer. The printed path will always be absolute, as this --- | is the only way to ensure the path is safely referring to the intended --- | location. -printPath - :: forall a b - . IsRelOrAbs a - => IsDirOrFile b - => Printer - -> SandboxedPath a b - -> String -printPath r (SandboxedPath root p) = - unsafePrintPath - r - (onRelOrAbs (\_ p' -> canonicalize (root p')) (flip const) p) diff --git a/test/Main.purs b/test/Main.purs index 6be722e..41b9e81 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -10,7 +10,7 @@ import Data.String as Str import Data.String.NonEmpty (NonEmptyString) import Data.Symbol (class IsSymbol, reflectSymbol) as Symbol import Data.Symbol (SProxy(..)) -import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Rel, alterExtension, canonicalize, currentDir, dir, file, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, unsafePrintPath, unsandbox, (<..>), (<.>), ()) +import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Rel, alterExtension, canonicalize, currentDir, dir, file, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, debugPrintPath, unsandbox, (<..>), (<.>), ()) import Pathy.Gen as PG import Test.QuickCheck as QC import Test.QuickCheck.Gen as Gen @@ -26,7 +26,7 @@ test name actual expected= do else throw $ "Failed:\n Expected: " <> (show expected) <> "\n Actual: " <> (show actual) test' :: forall a b eff. IsRelOrAbs a => IsDirOrFile b => String -> Path a b -> String -> Eff (console :: CONSOLE, exception :: EXCEPTION | eff) Unit -test' n p s = test n (unsafePrintPath posixPrinter p) s +test' n p s = test n (printTestPath p) s pathPart ∷ Gen.Gen NonEmptyString pathPart = asNonEmptyString <$> Gen.suchThat QC.arbitrary (not <<< Str.null) @@ -49,24 +49,24 @@ parsePrintCheck input parsed = then QC.Success else QC.Failed $ "`parse (print path) != Just path` for path: `" <> show input <> "` which was re-parsed into `" <> show parsed <> "`" - <> "\n\tPrinted path: " <> show (unsafePrintPath posixPrinter input) - <> "\n\tPrinted path': `" <> show (map (unsafePrintPath posixPrinter) parsed) <> "`" + <> "\n\tPrinted path: " <> show (printTestPath input) + <> "\n\tPrinted path': `" <> show (map (printTestPath) parsed) <> "`" parsePrintAbsDirPath :: Gen.Gen QC.Result parsePrintAbsDirPath = PG.genAbsDirPath <#> \path -> - parsePrintCheck path (parseAbsDir posixParser $ unsafePrintPath posixPrinter path) + parsePrintCheck path (parseAbsDir posixParser $ printTestPath path) parsePrintAbsFilePath :: Gen.Gen QC.Result parsePrintAbsFilePath = PG.genAbsFilePath <#> \path -> - parsePrintCheck path (parseAbsFile posixParser $ unsafePrintPath posixPrinter path) + parsePrintCheck path (parseAbsFile posixParser $ printTestPath path) parsePrintRelDirPath :: Gen.Gen QC.Result parsePrintRelDirPath = PG.genRelDirPath <#> \path -> - parsePrintCheck path (parseRelDir posixParser $ unsafePrintPath posixPrinter path) + parsePrintCheck path (parseRelDir posixParser $ printTestPath path) parsePrintRelFilePath :: Gen.Gen QC.Result parsePrintRelFilePath = PG.genRelFilePath <#> \path -> - parsePrintCheck path (parseRelFile posixParser $ unsafePrintPath posixPrinter path) + parsePrintCheck path (parseRelFile posixParser $ printTestPath path) checkRelative :: forall b. IsDirOrFile b => Gen.Gen (Path Abs b) -> Gen.Gen QC.Result checkRelative gen = do @@ -82,10 +82,10 @@ checkRelative gen = do else QC.Failed $ "`relativeTo` property did not hold:" - <> "\n\tcp1: " <> unsafePrintPath posixPrinter cp1 - <> "\n\tcp2: " <> unsafePrintPath posixPrinter cp2 - <> "\n\trel: " <> unsafePrintPath posixPrinter rel - <> "\n\tcp1': " <> unsafePrintPath posixPrinter cp1' + <> "\n\tcp1: " <> printTestPath cp1 + <> "\n\tcp2: " <> printTestPath cp2 + <> "\n\trel: " <> printTestPath rel + <> "\n\tcp1': " <> printTestPath cp1' main :: QC.QC () Unit main = do @@ -326,3 +326,6 @@ instance isSymbolNonEmpty :: (Symbol.IsSymbol s, Symbol.Equals s "" Symbol.False where asNonEmpty :: String -> NonEmptyString asNonEmpty = unsafeCoerce + +printTestPath :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> String +printTestPath p = debugPrintPath posixPrinter p From d757188de335f369ebae48f666417b7823ca51f1 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Wed, 21 Feb 2018 13:26:22 +0000 Subject: [PATCH 36/59] Only use proxies for name construction? --- src/Pathy/Gen.purs | 8 ++++---- src/Pathy/Name.purs | 16 ++++++++++++++++ src/Pathy/Path.purs | 24 +++++++++++++++--------- test/Main.purs | 44 ++++++++++++++++---------------------------- 4 files changed, 51 insertions(+), 41 deletions(-) diff --git a/src/Pathy/Gen.purs b/src/Pathy/Gen.purs index adec86b..2aebf24 100644 --- a/src/Pathy/Gen.purs +++ b/src/Pathy/Gen.purs @@ -32,13 +32,13 @@ genAbsDirPath = Gen.sized \size → do newSize ← Gen.chooseInt 0 size Gen.resize (const newSize) do parts ∷ L.List NonEmptyString ← Gen.unfoldable genName - pure $ foldr (flip P.appendPath <<< P.dir) P.rootDir parts + pure $ foldr (flip P.appendPath <<< P.dir' <<< P.Name) P.rootDir parts genAbsFilePath :: forall m. MonadGen m => MonadRec m => m AbsFile genAbsFilePath = do dir ← genAbsDirPath file ← genName - pure $ dir P.file file + pure $ dir P.file' (P.Name file) genAbsAnyPath :: forall m. MonadGen m => MonadRec m => m AbsPath genAbsAnyPath = Gen.oneOf $ (Left <$> genAbsDirPath) :| [Right <$> genAbsFilePath] @@ -48,13 +48,13 @@ genRelDirPath = Gen.sized \size → do newSize ← Gen.chooseInt 0 size Gen.resize (const newSize) do parts ∷ L.List NonEmptyString ← Gen.unfoldable genName - pure $ foldr (flip P.appendPath <<< P.dir) P.currentDir parts + pure $ foldr (flip P.appendPath <<< P.dir' <<< P.Name) P.currentDir parts genRelFilePath :: forall m. MonadGen m => MonadRec m => m RelFile genRelFilePath = do dir ← genRelDirPath file ← genName - pure $ dir P.file file + pure $ dir P.file' (P.Name file) genRelAnyPath :: forall m. MonadGen m => MonadRec m => m RelPath genRelAnyPath = Gen.oneOf $ (Left <$> genRelDirPath) :| [Right <$> genRelFilePath] diff --git a/src/Pathy/Name.purs b/src/Pathy/Name.purs index 2466c14..2e810eb 100644 --- a/src/Pathy/Name.purs +++ b/src/Pathy/Name.purs @@ -7,7 +7,12 @@ import Data.Newtype (class Newtype) import Data.String as S import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES +import Data.Symbol (class IsSymbol, SProxy(..)) +import Data.Symbol (reflectSymbol) as Symbol import Pathy.Phantom (kind DirOrFile) +import Type.Data.Boolean (False) as Symbol +import Type.Data.Symbol (class Equals) as Symbol +import Unsafe.Coerce (unsafeCoerce) -- | A type used for both directory and file names, indexed by `DirOrFile`. newtype Name (n :: DirOrFile) = Name NonEmptyString @@ -44,3 +49,14 @@ alterExtension f (Name name) = (Name name') (\ext' -> Name (name' <> NES.singleton '.' <> ext')) (f ext) + +-- | A class for creating `Name` values from type-level strings. This allows us +-- | to guarantee that a name is not empty at compile-time. +class IsName sym where + reflectName :: forall d. SProxy sym -> Name d + +instance isNameNESymbol :: (IsSymbol s, Symbol.Equals s "" Symbol.False) => IsName s where + reflectName _ = asNonEmpty $ Symbol.reflectSymbol (SProxy :: SProxy s) + where + asNonEmpty :: forall d. String -> Name d + asNonEmpty = unsafeCoerce diff --git a/src/Pathy/Path.purs b/src/Pathy/Path.purs index 7470d97..8667628 100644 --- a/src/Pathy/Path.purs +++ b/src/Pathy/Path.purs @@ -36,10 +36,11 @@ import Data.Either (Either) import Data.Identity (Identity(..)) import Data.Maybe (Maybe(..), fromMaybe) import Data.Newtype (un) -import Data.String.NonEmpty (NonEmptyString) +import Data.String.NonEmpty as NES +import Data.Symbol (SProxy) import Data.Tuple (Tuple(..)) import Partial.Unsafe (unsafeCrashWith) -import Pathy.Name (Name(Name), alterExtension) +import Pathy.Name (class IsName, Name(..), alterExtension, reflectName) import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel, foldDirOrFile, foldRelOrAbs, onDirOrFile, kind DirOrFile, kind RelOrAbs) import Unsafe.Coerce (unsafeCoerce) @@ -106,16 +107,19 @@ currentDir :: Path Rel Dir currentDir = Init -- | Creates a path which points to a relative file of the specified name. -file :: NonEmptyString -> Path Rel File -file = file' <<< Name +-- | +-- | Instead of accepting a runtime value, this function accepts a type-level +-- | string via a proxy, to ensure the constructed name is not empty. +file :: forall s. IsName s => SProxy s -> Path Rel File +file = file' <<< reflectName -- | Creates a path which points to a relative file of the specified name. file' :: Name File -> Path Rel File file' = In currentDir -- | Creates a path which points to a relative directory of the specified name. -dir :: NonEmptyString -> Path Rel Dir -dir = dir' <<< Name +dir :: forall s. IsName s => SProxy s -> Path Rel Dir +dir = dir' <<< reflectName -- | Creates a path which points to a relative directory of the specified name. dir' :: Name Dir -> Path Rel Dir @@ -242,13 +246,15 @@ renameTraverse f = case _ of In p n -> In p <$> f n p -> pure p --- | Sets the extension of a name. +-- | Sets the extension on the terminal segment of a path. If the path is +-- | `rootDir` / `currentDir` or some `parentOf p` this will have no effect. If +-- | the passed string is empty, this will remove any existing extension. -- | -- | ```purescript -- | file "image" <.> "png" -- | ``` -setExtension :: forall a b. Path a b -> NonEmptyString -> Path a b -setExtension p ext = rename (alterExtension (const (Just ext))) p +setExtension :: forall a b. Path a b -> String -> Path a b +setExtension p ext = rename (alterExtension (const (NES.fromString ext))) p infixl 6 setExtension as <.> diff --git a/test/Main.purs b/test/Main.purs index 41b9e81..e87ff25 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -8,14 +8,11 @@ import Control.Monad.Eff.Exception (EXCEPTION, throw) import Data.Maybe (Maybe(..)) import Data.String as Str import Data.String.NonEmpty (NonEmptyString) -import Data.Symbol (class IsSymbol, reflectSymbol) as Symbol import Data.Symbol (SProxy(..)) import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Rel, alterExtension, canonicalize, currentDir, dir, file, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, debugPrintPath, unsandbox, (<..>), (<.>), ()) import Pathy.Gen as PG import Test.QuickCheck as QC import Test.QuickCheck.Gen as Gen -import Type.Data.Boolean (False) as Symbol -import Type.Data.Symbol (class Equals) as Symbol import Unsafe.Coerce (unsafeCoerce) test :: forall a eff. Show a => Eq a => String -> a -> a -> Eff (console :: CONSOLE, exception :: EXCEPTION | eff) Unit @@ -35,13 +32,13 @@ pathPart = asNonEmptyString <$> Gen.suchThat QC.arbitrary (not <<< Str.null) asNonEmptyString = unsafeCoerce dirFoo :: Path Rel Dir -dirFoo = dir (reflectNonEmpty $ SProxy :: SProxy "foo") +dirFoo = dir (SProxy :: SProxy "foo") dirBar :: Path Rel Dir -dirBar = dir (reflectNonEmpty $ SProxy :: SProxy "bar") +dirBar = dir (SProxy :: SProxy "bar") dirBaz :: Path Rel Dir -dirBaz = dir (reflectNonEmpty $ SProxy :: SProxy "baz") +dirBaz = dir (SProxy :: SProxy "baz") parsePrintCheck :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> Maybe (Path a b) -> QC.Result parsePrintCheck input parsed = @@ -121,17 +118,17 @@ main = do test' "() - file with two parents" (dirFoo dirBar - file (reflectNonEmpty $ SProxy :: SProxy "image.png")) + file (SProxy :: SProxy "image.png")) "./foo/bar/image.png" test' "(<.>) - file without extension" - (file (reflectNonEmpty $ SProxy :: SProxy "image") - <.> (reflectNonEmpty $ SProxy :: SProxy "png")) + (file (SProxy :: SProxy "image") + <.> "png") "./image.png" test' "(<.>) - file with extension" - (file (reflectNonEmpty $ SProxy :: SProxy "image.jpg") - <.> (reflectNonEmpty $ SProxy :: SProxy "png")) + (file (SProxy :: SProxy "image.jpg") + <.> "png") "./image.png" test' "printPath - ./../" @@ -231,8 +228,8 @@ main = do "./foo/" test "rename - single level deep" - (rename (alterExtension (const Nothing)) (file (reflectNonEmpty $ SProxy :: SProxy "image.png"))) - (file $ reflectNonEmpty $ SProxy :: SProxy "image") + (rename (alterExtension (const Nothing)) (file (SProxy :: SProxy "image.png"))) + (file $ SProxy :: SProxy "image") test "sandbox - fail when relative path lies outside sandbox (above)" (sandbox (rootDir dirBar) (parentOf currentDir)) @@ -264,27 +261,27 @@ main = do test "parseRelFile - image.png" (parseRelFile posixParser "image.png") - (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") + (Just $ file $ SProxy :: SProxy "image.png") test "parseRelFile - ./image.png" (parseRelFile posixParser "./image.png") - (Just $ file $ reflectNonEmpty $ SProxy :: SProxy "image.png") + (Just $ file $ SProxy :: SProxy "image.png") test "parseRelFile - foo/image.png" (parseRelFile posixParser "foo/image.png") - (Just $ dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) + (Just $ dirFoo file (SProxy :: SProxy "image.png")) test "parseRelFile - ../foo/image.png" (parseRelFile posixParser "../foo/image.png") - (Just $ currentDir <..> dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) + (Just $ currentDir <..> dirFoo file (SProxy :: SProxy "image.png")) test "parseAbsFile - /image.png" (parseAbsFile posixParser "/image.png") - (Just $ rootDir file (reflectNonEmpty $ SProxy :: SProxy "image.png")) + (Just $ rootDir file (SProxy :: SProxy "image.png")) test "parseAbsFile - /foo/image.png" (parseAbsFile posixParser "/foo/image.png") - (Just $ rootDir dirFoo file (reflectNonEmpty $ SProxy :: SProxy "image.png")) + (Just $ rootDir dirFoo file (SProxy :: SProxy "image.png")) test "parseRelDir - empty string" (parseRelDir posixParser "") @@ -318,14 +315,5 @@ main = do (parseAbsDir posixParser "/foo/bar/") (Just $ rootDir dirFoo dirBar) -class IsSymbolNonEmpty sym where - reflectNonEmpty :: SProxy sym -> NonEmptyString - -instance isSymbolNonEmpty :: (Symbol.IsSymbol s, Symbol.Equals s "" Symbol.False) => IsSymbolNonEmpty s where - reflectNonEmpty _ = asNonEmpty $ Symbol.reflectSymbol (SProxy :: SProxy s) - where - asNonEmpty :: String -> NonEmptyString - asNonEmpty = unsafeCoerce - printTestPath :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> String printTestPath p = debugPrintPath posixPrinter p From cde2bfc378935103e1c52d190a96bcd6be56e0c0 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Thu, 22 Feb 2018 14:36:41 +0400 Subject: [PATCH 37/59] add comment to dir --- src/Pathy/Path.purs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Pathy/Path.purs b/src/Pathy/Path.purs index 8667628..fec2a60 100644 --- a/src/Pathy/Path.purs +++ b/src/Pathy/Path.purs @@ -118,6 +118,9 @@ file' :: Name File -> Path Rel File file' = In currentDir -- | Creates a path which points to a relative directory of the specified name. +-- | +-- | Instead of accepting a runtime value, this function accepts a type-level +-- | string via a proxy, to ensure the constructed name is not empty. dir :: forall s. IsName s => SProxy s -> Path Rel Dir dir = dir' <<< reflectName From 6dc7471b2c6abe7beca76739b7e9b1de414c993b Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Thu, 22 Feb 2018 15:37:59 +0400 Subject: [PATCH 38/59] add typelevel-prelude to deps --- bower.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 2c405bb..80ee726 100644 --- a/bower.json +++ b/bower.json @@ -24,7 +24,8 @@ "purescript-profunctor": "^3.0.0", "purescript-strings": "^3.5.0", "purescript-transformers": "^3.0.0", - "purescript-unsafe-coerce": "^3.0.0" + "purescript-unsafe-coerce": "^3.0.0", + "purescript-typelevel-prelude": "^2.6.0" }, "devDependencies": { "purescript-quickcheck": "^4.0.0" From e9cd0d3a4b87fd5923f301fadf2831b7872ac3ca Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Thu, 22 Feb 2018 17:25:44 +0400 Subject: [PATCH 39/59] use new `{split,join}Name` for alterExtension and extention definitions --- src/Pathy.purs | 2 +- src/Pathy/Gen.purs | 21 ++++++------ src/Pathy/Name.purs | 78 ++++++++++++++++++++++++++++++++++----------- test/Main.purs | 50 ++++++++++++++++++++++++++++- 4 files changed, 120 insertions(+), 31 deletions(-) diff --git a/src/Pathy.purs b/src/Pathy.purs index d676524..29856c9 100644 --- a/src/Pathy.purs +++ b/src/Pathy.purs @@ -8,7 +8,7 @@ module Pathy ) where import Pathy.Path (AbsDir, AbsFile, AbsPath, AnyPath, Path, RelDir, RelFile, RelPath, appendPath, canonicalize, currentDir, dir, dir', extendPath, file, file', fileName, foldPath, name, parentAppend, parentOf, peel, peelFile, refine, relativeTo, rename, renameTraverse, rootDir, setExtension, (<..>), (<.>), ()) -import Pathy.Name (Name(..), alterExtension, extension) +import Pathy.Name (Name(..), joinName, splitName, alterExtension, extension) import Pathy.Printer (Escaper(..), Printer, debugPrintPath, posixPrinter, printPath, unsafePrintPath, windowsPrinter) import Pathy.Parser (Parser(..), parseAbsDir, parseAbsFile, parsePath, parseRelDir, parseRelFile, posixParser) import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel) diff --git a/src/Pathy/Gen.purs b/src/Pathy/Gen.purs index 2aebf24..985ddc1 100644 --- a/src/Pathy/Gen.purs +++ b/src/Pathy/Gen.purs @@ -5,6 +5,7 @@ module Pathy.Gen , genRelDirPath , genRelFilePath , genRelAnyPath + , genName ) where import Prelude @@ -18,12 +19,12 @@ import Data.Foldable (foldr) import Data.List as L import Data.NonEmpty ((:|)) import Data.String.Gen as SG -import Data.String.NonEmpty (NonEmptyString, cons) -import Pathy (AbsPath, AbsFile, AbsDir, RelDir, RelFile, RelPath, ()) +import Data.String.NonEmpty (cons) +import Pathy (AbsDir, AbsFile, AbsPath, Dir, RelDir, RelFile, RelPath, ()) import Pathy as P -genName ∷ ∀ m. MonadGen m ⇒ MonadRec m ⇒ m NonEmptyString -genName = cons <$> genChar <*> SG.genString genChar +genName ∷ ∀ m a. MonadGen m ⇒ MonadRec m ⇒ m (P.Name a) +genName = map P.Name $ cons <$> genChar <*> SG.genString genChar where genChar = Gen.oneOf $ CG.genDigitChar :| [CG.genAlpha] @@ -31,14 +32,14 @@ genAbsDirPath :: forall m. MonadGen m => MonadRec m => m AbsDir genAbsDirPath = Gen.sized \size → do newSize ← Gen.chooseInt 0 size Gen.resize (const newSize) do - parts ∷ L.List NonEmptyString ← Gen.unfoldable genName - pure $ foldr (flip P.appendPath <<< P.dir' <<< P.Name) P.rootDir parts + parts ∷ L.List (P.Name Dir) ← Gen.unfoldable genName + pure $ foldr (flip P.appendPath <<< P.dir') P.rootDir parts genAbsFilePath :: forall m. MonadGen m => MonadRec m => m AbsFile genAbsFilePath = do dir ← genAbsDirPath file ← genName - pure $ dir P.file' (P.Name file) + pure $ dir P.file' file genAbsAnyPath :: forall m. MonadGen m => MonadRec m => m AbsPath genAbsAnyPath = Gen.oneOf $ (Left <$> genAbsDirPath) :| [Right <$> genAbsFilePath] @@ -47,14 +48,14 @@ genRelDirPath :: forall m. MonadGen m => MonadRec m => m RelDir genRelDirPath = Gen.sized \size → do newSize ← Gen.chooseInt 0 size Gen.resize (const newSize) do - parts ∷ L.List NonEmptyString ← Gen.unfoldable genName - pure $ foldr (flip P.appendPath <<< P.dir' <<< P.Name) P.currentDir parts + parts ∷ L.List (P.Name Dir) ← Gen.unfoldable genName + pure $ foldr (flip P.appendPath <<< P.dir') P.currentDir parts genRelFilePath :: forall m. MonadGen m => MonadRec m => m RelFile genRelFilePath = do dir ← genRelDirPath file ← genName - pure $ dir P.file' (P.Name file) + pure $ dir P.file' file genRelAnyPath :: forall m. MonadGen m => MonadRec m => m RelPath genRelAnyPath = Gen.oneOf $ (Left <$> genRelDirPath) :| [Right <$> genRelFilePath] diff --git a/src/Pathy/Name.purs b/src/Pathy/Name.purs index 2e810eb..6b5f355 100644 --- a/src/Pathy/Name.purs +++ b/src/Pathy/Name.purs @@ -2,7 +2,7 @@ module Pathy.Name where import Prelude -import Data.Maybe (Maybe(..), maybe) +import Data.Maybe (Maybe(..), fromMaybe) import Data.Newtype (class Newtype) import Data.String as S import Data.String.NonEmpty (NonEmptyString) @@ -24,32 +24,72 @@ derive newtype instance ordName :: Ord (Name a) instance showName :: Show (Name a) where show (Name name) = "(Name " <> show name <> ")" --- | Retrieves the extension of a name. +-- | Splits `Name` in name and extension part. +-- | +-- | ```purescript +-- | splitName (Name ".foo") == { name: ".foo", extension: Nothing } +-- | splitName (Name "foo.") == { name: "foo.", extension: Nothing } +-- | splitName (Name "foo") == { name: "foo", extension: Nothing } +-- | splitName (Name ".") == { name: ".", extension: Nothing } +-- | splitName (Name "foo.baz") == { name: "foo", extension: Just "baz" } +-- | ``` +-- | _Note, in real code all strings from this examples would be `NonEmptyString`._ +-- | +-- | Also for any `Name` this property holds: +-- | ```purescript +-- | joinName <<< splitName = id +-- | ```` +-- | see [`joinName`](#v:joinName). +splitName :: forall n. Name n -> { name :: NonEmptyString, ext :: Maybe NonEmptyString } +splitName (Name nameIn) = + fromMaybe { name: nameIn, ext: Nothing } do + idx <- NES.lastIndexOf (S.Pattern ".") nameIn + name <- NES.take idx nameIn + ext <- NES.drop (idx + 1) nameIn + pure $ { name, ext: Just ext } + +-- | Joins name and extension part into one `Name`. +-- | +-- | Also for any `Name` this property holds: +-- | ```purescript +-- | joinName <<< splitName = id +-- | ```` +-- | see [`splitName`](#v:splitName). +joinName :: forall n. { name :: NonEmptyString, ext :: Maybe NonEmptyString } -> Name n +joinName { name, ext } = Name $ case ext of + Nothing -> name + Just ext -> name <> NES.singleton '.' <> ext + +-- | Retrieves the extension of a name. also see [`splitName`](#v:splitName) +-- | +-- | ```purescript +-- | extension (Name ".foo") == Nothing +-- | extension (Name "foo.") == Nothing +-- | extension (Name ".") == Nothing +-- | extension (Name "foo.baz") == Just "baz" +-- | ```` +-- | _Note, in real code all strings from this examples would be `NonEmptyString`._ extension :: forall n. Name n -> Maybe NonEmptyString -extension (Name name) = - flip NES.drop name <<< (_ + 1) =<< NES.lastIndexOf (S.Pattern ".") name +extension = splitName >>> _.ext -- | Alters an extension of a name. This allows extensions to be added, removed, --- | or modified. +-- | or modified. see [`splitName`](#v:splitName) and [`joinName`](#v:joinName) +-- | for how a `Name` is split into name and extention part and joined back +-- | into a `Name`. +-- | +-- | Also for any `Name` this property holds: +-- | ```purescript +-- | alterExtension id = id +-- | ```` alterExtension :: forall n . (Maybe NonEmptyString -> Maybe NonEmptyString) -> Name n -> Name n -alterExtension f (Name name) = - case NES.lastIndexOf (S.Pattern ".") name of - Nothing -> extend name Nothing - Just i -> - case NES.splitAt i name of - Just { before: Just n, after } -> extend n (NES.drop 1 =<< after) - _ -> extend name Nothing - where - extend name' ext = - maybe - (Name name') - (\ext' -> Name (name' <> NES.singleton '.' <> ext')) - (f ext) - +alterExtension f n = + let spn = splitName n + in joinName spn{ext = f spn.ext} + -- | A class for creating `Name` values from type-level strings. This allows us -- | to guarantee that a name is not empty at compile-time. class IsName sym where diff --git a/test/Main.purs b/test/Main.purs index e87ff25..5d3fb47 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -6,11 +6,16 @@ import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, info) import Control.Monad.Eff.Exception (EXCEPTION, throw) import Data.Maybe (Maybe(..)) +import Data.Newtype (un) +import Data.NonEmpty ((:|)) import Data.String as Str import Data.String.NonEmpty (NonEmptyString) +import Data.String.NonEmpty as NES import Data.Symbol (SProxy(..)) -import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Rel, alterExtension, canonicalize, currentDir, dir, file, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, debugPrintPath, unsandbox, (<..>), (<.>), ()) +import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Name(..), Path, Rel, alterExtension, canonicalize, currentDir, debugPrintPath, dir, extension, file, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, unsandbox, (<..>), (<.>), (), joinName, splitName) import Pathy.Gen as PG +import Pathy.Name (reflectName) +import Test.QuickCheck ((===)) import Test.QuickCheck as QC import Test.QuickCheck.Gen as Gen import Unsafe.Coerce (unsafeCoerce) @@ -65,6 +70,31 @@ parsePrintRelFilePath :: Gen.Gen QC.Result parsePrintRelFilePath = PG.genRelFilePath <#> \path -> parsePrintCheck path (parseRelFile posixParser $ printTestPath path) +genAmbigiousName :: forall a. Gen.Gen (Name a) +genAmbigiousName = + let + genNES = PG.genName <#> un Name + in + map Name $ Gen.oneOf $ genNES :| + [ genNES <#> \a -> a <> (NES.singleton '.') + , genNES <#> \a -> (NES.singleton '.') <> a + , pure (NES.singleton '.') + , do + a <- genNES + b <- genNES + pure $ a <> (NES.singleton '.') <> b + ] + +checkAlterExtensionId :: Gen.Gen QC.Result +checkAlterExtensionId = do + n <- genAmbigiousName + pure $ alterExtension id n === id n + +checkJoinSplitNameId :: Gen.Gen QC.Result +checkJoinSplitNameId = do + n <- genAmbigiousName + pure $ joinName (splitName n) === id n + checkRelative :: forall b. IsDirOrFile b => Gen.Gen (Path Abs b) -> Gen.Gen QC.Result checkRelative gen = do p1 <- gen @@ -92,6 +122,8 @@ main = do info "checking `parse <<< print` for `RelFile`" *> QC.quickCheck parsePrintRelFilePath info "checking `relativeTo` for `AbsDir`" *> QC.quickCheck (checkRelative PG.genAbsDirPath) info "checking `relativeTo` for `AbsFile`" *> QC.quickCheck (checkRelative PG.genAbsFilePath) + info "checking `joinName <<< splitName === id`" *> QC.quickCheck checkJoinSplitNameId + info "checking `alterExtension id === id`" *> QC.quickCheck checkAlterExtensionId -- Should not compile: -- test @@ -231,6 +263,22 @@ main = do (rename (alterExtension (const Nothing)) (file (SProxy :: SProxy "image.png"))) (file $ SProxy :: SProxy "image") + test """extension (Name ".foo") == Nothing""" + (extension (reflectName $ SProxy :: SProxy ".foo")) + (Nothing) + test """extension (Name "foo.") == Nothing""" + (extension (reflectName $ SProxy :: SProxy "foo.")) + (Nothing) + test """extension (Name "foo") == Nothing""" + (extension (reflectName $ SProxy :: SProxy "foo")) + (Nothing) + test """extension (Name ".") == Nothing""" + (extension (reflectName $ SProxy :: SProxy ".")) + (Nothing) + test """extension (Name "foo.baz") == (Just "baz")""" + (extension (reflectName $ SProxy :: SProxy "foo.baz")) + (NES.fromString "baz") + test "sandbox - fail when relative path lies outside sandbox (above)" (sandbox (rootDir dirBar) (parentOf currentDir)) Nothing From 2402cd2b3ea1fafcffdb5c2a564b040206732483 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 23 Feb 2018 12:45:23 +0400 Subject: [PATCH 40/59] add newlines in parser definition --- src/Pathy/Parser.purs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Pathy/Parser.purs b/src/Pathy/Parser.purs index 012ec3d..92b78d0 100644 --- a/src/Pathy/Parser.purs +++ b/src/Pathy/Parser.purs @@ -22,7 +22,15 @@ import Pathy.Path (AbsDir, AbsFile, Path, RelDir, RelFile, currentDir, extendPat import Pathy.Name (Name(..)) import Pathy.Phantom (Dir) -newtype Parser = Parser (forall z. (RelDir -> z) -> (AbsDir -> z) -> (RelFile -> z) -> (AbsFile -> z) -> z -> String -> z) +newtype Parser = Parser + ( forall z + . (RelDir -> z) + -> (AbsDir -> z) + -> (RelFile -> z) + -> (AbsFile -> z) + -> z + -> String + -> z) -- | A parser for POSIX paths. posixParser :: Parser From 5d76c88a4d92fbba8ded1a0b273caad79e2edcdf Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 23 Feb 2018 12:51:32 +0400 Subject: [PATCH 41/59] add testcase for /foo/././//bar/ --- src/Pathy/Parser.purs | 1 + test/Main.purs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/Pathy/Parser.purs b/src/Pathy/Parser.purs index 92b78d0..8e5b778 100644 --- a/src/Pathy/Parser.purs +++ b/src/Pathy/Parser.purs @@ -42,6 +42,7 @@ posixParser = Parser \relDir absDir relFile absFile z -> let isAbs = S.take 1 p == "/" isFile = S.takeRight 1 p /= "/" + -- NOTE: if we have `/foo/././//bar/` we will parse that as if it was `/foo/bar/` segs = L.fromFoldable $ A.reverse $ A.mapMaybe NES.fromString $ S.split (S.Pattern "/") p in case isAbs, isFile of diff --git a/test/Main.purs b/test/Main.purs index 5d3fb47..f1a0b83 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -363,5 +363,9 @@ main = do (parseAbsDir posixParser "/foo/bar/") (Just $ rootDir dirFoo dirBar) + test "parseRelDir - /foo/././//bar/" + (parseAbsDir posixParser "/foo/././//bar/") + (Just $ rootDir dirFoo dirBar) + printTestPath :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> String printTestPath p = debugPrintPath posixPrinter p From 40864f067df413a6ef59e65319e4d6940bf57ef5 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 23 Feb 2018 12:57:33 +0400 Subject: [PATCH 42/59] optimise Array.reverse >>> List.fromFoldable --- src/Pathy/Parser.purs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Pathy/Parser.purs b/src/Pathy/Parser.purs index 8e5b778..bea80e7 100644 --- a/src/Pathy/Parser.purs +++ b/src/Pathy/Parser.purs @@ -10,6 +10,7 @@ module Pathy.Parser import Prelude +import Data.Array (foldl) import Data.Array as A import Data.Either (Either(..), either) import Data.List (List(..), (:)) @@ -18,8 +19,8 @@ import Data.Maybe (Maybe(..)) import Data.String as S import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES -import Pathy.Path (AbsDir, AbsFile, Path, RelDir, RelFile, currentDir, extendPath, parentOf, rootDir) import Pathy.Name (Name(..)) +import Pathy.Path (AbsDir, AbsFile, Path, RelDir, RelFile, currentDir, extendPath, parentOf, rootDir) import Pathy.Phantom (Dir) newtype Parser = Parser @@ -43,7 +44,7 @@ posixParser = Parser \relDir absDir relFile absFile z -> isAbs = S.take 1 p == "/" isFile = S.takeRight 1 p /= "/" -- NOTE: if we have `/foo/././//bar/` we will parse that as if it was `/foo/bar/` - segs = L.fromFoldable $ A.reverse $ A.mapMaybe NES.fromString $ S.split (S.Pattern "/") p + segs = asReversedList $ A.mapMaybe NES.fromString $ S.split (S.Pattern "/") p in case isAbs, isFile of true, true -> buildPath z rootDir (either (const z) absFile) segs @@ -51,6 +52,11 @@ posixParser = Parser \relDir absDir relFile absFile z -> false, true -> buildPath z currentDir (either (const z) relFile) segs false, false -> buildPath z currentDir (either relDir relDir) segs +-- optimised version of `Array.reverse >>> List.fromFoldable` +asReversedList :: forall a. Array a -> L.List a +asReversedList = + foldl (flip L.Cons) L.Nil + buildPath :: forall z a b . z From a3655caa11b56bddc146fdff669a58e5cb50ecc3 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 23 Feb 2018 13:08:50 +0400 Subject: [PATCH 43/59] add comment about `/../` case in canonicalize --- src/Pathy/Path.purs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Pathy/Path.purs b/src/Pathy/Path.purs index fec2a60..c288cab 100644 --- a/src/Pathy/Path.purs +++ b/src/Pathy/Path.purs @@ -169,7 +169,10 @@ canonicalize p = fromMaybe p (go p) Init -> Nothing p'@(ParentOf Init) -> - foldRelOrAbs (const Nothing) (const (Just Init)) p' + foldRelOrAbs + (const Nothing) + (const (Just Init)) -- This normalizes `/../` case into `/` + p' ParentOf (In p' _) -> -- Coercion is safe as `ParentOf` can only appear where `b' ~ Dir` Just $ (unsafeCoerce :: Path a Dir -> Path a b') (canonicalize p') From 1de38623b648b3e6a6b2acd342f82ebd814a0786 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 23 Feb 2018 13:09:48 +0400 Subject: [PATCH 44/59] add @(ParentOf _) to canonicalize --- src/Pathy/Path.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Pathy/Path.purs b/src/Pathy/Path.purs index c288cab..667085d 100644 --- a/src/Pathy/Path.purs +++ b/src/Pathy/Path.purs @@ -176,7 +176,7 @@ canonicalize p = fromMaybe p (go p) ParentOf (In p' _) -> -- Coercion is safe as `ParentOf` can only appear where `b' ~ Dir` Just $ (unsafeCoerce :: Path a Dir -> Path a b') (canonicalize p') - ParentOf p' -> + ParentOf p'@(ParentOf _) -> case go p' of Just p'' -> Just $ canonicalize (ParentOf p'') Nothing -> Nothing From 080dbfaebb12ac84677163ae60b55bbfaa88e357 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 23 Feb 2018 13:41:35 +0400 Subject: [PATCH 45/59] add `peel' = canonicalize >>> peel` --- src/Pathy/Path.purs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Pathy/Path.purs b/src/Pathy/Path.purs index 667085d..563e656 100644 --- a/src/Pathy/Path.purs +++ b/src/Pathy/Path.purs @@ -207,10 +207,15 @@ foldPath r f g = case _ of -- | Peels off the last directory and the terminal file or directory name -- | from the path. Returns `Nothing` if the path is `rootDir` / `currentDir` or --- | some `parentOf p`. +-- | some `parentOf p`, so you might wanna [canonicalize](#v:canonicalize) first, +-- | or use [`peel'`](#v:peel'). peel :: forall a b. Path a b -> Maybe (Tuple (Path a Dir) (Name b)) peel = foldPath Nothing (const Nothing) (\p n -> Just (Tuple p n)) +-- | Same as [`peel`](#v:peel) but input is first [canonicalized](#v:canonicalize). +peel' :: forall a b. IsRelOrAbs a => Path a b -> Maybe (Tuple (Path a Dir) (Name b)) +peel' = canonicalize >>> peel + -- | Peels off the last director and terminal file from a path. Unlike the -- | general `peel` function this is guaranteed to return a result, as `File` -- | paths are known to have a name. From 3abd056b92018e8c12eb1df285203f0f9657582c Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 23 Feb 2018 13:48:17 +0400 Subject: [PATCH 46/59] use foldRelOrAbs instead of onRelOrAbs in printPath --- src/Pathy/Printer.purs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Pathy/Printer.purs b/src/Pathy/Printer.purs index b3fb1eb..0d6c331 100644 --- a/src/Pathy/Printer.purs +++ b/src/Pathy/Printer.purs @@ -23,7 +23,7 @@ import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES import Partial.Unsafe (unsafePartial) import Pathy.Path (Path, canonicalize, foldPath, ()) -import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, foldDirOrFile, foldRelOrAbs, onRelOrAbs) +import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, foldDirOrFile, foldRelOrAbs) import Pathy.Sandboxed (SandboxedPath, sandboxRoot, unsandbox) -- | A `Printer` defines options for printing paths. @@ -80,7 +80,7 @@ printPath r sp = in printPathRep r - (onRelOrAbs (\_ p' -> canonicalize (root p')) (flip const) p) + (foldRelOrAbs (\p' -> canonicalize (root p')) id p) -- | Prints a `SandboxedPath` into its canonical `String` representation, using -- | the specified printer. This will print a relative path if `b ~ Rel`, which From 6780a87123bb308338b8a862bba0f201ccb8f663 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 23 Feb 2018 13:58:25 +0400 Subject: [PATCH 47/59] Add property to relativeTo comment --- src/Pathy/Path.purs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Pathy/Path.purs b/src/Pathy/Path.purs index 563e656..aade80b 100644 --- a/src/Pathy/Path.purs +++ b/src/Pathy/Path.purs @@ -269,7 +269,12 @@ setExtension p ext = rename (alterExtension (const (NES.fromString ext))) p infixl 6 setExtension as <.> --- | Makes a path relative to a reference path. +-- | Makes a path relative to a reference path. This function is best +-- | explaned using this property: +-- | +-- | ```purescript +-- | a == r a `relativeTo` r +-- | ``` relativeTo :: forall b. Path Abs b -> Path Abs Dir -> Path Rel b relativeTo p rp = coeB $ step Init (canonicalize (coeD p)) (canonicalize rp) where From 68e9764a53b202e2d08e9a85b944da0e724c63b3 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 23 Feb 2018 14:02:44 +0400 Subject: [PATCH 48/59] simplify canonicalize ParentOf ParentOf case --- src/Pathy/Path.purs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Pathy/Path.purs b/src/Pathy/Path.purs index aade80b..8d4959f 100644 --- a/src/Pathy/Path.purs +++ b/src/Pathy/Path.purs @@ -177,9 +177,7 @@ canonicalize p = fromMaybe p (go p) -- Coercion is safe as `ParentOf` can only appear where `b' ~ Dir` Just $ (unsafeCoerce :: Path a Dir -> Path a b') (canonicalize p') ParentOf p'@(ParentOf _) -> - case go p' of - Just p'' -> Just $ canonicalize (ParentOf p'') - Nothing -> Nothing + canonicalize <<< ParentOf <$> go p' In p' n -> flip In n <$> go p' From 9d4b0160d21b550938824edbc179bc0a609fa696 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 23 Feb 2018 14:15:58 +0400 Subject: [PATCH 49/59] update comments remove peel' --- src/Pathy/Path.purs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Pathy/Path.purs b/src/Pathy/Path.purs index 8d4959f..f0b6c21 100644 --- a/src/Pathy/Path.purs +++ b/src/Pathy/Path.purs @@ -205,15 +205,10 @@ foldPath r f g = case _ of -- | Peels off the last directory and the terminal file or directory name -- | from the path. Returns `Nothing` if the path is `rootDir` / `currentDir` or --- | some `parentOf p`, so you might wanna [canonicalize](#v:canonicalize) first, --- | or use [`peel'`](#v:peel'). +-- | some `parentOf p`, so you might wanna [`canonicalize`](#v:canonicalize) path first. peel :: forall a b. Path a b -> Maybe (Tuple (Path a Dir) (Name b)) peel = foldPath Nothing (const Nothing) (\p n -> Just (Tuple p n)) --- | Same as [`peel`](#v:peel) but input is first [canonicalized](#v:canonicalize). -peel' :: forall a b. IsRelOrAbs a => Path a b -> Maybe (Tuple (Path a Dir) (Name b)) -peel' = canonicalize >>> peel - -- | Peels off the last director and terminal file from a path. Unlike the -- | general `peel` function this is guaranteed to return a result, as `File` -- | paths are known to have a name. @@ -256,12 +251,16 @@ renameTraverse f = case _ of p -> pure p -- | Sets the extension on the terminal segment of a path. If the path is --- | `rootDir` / `currentDir` or some `parentOf p` this will have no effect. If --- | the passed string is empty, this will remove any existing extension. +-- | `rootDir` / `currentDir` or some `parentOf p` this will have no effect, +-- | so in some cases you might need to [`canonicalize`](#v:canonicalize) +-- | path first. If the passed string is empty, this will remove any existing +-- | extension. -- | -- | ```purescript -- | file "image" <.> "png" -- | ``` +-- | See [`splitName`](Pathy.Name#v:splitName) and [`alterExtension`](Pathy.Name#v:alterExtension) +-- | fore more examples. setExtension :: forall a b. Path a b -> String -> Path a b setExtension p ext = rename (alterExtension (const (NES.fromString ext))) p From 04466b6772335f32b91a6238778b43084f1744fa Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 23 Feb 2018 17:57:49 +0400 Subject: [PATCH 50/59] fix windowsPrinter --- src/Pathy/Printer.purs | 39 +++++++++++++++++++++++++++------------ test/Main.purs | 9 ++++++++- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/Pathy/Printer.purs b/src/Pathy/Printer.purs index 0d6c331..4ff621e 100644 --- a/src/Pathy/Printer.purs +++ b/src/Pathy/Printer.purs @@ -15,15 +15,16 @@ module Pathy.Printer import Prelude import Data.Foldable (fold) -import Data.Maybe (Maybe, maybe) +import Data.Maybe (Maybe(..), maybe) import Data.Monoid (class Monoid) import Data.Newtype (class Newtype, un, unwrap) import Data.String as Str import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES import Partial.Unsafe (unsafePartial) +import Pathy.Name (Name) import Pathy.Path (Path, canonicalize, foldPath, ()) -import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, foldDirOrFile, foldRelOrAbs) +import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Dir, foldDirOrFile, foldRelOrAbs, kind DirOrFile, kind RelOrAbs) import Pathy.Sandboxed (SandboxedPath, sandboxRoot, unsandbox) -- | A `Printer` defines options for printing paths. @@ -45,7 +46,7 @@ type Printer = -- | A printer for POSIX paths. posixPrinter :: Printer posixPrinter = - { root: maybe "/" (\name -> "/" <> NES.toString name) + { root: maybe "/" (\name -> "/" <> NES.toString (un Escaper posixEscaper name)) , current: NES.singleton '.' , up: NES.singleton '.' <> NES.singleton '.' , sep: NES.singleton '/' @@ -114,17 +115,31 @@ printPathRep => Printer -> Path a b -> String -printPathRep printer p = go p +printPathRep printer inputPath = go inputPath where go :: forall b'. IsDirOrFile b' => Path a b' -> String - go = - foldPath - (NES.toString (foldRelOrAbs (const (printer.current <> printer.sep)) (const printer.sep) p)) - (\p' -> go p' <> NES.toString (printer.up <> printer.sep)) - (\p' -> - foldDirOrFile - (\d -> go p' <> printSegment printer d <> NES.toString printer.sep) - (\f -> go p' <> printSegment printer f)) + go = foldPath caseCurrent caseParentOf caseIn + + isAbs :: Boolean + isAbs = foldRelOrAbs (const false) (const true) inputPath + + caseCurrent :: String + caseCurrent = if isAbs + then printer.root Nothing + else NES.toString $ printer.current <> printer.sep + + caseParentOf :: Path a Dir -> String + caseParentOf p = go p <> NES.toString (printer.up <> printer.sep) + + caseIn :: forall b'. IsDirOrFile b' => Path a Dir -> Name b' -> String + caseIn p name = name # foldDirOrFile + (\dirName -> p # foldPath + (if isAbs + then printer.root (Just $ unwrap dirName) <> NES.toString printer.sep + else caseCurrent <> printSegment printer dirName <> NES.toString printer.sep) + (\p' -> caseParentOf p' <> printSegment printer dirName <> NES.toString printer.sep) + (\p' n' -> caseIn p' n' <> printSegment printer dirName <> NES.toString printer.sep)) + (\fileName -> go p <> printSegment printer fileName) -- | Prints a name as a `String` using the escaper from the specified printer. printSegment :: forall name. Newtype name NonEmptyString => Printer -> name -> String diff --git a/test/Main.purs b/test/Main.purs index f1a0b83..75eabec 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -12,7 +12,7 @@ import Data.String as Str import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES import Data.Symbol (SProxy(..)) -import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Name(..), Path, Rel, alterExtension, canonicalize, currentDir, debugPrintPath, dir, extension, file, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, unsandbox, (<..>), (<.>), (), joinName, splitName) +import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Name(..), Path, Rel, alterExtension, canonicalize, currentDir, debugPrintPath, dir, extension, file, joinName, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, splitName, unsandbox, windowsPrinter, (<..>), (<.>), ()) import Pathy.Gen as PG import Pathy.Name (reflectName) import Test.QuickCheck ((===)) @@ -146,6 +146,10 @@ main = do test' "() - two directories" (dirFoo dirBar) "./foo/bar/" + + test "windowsPrinter" + (printWindowsPath $ rootDir dir (SProxy :: SProxy "C") dirBar) + "C:\\bar\\" test' "() - file with two parents" (dirFoo @@ -369,3 +373,6 @@ main = do printTestPath :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> String printTestPath p = debugPrintPath posixPrinter p + +printWindowsPath :: forall a b. IsRelOrAbs a => IsDirOrFile b => Path a b -> String +printWindowsPath p = debugPrintPath windowsPrinter p From 192c9dbbafbcb0b172f3d6168212241cf197ea15 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 23 Feb 2018 15:34:09 +0000 Subject: [PATCH 51/59] Update readme examples, etc --- README.md | 95 ++++++++++++++++++++++++------------------------------- 1 file changed, 41 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 67cf265..80d88f1 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,6 @@ A type-safe abstraction for platform-independent file system paths. -# Example - -```purescript -fullPath = rootDir dir "baz" file "foo.png" -``` - -See the [tests file](/test/Main.purs) for various example usages more. - -# Getting Started - ## Installation ```bash @@ -23,7 +13,7 @@ bower install purescript-pathy ``` ```purescript -import Data.Path.Pathy +import Pathy ``` ## Introduction @@ -34,9 +24,8 @@ Many path libraries provide a single abstraction to deal with file system paths. * The distinction between relative and absolute paths. * The distinction between paths denoting file resources and paths denoting directories. - * The distinction between paths that are secure (sandboxed to some location in the file system) and those that are insecure. -*Pathy* also uses a single abstraction for file system paths, called `Path`, but uses *phantom types* to keep track of the above distinctions. +Pathy also uses a single abstraction for file system paths, called `Path`, but uses *phantom types* to keep track of the above distinctions. This approach lets you write code that performs type-safe composition of relative, absolute, file, and directory paths, and makes sure you never use paths in an unsafe fashion. Bogus and insecure operations simply aren't allowed by the type system! @@ -46,48 +35,50 @@ Many paths come from user-input or configuration data. Pathy can parse such stri Building path liberals is easy. You will typically build path literals from the following components: - * `rootDir` — The root directory of an absolute path. - * `currentDir` — The current directory (AKA the "working directory"), useful for building relative paths. - * `file` — A file (in the current directory). - * `dir` — A directory (in the current directory). - * `()` — Adds a relative path to the end of a (relative or absolute) path. - * `(<.>)` — Sets the extension of a file path. - * `(<..>)` — Ascends one level in a directory, then descends into the specified relative path. + * `rootDir` – The root directory of an absolute path. + * `currentDir` – The current directory (AKA the "working directory"), useful for building relative paths. + * `file` – A file (in the current directory). + * `dir` – A directory (in the current directory). + * `()` – Adds a relative path to the end of a (relative or absolute) path. + * `(<.>)` – Sets the extension of a file path. + * `(<..>)` – Ascends one level in a directory, then descends into the specified relative path. + +All path segments (`file` / `dir`) names are required to be non-empty. This is enforced by `Name` being constructed from a `NonEmptyString`. At compile time, we can have provably non-empty strings by using `Symbol`s and a bit of type class trickery: -For example: +``` purescript +dirFoo :: Name Dir +dirFoo = dir (SProxy :: SProxy "foo") +``` + +Here we're using a symbol proxy (`SProxy`) and then typing it to explicitly carry the name that we want to use for our path at runtime. There is also a `dir'` and `file'` variation on the function that accepts normal `Name` values, so if you are not constructing a path at compile-time, you'd be using these instead. + +Some example compile-time path constructions: ```purescript -let - path1 = rootDir dir "foo" dir "bar" file "baz.boo" - path2 = currentDir dir "foo" -in do - trace $ show $ printPath path1 - trace $ show $ printPath path2 +path1 = rootDir dir (SProxy :: SProxy "foo") dir (SProxy :: SProxy "bar") file (SProxy :: SProxy "baz.boo") +path2 = currentDir dir (SProxy :: SProxy "foo") ``` -Pathy doesn't let you create combinators that don't make sense, such as: +Thanks to the phantom type parameters, Pathy doesn't let you create path combinations that don't make sense. The following examples will be rejected at compile time: ```purescript -rootDir rootDir +rootDir rootDir currentDir rootDir -file "foo" file "bar" -file "foo" dir "bar" +file (SProxy :: SProxy "foo") file (SProxy :: SProxy "bar") +file (SProxy :: SProxy "foo") dir (SProxy :: SProxy "bar") ``` -All these combinations will be disallowed at compile time! - ### The Path Type -The `Path a b s` type has three type parameters: +The `Path a b` type has two type parameters: - * `a` — This may be `Abs` or `Rel`, indicating whether the path is absolute or relative. - * `b` — This may be `Dir` or `File`, indicating whether the path is a file or directory. - * `s` — This may be `Sandboxed` or `Unsandboxed`, indicating whether the path has been sandboxed yet or not. + * `a` – This may be `Abs` or `Rel`, indicating whether the path is absolute or relative. + * `b` – This may be `Dir` or `File`, indicating whether the path is a file or directory. You should try to make the `Path` functions that you write as generic as possible. If you have a function that only cares if a path refers to a file, then you can write it like this: ```purescript -myFunction :: forall a s. Path a File s -> ... +myFunction :: forall a. Path a File -> ... myFunction p = ... ``` @@ -97,38 +88,34 @@ By universally quantifying over the type parameters you don't care about, you en To parse a string into a `Path`, you can use the `parsePath` function, which expects you to handle four cases: - * `Path Rel File Unsandboxed` - * `Path Abs File Unsandboxed` - * `Path Rel Dir Unsandboxed` - * `Path Abs Dir Unsandboxed` + * `Path Rel File` + * `Path Abs File` + * `Path Rel Dir` + * `Path Abs Dir` If you need a specific case, you can use helper functions such as `parseRelFile`, which return a `Maybe`. -### Print Paths to Strings - -You can print any path as a `String` by calling the `printPath` function. - -For security reasons, you can only perform this operation if you have *sandboxed* the path. Sandboxing a path ensures that users cannot escape a sandbox directory that you specify; it's the right thing to do! +The `parsePath` function also expects a `Parser` argument so that different path formats can be parsed into the common `Path` type. ### Sandboxing -Pathy makes it easy to create relative paths, even paths that ascend into parent directories of relative paths. - -With this power comes danger: if you parse a user string, the user may be able to escape any arbitrary directory. +Pathy makes it easy to create relative paths, even paths that ascend into parent directories of relative paths. With this power comes danger: if you parse a user string, the user may be able to escape any arbitrary directory. Pathy solves this security problem by *disallowing* conversion from a `Path` to a `String` until the `Path` has been *sandboxed*. To sandbox a path, you just call `sandbox` and provide the sandbox directory, as well as the path to sandbox: ```purescript -sandbox (rootDir dir "foo") (rootDir dir "foo" dir "bar") +sandbox + (rootDir dir (SProxy :: SProxy "foo")) -- sandbox root + (rootDir dir (SProxy :: SProxy "foo") dir (SProxy :: SProxy "bar")) -- path to sandbox ``` -This returns a `Maybe`, which is either equal to `Nothing` if the tainted path escapes the sandbox, or `Just p`, where `p` is the tainted path, relative to the sandbox path. +This returns a `Maybe`, which is `Nothing` if the tainted path escapes the sandbox. -After you have sandboxed a foreign path, you may call `printPath` on it. There's no need to remember this rule because it's enforced at compile-time by phantom types! +After you have sandboxed a foreign path, you may call `printPath` on it, which will print the path absolutely. -All the path literals you build by hand are automatically sandboxed, unless you call `parentDir'` on them. +There is also the option to `unsafePrintPath`. This is labelled as being unsafe as it may be depending on how it is used - for example, if a path was sandboxed against some path other than the current working directory, but then used when launching a command in the current working directory, it may still refer to a location that it should not have access to. ### Renaming, Transforming, Etc. From 63882284355a5f614ddbc0eb5a067dd950ae408e Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Fri, 23 Feb 2018 15:11:23 +0000 Subject: [PATCH 52/59] Add some tests for windows path printing --- test/Main.purs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/test/Main.purs b/test/Main.purs index 75eabec..9d7d6e7 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -12,7 +12,7 @@ import Data.String as Str import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES import Data.Symbol (SProxy(..)) -import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Name(..), Path, Rel, alterExtension, canonicalize, currentDir, debugPrintPath, dir, extension, file, joinName, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, splitName, unsandbox, windowsPrinter, (<..>), (<.>), ()) +import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Name(..), Path, Rel, alterExtension, canonicalize, currentDir, debugPrintPath, dir, extension, file, joinName, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, sandboxAny, splitName, unsandbox, windowsPrinter, (<..>), (<.>), ()) import Pathy.Gen as PG import Pathy.Name (reflectName) import Test.QuickCheck ((===)) @@ -71,7 +71,7 @@ parsePrintRelFilePath = PG.genRelFilePath <#> \path -> parsePrintCheck path (parseRelFile posixParser $ printTestPath path) genAmbigiousName :: forall a. Gen.Gen (Name a) -genAmbigiousName = +genAmbigiousName = let genNES = PG.genName <#> un Name in @@ -84,7 +84,7 @@ genAmbigiousName = b <- genNES pure $ a <> (NES.singleton '.') <> b ] - + checkAlterExtensionId :: Gen.Gen QC.Result checkAlterExtensionId = do n <- genAmbigiousName @@ -146,7 +146,7 @@ main = do test' "() - two directories" (dirFoo dirBar) "./foo/bar/" - + test "windowsPrinter" (printWindowsPath $ rootDir dir (SProxy :: SProxy "C") dirBar) "C:\\bar\\" @@ -171,6 +171,22 @@ main = do (parentOf currentDir) "./../" + test """printPath windowsPrinter - C:\Users\Default\""" + (printPath windowsPrinter $ sandboxAny $ rootDir dir (SProxy :: SProxy "C") dir (SProxy :: SProxy "Users") dir (SProxy :: SProxy "Default")) + """C:\Users\Default\""" + + test """printPath posixPrinter - /C/Users/Default/""" + (printPath posixPrinter $ sandboxAny $ rootDir dir (SProxy :: SProxy "C") dir (SProxy :: SProxy "Users") dir (SProxy :: SProxy "Default")) + """/C/Users/Default/""" + + test """printPath windowsPrinter - \""" + (printPath windowsPrinter $ sandboxAny rootDir) + """\""" + + test """printPath posixPrinter - /""" + (printPath posixPrinter $ sandboxAny rootDir) + """/""" + test' "() - ./../foo/" (parentOf currentDir dirFoo) "./../foo/" From fc3965286b3c9c2cad5b9930b2304064bc55a614 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Sat, 24 Feb 2018 13:30:55 +0000 Subject: [PATCH 53/59] Only store canonical representations of paths --- src/Pathy.purs | 2 +- src/Pathy/Parser.purs | 7 ++-- src/Pathy/Path.purs | 77 ++++++++++++++-------------------------- src/Pathy/Printer.purs | 12 +++---- src/Pathy/Sandboxed.purs | 4 +-- test/Main.purs | 44 +++++++++++++---------- 6 files changed, 64 insertions(+), 82 deletions(-) diff --git a/src/Pathy.purs b/src/Pathy.purs index 29856c9..4b55070 100644 --- a/src/Pathy.purs +++ b/src/Pathy.purs @@ -7,7 +7,7 @@ module Pathy , module Pathy.Sandboxed ) where -import Pathy.Path (AbsDir, AbsFile, AbsPath, AnyPath, Path, RelDir, RelFile, RelPath, appendPath, canonicalize, currentDir, dir, dir', extendPath, file, file', fileName, foldPath, name, parentAppend, parentOf, peel, peelFile, refine, relativeTo, rename, renameTraverse, rootDir, setExtension, (<..>), (<.>), ()) +import Pathy.Path (AbsDir, AbsFile, AbsPath, AnyPath, Path, RelDir, RelFile, RelPath, appendPath, currentDir, dir, dir', extendPath, file, file', fileName, foldPath, name, parentAppend, parentOf, peel, peelFile, refine, relativeTo, rename, renameTraverse, rootDir, setExtension, (<..>), (<.>), ()) import Pathy.Name (Name(..), joinName, splitName, alterExtension, extension) import Pathy.Printer (Escaper(..), Printer, debugPrintPath, posixPrinter, printPath, unsafePrintPath, windowsPrinter) import Pathy.Parser (Parser(..), parseAbsDir, parseAbsFile, parsePath, parseRelDir, parseRelFile, posixParser) diff --git a/src/Pathy/Parser.purs b/src/Pathy/Parser.purs index bea80e7..e7f4605 100644 --- a/src/Pathy/Parser.purs +++ b/src/Pathy/Parser.purs @@ -21,9 +21,9 @@ import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES import Pathy.Name (Name(..)) import Pathy.Path (AbsDir, AbsFile, Path, RelDir, RelFile, currentDir, extendPath, parentOf, rootDir) -import Pathy.Phantom (Dir) +import Pathy.Phantom (class IsRelOrAbs, Dir) -newtype Parser = Parser +newtype Parser = Parser ( forall z . (RelDir -> z) -> (AbsDir -> z) @@ -59,7 +59,8 @@ asReversedList = buildPath :: forall z a b - . z + . IsRelOrAbs a + => z -> Path a Dir -> (Either (Path a Dir) (Path a b) -> z) -> List NonEmptyString diff --git a/src/Pathy/Path.purs b/src/Pathy/Path.purs index f0b6c21..dbf5b59 100644 --- a/src/Pathy/Path.purs +++ b/src/Pathy/Path.purs @@ -17,7 +17,6 @@ module Pathy.Path , extendPath , appendPath, () , parentAppend, (<..>) - , canonicalize , foldPath , peel , peelFile @@ -34,14 +33,14 @@ import Prelude import Data.Either (Either) import Data.Identity (Identity(..)) -import Data.Maybe (Maybe(..), fromMaybe) +import Data.Maybe (Maybe(..), maybe) import Data.Newtype (un) import Data.String.NonEmpty as NES import Data.Symbol (SProxy) -import Data.Tuple (Tuple(..)) +import Data.Tuple (Tuple(..), fst, snd) import Partial.Unsafe (unsafeCrashWith) import Pathy.Name (class IsName, Name(..), alterExtension, reflectName) -import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel, foldDirOrFile, foldRelOrAbs, onDirOrFile, kind DirOrFile, kind RelOrAbs) +import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel, foldDirOrFile, foldRelOrAbs, onDirOrFile, onRelOrAbs, kind DirOrFile, kind RelOrAbs) import Unsafe.Coerce (unsafeCoerce) -- | A type that describes a Path. All flavors of paths are described by this @@ -56,14 +55,10 @@ import Unsafe.Coerce (unsafeCoerce) -- | `currentDir`, `file`, `dir`, `()`, and `parsePath`. -- | -- | This ADT allows invalid paths (e.g. paths inside files), but there is no --- | possible way for such paths to be constructed by user-land code. The only --- | "invalid path" that may be constructed is using the `parentOf` function, --- | e.g. `parentOf rootDir`, or by parsing an equivalent string such as --- | `/../`, but such paths may not be rendered to strings until they are first --- | sandboxed to some directory. +-- | possible way for such paths to be constructed by user-land code. data Path (a :: RelOrAbs) (b :: DirOrFile) = Init - | ParentOf (Path a Dir) + | ParentOf (Path Rel Dir) | In (Path a Dir) (Name b) derive instance eqPath :: Eq (Path a b) @@ -129,8 +124,13 @@ dir' :: Name Dir -> Path Rel Dir dir' = In currentDir -- | Creates a path that points to the parent directory of the specified path. -parentOf :: forall a. Path a Dir -> Path a Dir -parentOf p = ParentOf p +-- | +-- | Calling `parentOf` on `rootDir` will return `rootDir`. +parentOf :: forall a. IsRelOrAbs a => Path a Dir -> Path a Dir +parentOf = + onRelOrAbs + (\coe p -> maybe (ParentOf p) (coe <<< fst) (peel p)) + (\coe -> coe <<< maybe Init fst <<< peel) -- | Extends a path with a file or directory under the current path. extendPath :: forall a b. Path a Dir -> Name b -> Path a b @@ -138,12 +138,12 @@ extendPath p = In p -- | Given a directory path, appends a relative path to extend the original -- | path. -appendPath :: forall a b. Path a Dir -> Path Rel b -> Path a b +appendPath :: forall a b. IsRelOrAbs a => Path a Dir -> Path Rel b -> Path a b appendPath = case _, _ of Init, Init -> Init ParentOf p, Init -> ParentOf (p Init) In p (Name d), Init -> In (p Init) (Name d) - p1, ParentOf p2 -> ParentOf (p1 p2) + p1, ParentOf p2 -> (unsafeCoerce :: Path a Dir -> Path a b) $ parentOf (p1 p2) p1, In p2 n -> In (p1 p2) n infixl 6 appendPath as @@ -152,35 +152,13 @@ infixl 6 appendPath as -- | the specified path. -- | -- | ```purescript --- | canonicalize (rootDir dir "foo" <..> dir "bar") = rootDir dir "bar" +-- | rootDir dir "foo" <..> dir "bar" = rootDir dir "bar" -- | ``` -parentAppend :: forall a b. Path a Dir -> Path Rel b -> Path a b +parentAppend :: forall a b. IsRelOrAbs a => Path a Dir -> Path Rel b -> Path a b parentAppend d p = parentOf d p infixl 6 parentAppend as <..> --- | Canonicalizes a path, by reducing things in the form `/x/../` to just --- | `/x/`. Paths like `/../` will be normalized to `/`. -canonicalize :: forall a b. IsRelOrAbs a => Path a b -> Path a b -canonicalize p = fromMaybe p (go p) - where - go :: forall b'. Path a b' -> Maybe (Path a b') - go = case _ of - Init -> - Nothing - p'@(ParentOf Init) -> - foldRelOrAbs - (const Nothing) - (const (Just Init)) -- This normalizes `/../` case into `/` - p' - ParentOf (In p' _) -> - -- Coercion is safe as `ParentOf` can only appear where `b' ~ Dir` - Just $ (unsafeCoerce :: Path a Dir -> Path a b') (canonicalize p') - ParentOf p'@(ParentOf _) -> - canonicalize <<< ParentOf <$> go p' - In p' n -> - flip In n <$> go p' - -- | A fold over `Path`s. Since `Path` has private constructors, this allows for -- | functions to be written over its constructors, similar to a total pattern -- | match. @@ -194,7 +172,7 @@ canonicalize p = fromMaybe p (go p) foldPath :: forall a b r . r - -> (Path a Dir -> r) + -> (Path Rel Dir -> r) -> (Path a Dir -> Name b -> r) -> Path a b -> r @@ -205,7 +183,7 @@ foldPath r f g = case _ of -- | Peels off the last directory and the terminal file or directory name -- | from the path. Returns `Nothing` if the path is `rootDir` / `currentDir` or --- | some `parentOf p`, so you might wanna [`canonicalize`](#v:canonicalize) path first. +-- | a relative path that is ascending (`../`) peel :: forall a b. Path a b -> Maybe (Tuple (Path a Dir) (Name b)) peel = foldPath Nothing (const Nothing) (\p n -> Just (Tuple p n)) @@ -227,19 +205,18 @@ name = foldPath Nothing (const Nothing) (const Just) -- | this is guaranteed to return a result, as `File` paths are known to have a -- | name. fileName :: forall a. Path a File -> Name File -fileName = case _ of - Init -> unsafeCrashWith "`Init` in Pathy.fileName (this should be impossible)" - ParentOf _ -> unsafeCrashWith "`ParentOf` in Pathy.fileName (this should be impossible)" - In _ n -> n +fileName = snd <<< peelFile -- | Attempts to rename the terminal segment of a path. If the path is --- | `rootDir` / `currentDir` or some `parentOf p` this will have no effect. +-- | `rootDir` / `currentDir` or a relative path that is ascending (`../`) this +-- | will have no effect. rename :: forall a b. (Name b -> Name b) -> Path a b -> Path a b rename f = un Identity <<< renameTraverse (pure <<< f) -- | Attempts to rename the terminal segment of a path using a function that -- | returns the result in some `Applicative`. If the path is `rootDir` / --- | `currentDir` or some `parentOf p` this will have no effect. +-- | `currentDir` or a relative path that is ascending (`../`) this will +-- | have no effect. renameTraverse :: forall f a b . Applicative f @@ -251,10 +228,8 @@ renameTraverse f = case _ of p -> pure p -- | Sets the extension on the terminal segment of a path. If the path is --- | `rootDir` / `currentDir` or some `parentOf p` this will have no effect, --- | so in some cases you might need to [`canonicalize`](#v:canonicalize) --- | path first. If the passed string is empty, this will remove any existing --- | extension. +-- | `rootDir` / `currentDir` or a relative path that is ascending (`../`) this +-- | will have no effect. -- | -- | ```purescript -- | file "image" <.> "png" @@ -273,7 +248,7 @@ infixl 6 setExtension as <.> -- | a == r a `relativeTo` r -- | ``` relativeTo :: forall b. Path Abs b -> Path Abs Dir -> Path Rel b -relativeTo p rp = coeB $ step Init (canonicalize (coeD p)) (canonicalize rp) +relativeTo p = coeB <<< step Init (coeD p) where step :: Path Rel Dir -> Path Abs Dir -> Path Abs Dir -> Path Rel Dir step acc = case _, _ of diff --git a/src/Pathy/Printer.purs b/src/Pathy/Printer.purs index 4ff621e..709a402 100644 --- a/src/Pathy/Printer.purs +++ b/src/Pathy/Printer.purs @@ -23,8 +23,8 @@ import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES import Partial.Unsafe (unsafePartial) import Pathy.Name (Name) -import Pathy.Path (Path, canonicalize, foldPath, ()) -import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Dir, foldDirOrFile, foldRelOrAbs, kind DirOrFile, kind RelOrAbs) +import Pathy.Path (Path, foldPath, ()) +import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Dir, Rel, foldDirOrFile, foldRelOrAbs, kind DirOrFile, kind RelOrAbs) import Pathy.Sandboxed (SandboxedPath, sandboxRoot, unsandbox) -- | A `Printer` defines options for printing paths. @@ -81,7 +81,7 @@ printPath r sp = in printPathRep r - (foldRelOrAbs (\p' -> canonicalize (root p')) id p) + (foldRelOrAbs (root _) id p) -- | Prints a `SandboxedPath` into its canonical `String` representation, using -- | the specified printer. This will print a relative path if `b ~ Rel`, which @@ -117,7 +117,7 @@ printPathRep -> String printPathRep printer inputPath = go inputPath where - go :: forall b'. IsDirOrFile b' => Path a b' -> String + go :: forall a' b'. IsRelOrAbs a' => IsDirOrFile b' => Path a' b' -> String go = foldPath caseCurrent caseParentOf caseIn isAbs :: Boolean @@ -128,10 +128,10 @@ printPathRep printer inputPath = go inputPath then printer.root Nothing else NES.toString $ printer.current <> printer.sep - caseParentOf :: Path a Dir -> String + caseParentOf :: Path Rel Dir -> String caseParentOf p = go p <> NES.toString (printer.up <> printer.sep) - caseIn :: forall b'. IsDirOrFile b' => Path a Dir -> Name b' -> String + caseIn :: forall a' b'. IsRelOrAbs a' => IsDirOrFile b' => Path a' Dir -> Name b' -> String caseIn p name = name # foldDirOrFile (\dirName -> p # foldPath (if isAbs diff --git a/src/Pathy/Sandboxed.purs b/src/Pathy/Sandboxed.purs index 001a3de..937c0b9 100644 --- a/src/Pathy/Sandboxed.purs +++ b/src/Pathy/Sandboxed.purs @@ -9,7 +9,7 @@ module Pathy.Sandboxed import Prelude import Data.Maybe (Maybe(..)) -import Pathy.Path (Path, canonicalize, foldPath, relativeTo, rootDir, ()) +import Pathy.Path (Path, foldPath, relativeTo, rootDir, ()) import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, onRelOrAbs) -- | The type for paths that have been sandboxed. @@ -44,7 +44,7 @@ sandbox root = map (SandboxedPath root) <<< onRelOrAbs (go (root _)) (go id) -- | This should only be used for situations where a path is already constrained -- | within a system so that access to `/` is safe - for instance, in URIs. sandboxAny :: forall a b. IsRelOrAbs a => Path a b -> SandboxedPath a b -sandboxAny p = SandboxedPath rootDir (canonicalize p) +sandboxAny p = SandboxedPath rootDir p -- | Returns the location a `SandboxedPath` was sandboxed to. sandboxRoot :: forall a b. SandboxedPath a b -> Path Abs Dir diff --git a/test/Main.purs b/test/Main.purs index 9d7d6e7..405b509 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -12,7 +12,7 @@ import Data.String as Str import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES import Data.Symbol (SProxy(..)) -import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Name(..), Path, Rel, alterExtension, canonicalize, currentDir, debugPrintPath, dir, extension, file, joinName, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, sandboxAny, splitName, unsandbox, windowsPrinter, (<..>), (<.>), ()) +import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Name(..), Path, Rel, alterExtension, currentDir, debugPrintPath, dir, extension, file, joinName, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, sandboxAny, splitName, unsandbox, windowsPrinter, (<..>), (<.>), ()) import Pathy.Gen as PG import Pathy.Name (reflectName) import Test.QuickCheck ((===)) @@ -99,20 +99,18 @@ checkRelative :: forall b. IsDirOrFile b => Gen.Gen (Path Abs b) -> Gen.Gen QC.R checkRelative gen = do p1 <- gen p2 <- PG.genAbsDirPath - let cp1 = canonicalize p1 - let cp2 = canonicalize p2 - let rel = cp1 `relativeTo` cp2 - let cp1' = canonicalize (cp2 rel) + let rel = p1 `relativeTo` p2 + let p1' = p2 rel pure - if cp1 == cp1' + if p1 == p1' then QC.Success else QC.Failed $ "`relativeTo` property did not hold:" - <> "\n\tcp1: " <> printTestPath cp1 - <> "\n\tcp2: " <> printTestPath cp2 + <> "\n\tp1: " <> printTestPath p1 + <> "\n\tp2: " <> printTestPath p2 <> "\n\trel: " <> printTestPath rel - <> "\n\tcp1': " <> printTestPath cp1' + <> "\n\tp1': " <> printTestPath p1' main :: QC.QC () Unit main = do @@ -193,7 +191,7 @@ main = do test' "parentOf - ./../foo/../" ((parentOf currentDir dirFoo) (parentOf currentDir)) - "./../foo/../" + "./../" test' "(<..>) - ./../" (currentDir <..> currentDir) @@ -205,22 +203,30 @@ main = do test' "(<..>) - ./../foo/../" ((currentDir <..> dirFoo) <..> currentDir) - "./../foo/../" + "./../" - test' "canonicalize - 1 down, 1 up" - (canonicalize $ parentOf $ dirFoo) + test' "./foo/../ = ./" + (parentOf dirFoo) "./" - test' "canonicalize - 2 down, 2 up" - (canonicalize (parentOf (parentOf (dirFoo dirBar)))) + test' "./foo/bar/../../ = ./" + ((parentOf (parentOf (dirFoo dirBar)))) "./" - test' "canonicalize - 2 up from root" - (canonicalize (parentOf (parentOf rootDir))) + test' "/../../ = /" + ((parentOf (parentOf rootDir))) "/" - test "canonicalize /foo/../bar/ = /bar" - (canonicalize (rootDir dirFoo <..> dirBar)) + test "/foo/../bar/ = /bar" + ((rootDir dirFoo <..> dirBar)) + (rootDir dirBar) + + test "/foo/bar/ ../bar/ = /foo/bar/" + ((rootDir dirFoo dirBar) (currentDir <..> dirBar)) + (rootDir dirFoo dirBar) + + test "/foo/bar/ ../../bar/ = /bar/" + ((rootDir dirFoo dirBar) (currentDir <..> currentDir <..> currentDir dirBar)) (rootDir dirBar) test "relativeTo rootDir rootDir = currentDir" From ab1dc30d27b9fef69374b66a24fa830ad175bf52 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Mon, 26 Feb 2018 18:18:48 +0400 Subject: [PATCH 54/59] remove IsRelOrAbs from sandboxAny --- src/Pathy/Sandboxed.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Pathy/Sandboxed.purs b/src/Pathy/Sandboxed.purs index 937c0b9..81f0987 100644 --- a/src/Pathy/Sandboxed.purs +++ b/src/Pathy/Sandboxed.purs @@ -43,7 +43,7 @@ sandbox root = map (SandboxedPath root) <<< onRelOrAbs (go (root _)) (go id) -- | -- | This should only be used for situations where a path is already constrained -- | within a system so that access to `/` is safe - for instance, in URIs. -sandboxAny :: forall a b. IsRelOrAbs a => Path a b -> SandboxedPath a b +sandboxAny :: forall a b. Path a b -> SandboxedPath a b sandboxAny p = SandboxedPath rootDir p -- | Returns the location a `SandboxedPath` was sandboxed to. From 25a863d52bd864e72bd086389fa681e6cdd42a2b Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 27 Feb 2018 15:37:52 +0400 Subject: [PATCH 55/59] add `AnyDir` and `AnyFile` --- src/Pathy.purs | 2 +- src/Pathy/Path.purs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Pathy.purs b/src/Pathy.purs index 4b55070..261b209 100644 --- a/src/Pathy.purs +++ b/src/Pathy.purs @@ -7,7 +7,7 @@ module Pathy , module Pathy.Sandboxed ) where -import Pathy.Path (AbsDir, AbsFile, AbsPath, AnyPath, Path, RelDir, RelFile, RelPath, appendPath, currentDir, dir, dir', extendPath, file, file', fileName, foldPath, name, parentAppend, parentOf, peel, peelFile, refine, relativeTo, rename, renameTraverse, rootDir, setExtension, (<..>), (<.>), ()) +import Pathy.Path (AbsDir, AbsFile, AbsPath, AnyPath, AnyDir, AnyFile, Path, RelDir, RelFile, RelPath, appendPath, currentDir, dir, dir', extendPath, file, file', fileName, foldPath, name, parentAppend, parentOf, peel, peelFile, refine, relativeTo, rename, renameTraverse, rootDir, setExtension, (<..>), (<.>), ()) import Pathy.Name (Name(..), joinName, splitName, alterExtension, extension) import Pathy.Printer (Escaper(..), Printer, debugPrintPath, posixPrinter, printPath, unsafePrintPath, windowsPrinter) import Pathy.Parser (Parser(..), parseAbsDir, parseAbsFile, parsePath, parseRelDir, parseRelFile, posixParser) diff --git a/src/Pathy/Path.purs b/src/Pathy/Path.purs index dbf5b59..aa3aa9e 100644 --- a/src/Pathy/Path.purs +++ b/src/Pathy/Path.purs @@ -5,8 +5,10 @@ module Pathy.Path , AbsPath , RelDir , AbsDir + , AnyDir , RelFile , AbsFile + , AnyFile , rootDir , currentDir , dir @@ -85,6 +87,9 @@ type RelDir = Path Rel Dir -- | A type describing a directory whose location is absolutely specified. type AbsDir = Path Abs Dir +-- | A type describing a absolute or relative directory path. +type AnyDir = Either AbsDir RelDir + -- | A type describing a file whose location is given relative to some other, -- | unspecified directory (referred to as the "current directory"). type RelFile = Path Rel File @@ -92,6 +97,9 @@ type RelFile = Path Rel File -- | A type describing a file whose location is absolutely specified. type AbsFile = Path Abs File +-- | A type describing a absolute or relative file path. +type AnyFile = Either AbsFile RelFile + -- | The root directory, which can be used to define absolutely-located resources. rootDir :: Path Abs Dir rootDir = Init From b3f7359a390640aa196f4c3277438e190aa4d320 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 27 Feb 2018 15:38:00 +0400 Subject: [PATCH 56/59] fix warning --- src/Pathy/Name.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Pathy/Name.purs b/src/Pathy/Name.purs index 6b5f355..c3043c0 100644 --- a/src/Pathy/Name.purs +++ b/src/Pathy/Name.purs @@ -58,7 +58,7 @@ splitName (Name nameIn) = joinName :: forall n. { name :: NonEmptyString, ext :: Maybe NonEmptyString } -> Name n joinName { name, ext } = Name $ case ext of Nothing -> name - Just ext -> name <> NES.singleton '.' <> ext + Just ext' -> name <> NES.singleton '.' <> ext' -- | Retrieves the extension of a name. also see [`splitName`](#v:splitName) -- | From 032bf078d26ea8290bfaa40061a76eb6c9cae7e7 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 9 Mar 2018 19:06:52 +0400 Subject: [PATCH 57/59] add in' --- src/Pathy.purs | 2 +- src/Pathy/Path.purs | 16 ++++++++++++++-- test/Main.purs | 12 ++++++++++-- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Pathy.purs b/src/Pathy.purs index 261b209..a27743a 100644 --- a/src/Pathy.purs +++ b/src/Pathy.purs @@ -7,7 +7,7 @@ module Pathy , module Pathy.Sandboxed ) where -import Pathy.Path (AbsDir, AbsFile, AbsPath, AnyPath, AnyDir, AnyFile, Path, RelDir, RelFile, RelPath, appendPath, currentDir, dir, dir', extendPath, file, file', fileName, foldPath, name, parentAppend, parentOf, peel, peelFile, refine, relativeTo, rename, renameTraverse, rootDir, setExtension, (<..>), (<.>), ()) +import Pathy.Path (AbsDir, AbsFile, AbsPath, AnyPath, AnyDir, AnyFile, Path, RelDir, RelFile, RelPath, appendPath, currentDir, dir, dir', extendPath, file, file', in', fileName, foldPath, name, parentAppend, parentOf, peel, peelFile, refine, relativeTo, rename, renameTraverse, rootDir, setExtension, (<..>), (<.>), ()) import Pathy.Name (Name(..), joinName, splitName, alterExtension, extension) import Pathy.Printer (Escaper(..), Printer, debugPrintPath, posixPrinter, printPath, unsafePrintPath, windowsPrinter) import Pathy.Parser (Parser(..), parseAbsDir, parseAbsFile, parsePath, parseRelDir, parseRelFile, posixParser) diff --git a/src/Pathy/Path.purs b/src/Pathy/Path.purs index aa3aa9e..0841ab8 100644 --- a/src/Pathy/Path.purs +++ b/src/Pathy/Path.purs @@ -15,6 +15,7 @@ module Pathy.Path , dir' , file , file' + , in' , parentOf , extendPath , appendPath, () @@ -118,7 +119,7 @@ file = file' <<< reflectName -- | Creates a path which points to a relative file of the specified name. file' :: Name File -> Path Rel File -file' = In currentDir +file' = in' -- | Creates a path which points to a relative directory of the specified name. -- | @@ -129,7 +130,18 @@ dir = dir' <<< reflectName -- | Creates a path which points to a relative directory of the specified name. dir' :: Name Dir -> Path Rel Dir -dir' = In currentDir +dir' = in' + +-- | Creates a path which points to a relative directory or file of the specified name. +-- | In most cases [`dir'`](#v:dir') or [`file'`](#v:file') should be used instead, +-- | but it's still there in case the segment type is going to be determined based +-- | on some type variable. +-- | +-- | ``` purescript +-- | p == maybe p (\(Tuple r n) -> r in' n) (peel p) +-- | ``` +in' :: forall a. Name a -> Path Rel a +in' = In currentDir -- | Creates a path that points to the parent directory of the specified path. -- | diff --git a/test/Main.purs b/test/Main.purs index 405b509..af363b2 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -5,14 +5,15 @@ import Prelude import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, info) import Control.Monad.Eff.Exception (EXCEPTION, throw) -import Data.Maybe (Maybe(..)) +import Data.Maybe (Maybe(..), maybe) import Data.Newtype (un) import Data.NonEmpty ((:|)) import Data.String as Str import Data.String.NonEmpty (NonEmptyString) import Data.String.NonEmpty as NES import Data.Symbol (SProxy(..)) -import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Name(..), Path, Rel, alterExtension, currentDir, debugPrintPath, dir, extension, file, joinName, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, sandboxAny, splitName, unsandbox, windowsPrinter, (<..>), (<.>), ()) +import Data.Tuple (Tuple(..)) +import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Name(..), Path, Rel, alterExtension, currentDir, debugPrintPath, dir, extension, file, in', joinName, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, peel, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, sandboxAny, splitName, unsandbox, windowsPrinter, (<..>), (<.>), ()) import Pathy.Gen as PG import Pathy.Name (reflectName) import Test.QuickCheck ((===)) @@ -95,6 +96,11 @@ checkJoinSplitNameId = do n <- genAmbigiousName pure $ joinName (splitName n) === id n +checkPeelIn :: forall b. IsDirOrFile b => Gen.Gen (Path Abs b) -> Gen.Gen QC.Result +checkPeelIn gen = do + p <- gen + pure $ p === maybe p (\(Tuple r n) -> r in' n) (peel p) + checkRelative :: forall b. IsDirOrFile b => Gen.Gen (Path Abs b) -> Gen.Gen QC.Result checkRelative gen = do p1 <- gen @@ -120,6 +126,8 @@ main = do info "checking `parse <<< print` for `RelFile`" *> QC.quickCheck parsePrintRelFilePath info "checking `relativeTo` for `AbsDir`" *> QC.quickCheck (checkRelative PG.genAbsDirPath) info "checking `relativeTo` for `AbsFile`" *> QC.quickCheck (checkRelative PG.genAbsFilePath) + info "checking `p === maybe p (\\(Tuple r n) -> r in' n) (peel p)` for `AbsDir`" *> QC.quickCheck (checkPeelIn PG.genAbsDirPath) + info "checking `p === maybe p (\\(Tuple r n) -> r in' n) (peel p)` for `AbsFile`" *> QC.quickCheck (checkPeelIn PG.genAbsFilePath) info "checking `joinName <<< splitName === id`" *> QC.quickCheck checkJoinSplitNameId info "checking `alterExtension id === id`" *> QC.quickCheck checkAlterExtensionId From bb1614ee308e13ecc0541527505182b9c890d759 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 20 Mar 2018 17:15:33 +0100 Subject: [PATCH 58/59] reexport foldRelOrAbs, onRelOrAbs, foldDirOrFile, onDirOrFile from Pathy --- src/Pathy.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Pathy.purs b/src/Pathy.purs index a27743a..b77393e 100644 --- a/src/Pathy.purs +++ b/src/Pathy.purs @@ -11,5 +11,5 @@ import Pathy.Path (AbsDir, AbsFile, AbsPath, AnyPath, AnyDir, AnyFile, Path, Rel import Pathy.Name (Name(..), joinName, splitName, alterExtension, extension) import Pathy.Printer (Escaper(..), Printer, debugPrintPath, posixPrinter, printPath, unsafePrintPath, windowsPrinter) import Pathy.Parser (Parser(..), parseAbsDir, parseAbsFile, parsePath, parseRelDir, parseRelFile, posixParser) -import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel) +import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel, foldRelOrAbs, onRelOrAbs, foldDirOrFile, onDirOrFile) import Pathy.Sandboxed (SandboxedPath, sandbox, sandboxAny, sandboxRoot, unsandbox) From 4b1e07fae649ad31e766ee6a571803afc4bf2bc4 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 20 Mar 2018 17:30:07 +0100 Subject: [PATCH 59/59] add genDirName and genFileName --- src/Pathy/Gen.purs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Pathy/Gen.purs b/src/Pathy/Gen.purs index 985ddc1..5939c7b 100644 --- a/src/Pathy/Gen.purs +++ b/src/Pathy/Gen.purs @@ -6,6 +6,8 @@ module Pathy.Gen , genRelFilePath , genRelAnyPath , genName + , genDirName + , genFileName ) where import Prelude @@ -20,7 +22,7 @@ import Data.List as L import Data.NonEmpty ((:|)) import Data.String.Gen as SG import Data.String.NonEmpty (cons) -import Pathy (AbsDir, AbsFile, AbsPath, Dir, RelDir, RelFile, RelPath, ()) +import Pathy (AbsDir, AbsFile, AbsPath, Dir, File, RelDir, RelFile, RelPath, ()) import Pathy as P genName ∷ ∀ m a. MonadGen m ⇒ MonadRec m ⇒ m (P.Name a) @@ -28,6 +30,12 @@ genName = map P.Name $ cons <$> genChar <*> SG.genString genChar where genChar = Gen.oneOf $ CG.genDigitChar :| [CG.genAlpha] +genDirName :: ∀ m. MonadGen m ⇒ MonadRec m ⇒ m (P.Name Dir) +genDirName = genName + +genFileName :: ∀ m. MonadGen m ⇒ MonadRec m ⇒ m (P.Name File) +genFileName = genName + genAbsDirPath :: forall m. MonadGen m => MonadRec m => m AbsDir genAbsDirPath = Gen.sized \size → do newSize ← Gen.chooseInt 0 size