dirtree 0.0.1 → 0.1.0
raw patch · 5 files changed
+1082/−781 lines, 5 filesdep +bytestringdep +unixdep +zip-archivedep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: bytestring, unix, zip-archive
Dependency ranges changed: base
API changes (from Hackage documentation)
- System.DirTree: (-.>) :: String -> a -> (String, DirTree x a)
- System.DirTree: (-/>) :: String -> [(String, DirTree a b)] -> (String, DirTree a b)
- System.DirTree: (-|>) :: String -> a -> (String, DirTree a x)
- System.DirTree: data FileMap a
- System.DirTree: directoryFromFiles :: [(String, DirTree s a)] -> DirTree s a
- System.DirTree: flatten :: (s -> DirTree s' a') -> (a -> DirTree s' a') -> DirTree s a -> DirTree s' a'
- System.DirTree: foldDirTree :: (DirTreeNode (FileMap m) s a -> m) -> DirTree s a -> m
- System.DirTree: foldDirTree' :: Monoid m => (s -> m) -> (a -> m) -> DirTree s a -> m
- System.DirTree: foldDirTreeNode :: (r -> m) -> (s -> m) -> (a -> m) -> DirTreeNode r s a -> m
- System.DirTree: fromDeepFileList :: [(FileKey, Either s a)] -> FileMap (DirTree s a)
- System.DirTree: fromFile :: FileKey -> a -> DirTree Void a
- System.DirTree: fromFiles :: [(FileKey, Either v a)] -> Maybe (DirTree v a)
- System.DirTree: fromFiles' :: NonEmpty (FileKey, Either v a) -> DirTree v a
- System.DirTree: ifoldDirTree :: (FileKey -> DirTreeNode (FileMap m) s a -> m) -> DirTree s a -> m
- System.DirTree: ifoldDirTree' :: Monoid m => (FileKey -> s -> m) -> (FileKey -> a -> m) -> DirTree s a -> m
- System.DirTree: imapDirTree' :: (FileKey -> s -> s') -> (FileKey -> a -> a') -> DirTree s a -> DirTree s' a'
- System.DirTree: instance (Control.DeepSeq.NFData r, Control.DeepSeq.NFData s, Control.DeepSeq.NFData a) => Control.DeepSeq.NFData (System.DirTree.DirTreeNode r s a)
- System.DirTree: instance (Control.DeepSeq.NFData s, Control.DeepSeq.NFData a) => Control.DeepSeq.NFData (System.DirTree.DirTree s a)
- System.DirTree: instance (GHC.Classes.Eq r, GHC.Classes.Eq s, GHC.Classes.Eq a) => GHC.Classes.Eq (System.DirTree.DirTreeNode r s a)
- System.DirTree: instance (GHC.Classes.Eq s, GHC.Classes.Eq a) => GHC.Classes.Eq (System.DirTree.DirTree s a)
- System.DirTree: instance (GHC.Classes.Ord r, GHC.Classes.Ord s, GHC.Classes.Ord a) => GHC.Classes.Ord (System.DirTree.DirTreeNode r s a)
- System.DirTree: instance (GHC.Classes.Ord s, GHC.Classes.Ord a) => GHC.Classes.Ord (System.DirTree.DirTree s a)
- System.DirTree: instance (GHC.Show.Show a, GHC.Show.Show b) => GHC.Show.Show (System.DirTree.FileMap (System.DirTree.DirTree a b))
- System.DirTree: instance (GHC.Show.Show r, GHC.Show.Show s, GHC.Show.Show a) => GHC.Show.Show (System.DirTree.DirTreeNode r s a)
- System.DirTree: instance (GHC.Show.Show v, GHC.Show.Show c) => GHC.Show.Show (System.DirTree.DirTree v c)
- System.DirTree: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (System.DirTree.Anchored a)
- System.DirTree: instance Control.Lens.Indexed.FoldableWithIndex System.DirTree.FileKey (System.DirTree.DirTree v)
- System.DirTree: instance Control.Lens.Indexed.FunctorWithIndex System.DirTree.FileKey (System.DirTree.DirTree v)
- System.DirTree: instance Control.Lens.Indexed.TraversableWithIndex System.DirTree.FileKey (System.DirTree.DirTree v)
- System.DirTree: instance Data.Foldable.Foldable (System.DirTree.DirTree s)
- System.DirTree: instance Data.Foldable.Foldable (System.DirTree.DirTreeNode r s)
- System.DirTree: instance Data.Foldable.Foldable System.DirTree.Anchored
- System.DirTree: instance Data.Traversable.Traversable (System.DirTree.DirTree s)
- System.DirTree: instance Data.Traversable.Traversable (System.DirTree.DirTreeNode r s)
- System.DirTree: instance Data.Traversable.Traversable System.DirTree.Anchored
- System.DirTree: instance GHC.Base.Functor (System.DirTree.DirTree s)
- System.DirTree: instance GHC.Base.Functor (System.DirTree.DirTreeNode r s)
- System.DirTree: instance GHC.Base.Functor System.DirTree.Anchored
- System.DirTree: instance GHC.Base.Semigroup (System.DirTree.DirTree s a)
- System.DirTree: instance GHC.Classes.Eq a => GHC.Classes.Eq (System.DirTree.Anchored a)
- System.DirTree: instance GHC.Classes.Ord a => GHC.Classes.Ord (System.DirTree.Anchored a)
- System.DirTree: instance GHC.Generics.Generic (System.DirTree.Anchored a)
- System.DirTree: instance GHC.Generics.Generic (System.DirTree.DirTree s a)
- System.DirTree: instance GHC.Generics.Generic (System.DirTree.DirTreeNode r s a)
- System.DirTree: instance GHC.Show.Show a => GHC.Show.Show (System.DirTree.Anchored a)
- System.DirTree: itraverseDirTree :: Applicative f => (FileKey -> DirTreeNode (FileMap (f (DirTree s' a'))) s a -> f (DirTreeN s' a')) -> DirTree s a -> f (DirTree s' a')
- System.DirTree: itraverseDirTree' :: Applicative f => (FileKey -> s -> f s') -> (FileKey -> a -> f a') -> DirTree s a -> f (DirTree s' a')
- System.DirTree: lazyFollowLinks :: (FilePath -> IO a) -> DirTree Link a -> IO (DirTree Void a)
- System.DirTree: lazyReadDirTree :: (FilePath -> IO a) -> FilePath -> IO (DirTree Link a)
- System.DirTree: lookupFile :: FileKey -> DirTree v a -> Maybe (DirTree v a)
- System.DirTree: mapDirTree' :: (s -> s') -> (a -> a') -> DirTree s a -> DirTree s' a'
- System.DirTree: mapDirTreeNode :: (r -> r') -> (s -> s') -> (a -> a') -> DirTreeNode r s a -> DirTreeNode r' s' a'
- System.DirTree: toDeepFileList :: FileMap (DirTree s a) -> [(FileKey, Either s a)]
- System.DirTree: toFileNames :: FileMap a -> [String]
- System.DirTree: toFiles :: DirTree v a -> [(FileKey, Either v a)]
- System.DirTree: traverseDirTree :: Applicative f => (DirTreeNode (FileMap (f (DirTree s' a'))) s a -> f (DirTreeN s' a')) -> DirTree s a -> f (DirTree s' a')
- System.DirTree: traverseDirTree' :: Applicative m => (s -> m s') -> (a -> m a') -> DirTree s a -> m (DirTree s' a')
- System.DirTree: traverseDirTreeNode :: Functor m => (r -> m r') -> (s -> m s') -> (a -> m a') -> DirTreeNode r s a -> m (DirTreeNode r' s' a')
- System.DirTree: type DirTreeN s a = DirTreeNode (FileMap (DirTree s a)) s a
+ System.DirTree: (.*) :: String -> a -> (String, DirTree a)
+ System.DirTree: (.*.) :: String -> a -> (String, RelativeDirTree s a)
+ System.DirTree: (.*>) :: String -> s -> (String, RelativeDirTree s a)
+ System.DirTree: (./) :: String -> [(String, DirTree a)] -> (String, DirTree a)
+ System.DirTree: DirForest :: FileMap (DirTree a) -> DirForest a
+ System.DirTree: FileMap :: Map String a -> FileMap a
+ System.DirTree: Real :: a -> RelativeFile s a
+ System.DirTree: [fileMapAsMap] :: FileMap a -> Map String a
+ System.DirTree: [getInternalFileMap] :: DirForest a -> FileMap (DirTree a)
+ System.DirTree: _DirTreeNode :: AsDirTreeNode r_am1U r_aaxx a_aaxy => Prism' r_am1U (DirTreeNode r_aaxx a_aaxy)
+ System.DirTree: _Directory :: AsDirTreeNode r_am1U r_aaxx a_aaxy => Prism' r_am1U r_aaxx
+ System.DirTree: _File :: AsDirTreeNode r_am1U r_aaxx a_aaxy => Prism' r_am1U a_aaxy
+ System.DirTree: _Real :: AsRelativeFile r_amqK s_am2a a_am2b => Prism' r_amqK a_am2b
+ System.DirTree: _RelativeFile :: AsRelativeFile r_amqK s_am2a a_am2b => Prism' r_amqK (RelativeFile s_am2a a_am2b)
+ System.DirTree: _Symlink :: AsRelativeFile r_amqK s_am2a a_am2b => Prism' r_amqK s_am2a
+ System.DirTree: alterFile :: forall f a. Functor f => (Maybe (DirTree a) -> f (Maybe (DirTree a))) -> FileKey -> Maybe (DirTree a) -> f (Maybe (DirTree a))
+ System.DirTree: alterForest :: forall f a. Functor f => (Maybe (DirTree a) -> f (Maybe (DirTree a))) -> ForestFileKey -> DirForest a -> f (DirForest a)
+ System.DirTree: asRelativeDirForest :: DirForest a -> RelativeDirForest s a
+ System.DirTree: asRelativeDirTree :: DirTree a -> RelativeDirTree s a
+ System.DirTree: class AsDirTreeNode r_am1U r_aaxx a_aaxy | r_am1U -> r_aaxx a_aaxy
+ System.DirTree: class AsRelativeFile r_amqK s_am2a a_am2b | r_amqK -> s_am2a a_am2b
+ System.DirTree: createDeepFile :: FileKey -> a -> DirTree a
+ System.DirTree: createDeepForest :: ForestFileKey -> DirTree a -> DirForest a
+ System.DirTree: createDeepTree :: FileKey -> DirTree a -> DirTree a
+ System.DirTree: data RelativeFile s a
+ System.DirTree: diffFileKey :: FileKey -> FileKey -> FilePath
+ System.DirTree: diffPath :: FileKey -> FilePath -> Maybe FileKey
+ System.DirTree: directory' :: [(String, DirTree a)] -> DirTree a
+ System.DirTree: emptyDirectory :: DirTree a
+ System.DirTree: emptyForest :: DirForest a
+ System.DirTree: flattenDirTree :: (DirTreeNode (FileMap m) a -> m) -> DirTree a -> m
+ System.DirTree: fromForestFileKey :: ForestFileKey -> FileKey
+ System.DirTree: iflattenDirTree :: (FileKey -> DirTreeNode (FileMap m) a -> m) -> DirTree a -> m
+ System.DirTree: instance (Control.DeepSeq.NFData r, Control.DeepSeq.NFData a) => Control.DeepSeq.NFData (System.DirTree.DirTreeNode r a)
+ System.DirTree: instance (Control.DeepSeq.NFData s, Control.DeepSeq.NFData a) => Control.DeepSeq.NFData (System.DirTree.RelativeFile s a)
+ System.DirTree: instance (GHC.Classes.Eq r, GHC.Classes.Eq a) => GHC.Classes.Eq (System.DirTree.DirTreeNode r a)
+ System.DirTree: instance (GHC.Classes.Eq s, GHC.Classes.Eq a) => GHC.Classes.Eq (System.DirTree.RelativeFile s a)
+ System.DirTree: instance (GHC.Classes.Ord r, GHC.Classes.Ord a) => GHC.Classes.Ord (System.DirTree.DirTreeNode r a)
+ System.DirTree: instance (GHC.Classes.Ord s, GHC.Classes.Ord a) => GHC.Classes.Ord (System.DirTree.RelativeFile s a)
+ System.DirTree: instance (GHC.Show.Show r, GHC.Show.Show a) => GHC.Show.Show (System.DirTree.DirTreeNode r a)
+ System.DirTree: instance (GHC.Show.Show s, GHC.Show.Show a) => GHC.Show.Show (System.DirTree.RelativeFile s a)
+ System.DirTree: instance (System.DirTree.DirForest a1 Data.Type.Equality.~ t) => Control.Lens.Wrapped.Rewrapped (System.DirTree.DirForest a2) t
+ System.DirTree: instance (System.DirTree.DirTree a1 Data.Type.Equality.~ t) => Control.Lens.Wrapped.Rewrapped (System.DirTree.DirTree a2) t
+ System.DirTree: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (System.DirTree.DirForest a)
+ System.DirTree: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (System.DirTree.DirTree a)
+ System.DirTree: instance Control.Lens.At.At (System.DirTree.DirForest a)
+ System.DirTree: instance Control.Lens.At.At (System.DirTree.DirTree a)
+ System.DirTree: instance Control.Lens.At.At (System.DirTree.FileMap a)
+ System.DirTree: instance Control.Lens.At.Ixed (System.DirTree.DirForest a)
+ System.DirTree: instance Control.Lens.At.Ixed (System.DirTree.DirTree a)
+ System.DirTree: instance Control.Lens.At.Ixed (System.DirTree.FileMap a)
+ System.DirTree: instance Control.Lens.Indexed.FoldableWithIndex System.DirTree.FileKey System.DirTree.DirTree
+ System.DirTree: instance Control.Lens.Indexed.FoldableWithIndex System.DirTree.ForestFileKey System.DirTree.DirForest
+ System.DirTree: instance Control.Lens.Indexed.FunctorWithIndex System.DirTree.FileKey System.DirTree.DirTree
+ System.DirTree: instance Control.Lens.Indexed.FunctorWithIndex System.DirTree.ForestFileKey System.DirTree.DirForest
+ System.DirTree: instance Control.Lens.Indexed.TraversableWithIndex System.DirTree.FileKey System.DirTree.DirTree
+ System.DirTree: instance Control.Lens.Indexed.TraversableWithIndex System.DirTree.ForestFileKey System.DirTree.DirForest
+ System.DirTree: instance Control.Lens.Wrapped.Wrapped (System.DirTree.DirForest a)
+ System.DirTree: instance Control.Lens.Wrapped.Wrapped (System.DirTree.DirTree a)
+ System.DirTree: instance Data.Bifoldable.Bifoldable System.DirTree.DirTreeNode
+ System.DirTree: instance Data.Bifoldable.Bifoldable System.DirTree.RelativeFile
+ System.DirTree: instance Data.Bifunctor.Bifunctor System.DirTree.DirTreeNode
+ System.DirTree: instance Data.Bifunctor.Bifunctor System.DirTree.RelativeFile
+ System.DirTree: instance Data.Bitraversable.Bitraversable System.DirTree.DirTreeNode
+ System.DirTree: instance Data.Bitraversable.Bitraversable System.DirTree.RelativeFile
+ System.DirTree: instance Data.Foldable.Foldable (System.DirTree.DirTreeNode r)
+ System.DirTree: instance Data.Foldable.Foldable (System.DirTree.RelativeFile s)
+ System.DirTree: instance Data.Foldable.Foldable System.DirTree.DirForest
+ System.DirTree: instance Data.Foldable.Foldable System.DirTree.DirTree
+ System.DirTree: instance Data.Traversable.Traversable (System.DirTree.DirTreeNode r)
+ System.DirTree: instance Data.Traversable.Traversable (System.DirTree.RelativeFile s)
+ System.DirTree: instance Data.Traversable.Traversable System.DirTree.DirForest
+ System.DirTree: instance Data.Traversable.Traversable System.DirTree.DirTree
+ System.DirTree: instance GHC.Base.Functor (System.DirTree.DirTreeNode r)
+ System.DirTree: instance GHC.Base.Functor (System.DirTree.RelativeFile s)
+ System.DirTree: instance GHC.Base.Functor System.DirTree.DirForest
+ System.DirTree: instance GHC.Base.Functor System.DirTree.DirTree
+ System.DirTree: instance GHC.Base.Monoid (System.DirTree.DirForest a)
+ System.DirTree: instance GHC.Base.Semigroup (System.DirTree.DirForest a)
+ System.DirTree: instance GHC.Base.Semigroup (System.DirTree.DirTree a)
+ System.DirTree: instance GHC.Classes.Eq a => GHC.Classes.Eq (System.DirTree.DirForest a)
+ System.DirTree: instance GHC.Classes.Eq a => GHC.Classes.Eq (System.DirTree.DirTree a)
+ System.DirTree: instance GHC.Classes.Ord a => GHC.Classes.Ord (System.DirTree.DirForest a)
+ System.DirTree: instance GHC.Classes.Ord a => GHC.Classes.Ord (System.DirTree.DirTree a)
+ System.DirTree: instance GHC.Generics.Generic (System.DirTree.DirForest a)
+ System.DirTree: instance GHC.Generics.Generic (System.DirTree.DirTree a)
+ System.DirTree: instance GHC.Generics.Generic (System.DirTree.DirTreeNode r a)
+ System.DirTree: instance GHC.Generics.Generic (System.DirTree.RelativeFile s a)
+ System.DirTree: instance GHC.Show.Show a => GHC.Show.Show (System.DirTree.DirForest a)
+ System.DirTree: instance GHC.Show.Show a => GHC.Show.Show (System.DirTree.DirTree a)
+ System.DirTree: instance System.DirTree.AsDirTreeNode (System.DirTree.DirTree a) (System.DirTree.DirForest a) a
+ System.DirTree: instance System.DirTree.AsDirTreeNode (System.DirTree.DirTreeNode r a) r a
+ System.DirTree: instance System.DirTree.AsRelativeFile (System.DirTree.DirTreeNode a (System.DirTree.RelativeFile b c)) b c
+ System.DirTree: instance System.DirTree.AsRelativeFile (System.DirTree.RelativeFile s a) s a
+ System.DirTree: newtype DirForest a
+ System.DirTree: newtype FileMap a
+ System.DirTree: readRelativeDirTree :: (FilePath -> IO a) -> FilePath -> IO (RelativeDirTree Link a)
+ System.DirTree: realfile :: a -> RelativeDirTree s a
+ System.DirTree: singletonFileMap :: String -> a -> FileMap a
+ System.DirTree: singletonForest :: String -> DirTree a -> DirForest a
+ System.DirTree: toForestFileKey :: FileKey -> Maybe ForestFileKey
+ System.DirTree: toLink :: FileKey -> FilePath -> Link
+ System.DirTree: type ForestFileKey = NonEmpty String
+ System.DirTree: type RelativeDirForest s a = DirForest (RelativeFile s a)
+ System.DirTree: type RelativeDirTree s a = DirTree (RelativeFile s a)
+ System.DirTree: writeRelativeDirTree :: (FilePath -> a -> IO ()) -> FilePath -> RelativeDirTree Link a -> IO ()
+ System.DirTree.Zip: entries :: Lens' Archive [Entry]
+ System.DirTree.Zip: entriesFromDirForest :: Integer -> RelativeDirForest Link ByteString -> [Entry]
+ System.DirTree.Zip: entriesToDirForest :: [Entry] -> Maybe (RelativeDirForest Link ByteString)
+ System.DirTree.Zip: entryFromFile :: Integer -> FileKey -> RelativeFile Link ByteString -> Entry
+ System.DirTree.Zip: entryToDirForest :: Entry -> Maybe (DirForest Entry)
+ System.DirTree.Zip: files :: Lens' Archive (RelativeDirForest Link ByteString)
+ System.DirTree.Zip: fromArchive :: Archive -> ByteString
+ System.DirTree.Zip: toArchive :: ByteString -> Archive
- System.DirTree: DirTree :: DirTreeN s a -> DirTree s a
+ System.DirTree: DirTree :: DirTreeNode (DirForest a) a -> DirTree a
- System.DirTree: Directory :: r -> DirTreeNode r s a
+ System.DirTree: Directory :: r -> DirTreeNode r a
- System.DirTree: File :: a -> DirTreeNode r s a
+ System.DirTree: File :: a -> DirTreeNode r a
- System.DirTree: Symlink :: s -> DirTreeNode r s a
+ System.DirTree: Symlink :: s -> RelativeFile s a
- System.DirTree: [dirTreeNode] :: DirTree s a -> DirTreeN s a
+ System.DirTree: [dirTreeNode] :: DirTree a -> DirTreeNode (DirForest a) a
- System.DirTree: data DirTreeNode r s a
+ System.DirTree: data DirTreeNode r a
- System.DirTree: depthfirst :: Monoid m => (FileKey -> DirTreeNode [String] v a -> m) -> DirTree v a -> m
+ System.DirTree: depthfirst :: Semigroup m => (FileKey -> DirTreeNode [String] a -> m) -> DirTree a -> m
- System.DirTree: directory :: FileMap (DirTree s a) -> DirTree s a
+ System.DirTree: directory :: DirForest a -> DirTree a
- System.DirTree: file :: a -> DirTree s a
+ System.DirTree: file :: a -> DirTree a
- System.DirTree: fileTypeOfNode :: DirTreeNode a b c -> FileType
+ System.DirTree: fileTypeOfNode :: DirTreeNode a (RelativeFile b c) -> FileType
- System.DirTree: findNode :: (FileKey -> DirTreeNode [String] v a -> Bool) -> DirTree v a -> Maybe (FileKey, DirTreeNode [String] v a)
+ System.DirTree: findNode :: (FileKey -> DirTreeNode [String] a -> Bool) -> DirTree a -> Maybe (FileKey, DirTreeNode [String] a)
- System.DirTree: followLinks :: NFData a => (FilePath -> IO a) -> DirTree Link a -> IO (DirTree Void a)
+ System.DirTree: followLinks :: forall a. (FilePath -> IO a) -> RelativeDirTree Link a -> IO (DirTree a)
- System.DirTree: listNodes :: DirTree v a -> [(FileKey, DirTreeNode [String] v a)]
+ System.DirTree: listNodes :: DirTree a -> [(FileKey, DirTreeNode [String] a)]
- System.DirTree: newtype DirTree s a
+ System.DirTree: newtype DirTree a
- System.DirTree: readDirTree :: NFData a => (FilePath -> IO a) -> FilePath -> IO (DirTree Link a)
+ System.DirTree: readDirTree :: (FilePath -> IO a) -> FilePath -> IO (DirTree a)
- System.DirTree: readPath :: FilePath -> IO (DirTreeNode [String] FilePath ())
+ System.DirTree: readPath :: FilePath -> IO (DirTreeNode [String] (RelativeFile FilePath ()))
- System.DirTree: symlink :: s -> DirTree s a
+ System.DirTree: symlink :: s -> RelativeDirTree s a
- System.DirTree: type FileType = DirTreeNode () () ()
+ System.DirTree: type FileType = DirTreeNode () (RelativeFile () ())
- System.DirTree: writeDirTree :: (FilePath -> a -> IO ()) -> FilePath -> DirTree Link a -> IO ()
+ System.DirTree: writeDirTree :: (FilePath -> a -> IO ()) -> FilePath -> DirTree a -> IO ()
Files
- dirtree.cabal +13/−5
- src/System/DirTree.hs +829/−694
- src/System/DirTree/Zip.hs +113/−0
- test/System/DirTree/ZipSpec.hs +52/−0
- test/System/DirTreeSpec.hs +75/−82
dirtree.cabal view
@@ -4,12 +4,12 @@ -- -- see: https://github.com/sol/hpack ----- hash: ccecd49ad528fc24560e3ec81951861237966a7db1d26b8f8fc82c0350d392f3+-- hash: af2392fd0d2b2978caaee80e8994f8254986e75b5c1113b4016f71255f20c7f7 name: dirtree-version: 0.0.1+version: 0.1.0 synopsis: A small library for working with directories.-description: A small library for working with directories.+description: A small library for loading and building directories as trees. category: System homepage: https://github.com/kalhauge/dirtree#readme author: Christian Gram Kalhauge@@ -24,18 +24,22 @@ library exposed-modules: System.DirTree+ System.DirTree.Zip other-modules: Paths_dirtree hs-source-dirs: src ghc-options: -Wall build-depends:- base >=4.7 && <5+ base >=4.10 && <5+ , bytestring , containers , deepseq , directory , filepath , lens+ , unix+ , zip-archive default-language: Haskell2010 test-suite dirtree-test@@ -43,13 +47,15 @@ main-is: Main.hs other-modules: Spec+ System.DirTree.ZipSpec System.DirTreeSpec Paths_dirtree hs-source-dirs: test ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N build-depends:- base >=4.7 && <5+ base >=4.10 && <5+ , bytestring , containers , deepseq , directory@@ -59,4 +65,6 @@ , hspec-discover , hspec-expectations-pretty-diff , lens+ , unix+ , zip-archive default-language: Haskell2010
src/System/DirTree.hs view
@@ -1,695 +1,830 @@ {-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TupleSections #-}-{-|-Module : System.DirTree-Copyright : (c) Christian Gram Kalhauge, 2019-License : MIT-Maintainer : kalhauge@cs.ucla.edu--A directory tree, with helper functions to do different cool stuff.---}-module System.DirTree- (-- -- * DirTree- DirTree (..)-- , file- , symlink- , directory- , directoryFromFiles-- -- ** Constructors- , fromFiles- , fromFiles'- , fromFile- , toFiles-- -- ** Accessors- , FileKey- , fileKeyToPath- , fileKeyFromPath-- , lookupFile-- -- ** Traversals- -- These function are used for folding over the DirTree-- , traverseDirTree- , traverseDirTree'- , itraverseDirTree- , itraverseDirTree'-- , mapDirTree'- , imapDirTree'-- , depthfirst- , foldDirTree- , foldDirTree'- , ifoldDirTree- , ifoldDirTree'-- , flatten-- -- ** Utils- , findNode- , listNodes-- -- ** IO operations- -- These functions can be used to read a DirTree from the file system.- , readDirTree- , lazyReadDirTree-- , writeDirTree-- , followLinks- , lazyFollowLinks-- -- * DirTreeNode- , Link (..)- , DirTreeNode (..)- , FileType- , fileTypeOfNode-- -- ** Folds- , mapDirTreeNode- , foldDirTreeNode- , traverseDirTreeNode-- -- ** IO operations- , getFileType- , readPath-- -- * Helpers- , DirTreeN-- -- * FileMap- , FileMap- , toFileList- , fromFileList-- , (-.>), (-|>), (-/>)-- , toDeepFileList- , fromDeepFileList-- , toFileNames- , lookupFileMap- , emptyFileMap- ) where---- containers-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Map as Map---- deepseq-import Control.DeepSeq---- directory-import System.Directory hiding (findFile)---- filepath-import System.FilePath---- lens-import Control.Lens.Combinators--- import Control.Lens.Indexed---- base-import Data.Foldable-import Data.Semigroup-import Data.Void-import Text.Show-import GHC.Generics-import System.IO.Unsafe--- * DirTree---- | A dir tree is a tree of nodes.-newtype DirTree s a = DirTree- { dirTreeNode :: DirTreeN s a- }- deriving (Eq, Ord, NFData, Generic)--instance (Show v, Show c) => Show (DirTree v c) where- showsPrec d c = showParen (d >9) $ (f $ dirTreeNode c)- where- f = \case- Directory a ->- showString "directory " . showsPrec 11 a- Symlink a ->- showString "symlink " . showsPrec 11 a- File a ->- showString "file " . showsPrec 11 a---- | A `DirTreeN` is a `DirTreeNode` with a the directory as a recursive--- DirTree.-type DirTreeN s a = DirTreeNode (FileMap (DirTree s a)) s a--instance Semigroup (DirTree s a) where- DirTree (Directory as) <> DirTree (Directory bs) =- DirTree (Directory (as <> bs))- _ <> a = a--instance Functor (DirTree s) where- fmap = mapDirTree' id--instance Foldable (DirTree s) where- foldMap = foldDirTree' (const mempty)--instance Traversable (DirTree s) where- traverse = traverseDirTree' pure--instance FunctorWithIndex FileKey (DirTree v)-instance FoldableWithIndex FileKey (DirTree v)-instance TraversableWithIndex FileKey (DirTree v) where- itraverse = itraverseDirTree' (const pure)- {-# INLINE itraverse #-}---- ** Constructors---- | Constructs a dirtree with only a file-file :: a -> DirTree s a-file = DirTree . File---- | Constructs a dirtree with a symlink-symlink :: s -> DirTree s a-symlink = DirTree . Symlink---- | Constructs a dirtree with a directory-directory :: FileMap (DirTree s a) -> DirTree s a-directory = DirTree . Directory---- | Constructs a dirtree with a directory-directoryFromFiles :: [(String, DirTree s a)] -> DirTree s a-directoryFromFiles = DirTree . Directory . fromFileList---- ** Accessors---- | A filekey is the filepath in reverse order-type FileKey = [String]---- | Get a `FileKey` from a `FilePath`-fileKeyFromPath :: FilePath -> FileKey-fileKeyFromPath =- reverse . splitDirectories---- | Get a `FilePath` from a `FileKey`-fileKeyToPath :: FileKey -> FilePath-fileKeyToPath =- joinPath . reverse--diffFileKey :: FileKey -> FileKey -> FilePath-diffFileKey f to' =- let (n, bs) = (suffix f to')- in fileKeyToPath (bs ++ replicate n "..")- where- suffix (a:as) (b:bs)- | a /= b =- (1 + length as, b:bs)- | otherwise =- suffix as bs- suffix (_:as) [] =- (1 + length as, [])- suffix [] bs =- (0, bs)---- | Lookup a file in a `DirTree` using a `FileKey`-lookupFile :: FileKey -> DirTree v a -> Maybe (DirTree v a)-lookupFile fk = go (reverse fk)- where- go [] tree = Just tree- go (a:rest) (DirTree (Directory x)) =- go rest =<< lookupFileMap a x- go _ _ = Nothing-{-# inline lookupFile #-}--toFiles :: DirTree v a -> [(FileKey, Either v a)]-toFiles =- flip appEndo []- . ifoldDirTree' (\i s -> Endo ((i, Left s):)) (\i s -> Endo ((i, Right s):))-{-# INLINE toFiles #-}---- | Create a dirtree from a non-empty list of files.-fromFiles :: [(FileKey, Either v a)] -> Maybe (DirTree v a)-fromFiles =- fmap fromFiles' . NonEmpty.nonEmpty-{-# INLINE fromFiles #-}---- | Create a dirtree from a non-empty list of files.-fromFiles' :: NonEmpty.NonEmpty (FileKey, Either v a) -> DirTree v a-fromFiles' =- sconcat . fmap (uncurry fromPath)-{-# INLINE fromFiles' #-}--fromPath :: FileKey -> Either s a -> DirTree s a-fromPath key a =- foldr (\s f -> directory (singleFile s f)) (either symlink file a) key-{-# INLINE fromPath #-}--fromFile :: FileKey -> a -> DirTree Void a-fromFile key a =- foldr (\s f -> directory (singleFile s f)) (file a) key-{-# INLINE fromFile #-}---- ** Helpers---- | Traverse over the tree-itraverseDirTree ::- Applicative f- => ( FileKey -> DirTreeNode (FileMap (f (DirTree s' a'))) s a -> f (DirTreeN s' a'))- -> DirTree s a- -> f (DirTree s' a')-itraverseDirTree f = go []- where- go x (DirTree fs) = fmap DirTree . f x $- case fs of- Directory fm ->- Directory $ imap (\s a -> go (s:x) a) fm- Symlink a -> Symlink a- File a -> File a-{-# inline itraverseDirTree #-}---- | Traverse over the tree with index. This method uses two functions one--- symlinks and one for files.-itraverseDirTree' ::- Applicative f- => (FileKey -> s -> f s') -> (FileKey -> a -> f a')- -> DirTree s a- -> f (DirTree s' a')-itraverseDirTree' fs fa =- itraverseDirTree- (\key -> \case- Directory fm ->- Directory <$> traverse id fm- Symlink a -> Symlink <$> fs key a- File a -> File <$> fa key a- )-{-# inline itraverseDirTree' #-}---- | Maps over a `DirTree`-imapDirTree' :: (FileKey -> s -> s') -> (FileKey -> a -> a') -> DirTree s a -> DirTree s' a'-imapDirTree' fs fa =- runIdentity . itraverseDirTree' (\i -> Identity . fs i) (\i -> Identity . fa i)-{-# inline imapDirTree' #-}---- | Folds over a `DirTree`.-ifoldDirTree' :: Monoid m => (FileKey -> s -> m) -> (FileKey -> a -> m) -> DirTree s a -> m-ifoldDirTree' fs fa =- ifoldDirTree (\i -> foldDirTreeNode fold (fs i) (fa i))-{-# inline ifoldDirTree' #-}---- | Folds over a `DirTree` using the `DirTreeNode`.-ifoldDirTree :: (FileKey -> DirTreeNode (FileMap m) s a -> m) -> DirTree s a -> m-ifoldDirTree f = go []- where- go x (DirTree fs) = f x $- case fs of- Directory fm ->- Directory $ imap (\s a -> go (s:x) a) fm- Symlink a -> Symlink a- File a -> File a-{-# inline ifoldDirTree #-}---- | Traverse a DirTree-traverseDirTree ::- Applicative f- => (DirTreeNode (FileMap (f (DirTree s' a'))) s a -> f (DirTreeN s' a'))- -> DirTree s a- -> f (DirTree s' a')-traverseDirTree fm =- itraverseDirTree (const fm)-{-# inline traverseDirTree #-}---- | Traverse a DirTree-traverseDirTree' ::- Applicative m- => (s -> m s') -> (a -> m a')- -> DirTree s a -> m (DirTree s' a')-traverseDirTree' fs fa =- itraverseDirTree' (const fs) (const fa)-{-# inline traverseDirTree' #-}---- | Folds over a dirtree-foldDirTree :: (DirTreeNode (FileMap m) s a -> m) -> DirTree s a -> m-foldDirTree f =- ifoldDirTree (const f)-{-# inline foldDirTree #-}---- | Folds over a dirtree-foldDirTree' :: Monoid m => (s -> m) -> (a -> m) -> DirTree s a -> m-foldDirTree' fs fa =- ifoldDirTree' (const fs) (const fa)-{-# inline foldDirTree' #-}---- | maps over a dirtree-mapDirTree' :: (s -> s') -> (a -> a') -> DirTree s a -> DirTree s' a'-mapDirTree' fs fa =- imapDirTree' (const fs) (const fa)-{-# inline mapDirTree' #-}---- | Flatten a directory tree. This is usefull for following symlinks, or--- expanding zip-files.-flatten ::- (s -> DirTree s' a')- -> (a -> DirTree s' a')- -> DirTree s a- -> DirTree s' a'-flatten s a =- foldDirTree (foldDirTreeNode directory s a)-{-# inline flatten #-}---- * Utils---- | Recursively iterate over a folder.-depthfirst ::- Monoid m- =>(FileKey -> DirTreeNode [String] v a -> m)- -> DirTree v a- -> m-depthfirst fm =- ifoldDirTree $ \key file' ->- case file' of- Directory files -> do- fm key (Directory $ toFileNames files) <> fold files- File a ->- fm key (File a)- Symlink v ->- fm key (Symlink v)-{-# inline depthfirst #-}---- | Find a file given a predicate that takes a `FileKey` and `DirTreeNode`.-findNode ::- (FileKey -> DirTreeNode [String] v a -> Bool)- -> DirTree v a- -> Maybe (FileKey, DirTreeNode [String] v a)-findNode f =- fmap getFirst . depthfirst- (curry $ \case- a | uncurry f a -> Just (First a)- | otherwise -> Nothing- )-{-# inline findNode #-}---- | List all the nodes in the `DirTree`.-listNodes :: DirTree v a -> [(FileKey, DirTreeNode [String] v a)]-listNodes =- flip appEndo [] . depthfirst (curry $ Endo . (:))-{-# inline listNodes #-}----- ** IO Methods---- | A `Link` can either be `Internal`, pointing to something in the `DirTree` or--- `External` pointing to an absolute `FilePath`.-data Link- = Internal !FileKey- | External !FilePath- deriving (Show, Eq, Generic, NFData)---- | Reads a DirTree. All file paths are absolute to the filepath-readDirTree ::- NFData a =>- (FilePath -> IO a)- -> FilePath- -> IO (DirTree Link a)-readDirTree reader' fp = do- force <$> lazyReadDirTree reader' fp---- | Lazy read a DirTree. This function uses `unsafeInterleaveIO` to--- lazy interleave load a node. This means that it can be used to efficiently--- search of a file. All paths are absolute-lazyReadDirTree ::- (FilePath -> IO a)- -> FilePath- -> IO (DirTree Link a)-lazyReadDirTree reader' basepath = do- from' <- canonicalizePath basepath- go from' [] basepath- where- go from' key fp = unsafeInterleaveIO $ do- node <- readPath fp- foldDirTreeNode- (fmap directory . imapM (\s _ -> go from' (s:key) (fp </> s)) . fromFilenames)- (fmap symlink . absolute)- (const $ file <$> reader' fp)- node- where- absolute a- | isAbsolute a =- return $ External a- | otherwise = do- a' <- canonicalizePath (takeDirectory fp </> a)- let a'' = makeRelative from' a'- if a'' /= a'- then return $ Internal (fileKeyFromPath a'')- else return $ External a'-{-# INLINE lazyReadDirTree #-}----- | Reads a DirTree-writeDirTree ::- (FilePath -> a -> IO ())- -> FilePath- -> DirTree Link a- -> IO ()-writeDirTree writer fp tree = do- ifoldDirTree- ( \fk i ->- let fp' = fp </> fileKeyToPath fk in- case i of- Directory m -> do- createDirectory fp'- fold m- Symlink (External target) ->- createFileLink target fp'- Symlink (Internal key) ->- createFileLink- (case (fk, key) of- (_:fk', _) -> diffFileKey fk' key- ([], []) -> "."- ([], _) -> error "Fail"- ) fp'- File a ->- writer fp' a- )- tree-{-# INLINE writeDirTree #-}---- | Follow the links to create the tree. This function might recurse forever.-followLinks :: NFData a => (FilePath -> IO a) -> DirTree Link a -> IO (DirTree Void a)-followLinks fio dt =- force <$> lazyFollowLinks fio dt-{-# INLINE followLinks #-}----- | Like follow links but uses lazy io to only load the recursive folder when--- needed.-lazyFollowLinks :: (FilePath -> IO a) -> DirTree Link a -> IO (DirTree Void a)-lazyFollowLinks reader' tree =- go tree tree- where- go basetree =- unsafeInterleaveIO- . fmap (flatten id file)- . traverseDirTree' (readLink basetree) pure-- readLink basetree = \case- Internal s -> do- case lookupFile s basetree of- Just a -> go basetree a- Nothing ->- error $ "Could not find " ++ show s ++ " in the dirtree " ++ show (fmap (const ()) tree)- External s -> do- t <- lazyReadDirTree reader' s- lazyFollowLinks reader' t----- * DirTreeNode---- | A directory tree node. Everything is either a file, a symbolic link, or a--- directory.-data DirTreeNode r s a- = Directory r- | Symlink s- | File a- deriving (Show, Eq, Ord, Functor, Foldable, Traversable, NFData, Generic)---- | A `FileType` is just a `DirTreeNode` with no contents.-type FileType = DirTreeNode () () ()---- ** Helpers---- | A DirTreeNode is a weird kind of algebra.-flattenDirTreeNode :: DirTreeNode m m m -> m-flattenDirTreeNode = \case- File m -> m- Symlink m -> m- Directory m -> m---- | We can map over a DirTreeNode-mapDirTreeNode ::- (r -> r') -> (s -> s') -> (a -> a')- -> DirTreeNode r s a- -> DirTreeNode r' s' a'-mapDirTreeNode fr fs fa = \case- File a -> File $ fa a- Symlink s -> Symlink $ fs s- Directory r -> Directory $ fr r---- | We can fold over a DirTreeNode by providing a function for each case.-foldDirTreeNode :: (r -> m) -> (s -> m) -> (a -> m) -> DirTreeNode r s a -> m-foldDirTreeNode fr fs fa =- flattenDirTreeNode . mapDirTreeNode fr fs fa---- | We can fold over a DirTreeNode by providing a function for each case.-traverseDirTreeNode ::- Functor m- =>- (r -> m r') -> (s -> m s') -> (a -> m a')- -> DirTreeNode r s a -> m (DirTreeNode r' s' a')-traverseDirTreeNode fr fs fa =- flattenDirTreeNode . mapDirTreeNode- (fmap Directory . fr)- (fmap Symlink . fs)- (fmap File . fa)----- | Gets the `FileType` of a `DirTreeNode`-fileTypeOfNode :: DirTreeNode a b c -> FileType-fileTypeOfNode = mapDirTreeNode (const ()) (const ()) (const ())---- ** IO Methods---- | Check a filepath for Type, throws an IOException if path does not exist.-getFileType :: FilePath -> IO FileType-getFileType fp =- pathIsSymbolicLink fp >>= \case- True ->- return $ Symlink ()- False ->- doesDirectoryExist fp >>= \case- True ->- return $ Directory ()- False ->- return $ File ()---- | Reads the structure of the filepath-readPath ::- FilePath- -> IO (DirTreeNode [String] FilePath ())-readPath fp = do- node <- getFileType fp- foldDirTreeNode- (const $ Directory <$> listDirectory fp)- (const $ Symlink <$> getSymbolicLinkTarget fp)- (const . return $ File ())- node---- | A map from file names to-newtype FileMap a =- FileMap (Map.Map String a)- deriving (Eq, Ord, NFData, Generic, Functor, Foldable, Traversable)---(-.>) :: String -> a -> (String, DirTree x a)-(-.>) s a = (s, file a)--(-|>) :: String -> a -> (String, DirTree a x)-(-|>) s a = (s, symlink a)--(-/>) :: String -> [(String, DirTree a b)] -> (String, DirTree a b)-(-/>) s a = (s, directoryFromFiles a)---- | Create a list of pairs of filenames and file values.-toFileList :: FileMap a -> [(String, a)]-toFileList (FileMap a) = Map.toList a---- | Create a `FileMap` from a list of pairs of filenames a file values.-fromFileList :: [(String, a)] -> FileMap a-fromFileList = FileMap . Map.fromList---- | Create a `FileMap` from a list of pairs of filenames a file values.-fromFilenames :: [String] -> FileMap ()-fromFilenames = fromFileList . map (,())---- | Single File-singleFile :: String -> a -> FileMap a-singleFile s a = FileMap (Map.singleton s a)---- | empty filemap-emptyFileMap :: FileMap a-emptyFileMap = FileMap Map.empty---- | To a list of filenames-toFileNames :: FileMap a -> [String]-toFileNames = map fst . toFileList---- | Lookup a file using a filename-lookupFileMap :: String -> FileMap a -> Maybe a-lookupFileMap s (FileMap a) = Map.lookup s a---- | Returns a list of `FileMap`-toDeepFileList :: FileMap (DirTree s a) -> [(FileKey, Either s a)]-toDeepFileList fm =- toFiles $ directory fm---- | Returns an empty `FileMap`, if the input list is empty or contains files--- that does not correspond to a `FileMap`.-fromDeepFileList :: [(FileKey, Either s a)] -> FileMap (DirTree s a)-fromDeepFileList lst =- maybe emptyFileMap- ((\case- Directory fm -> fm- _ -> emptyFileMap- ) . dirTreeNode)- $ fromFiles lst--instance (Show a, Show b) => Show (FileMap (DirTree a b)) where- showsPrec d m = showParen (d > 9) $ showString "fromFileList " . showFileList m- where- showFileList =- showListWith (\(s, x) -> f s $ dirTreeNode x) . toFileList-- f s (Directory x) =- showsPrec (dir_prec+1) s .- showString " -/> " .- showFileList x-- f s (Symlink x) =- showsPrec (dir_prec+1) s .- showString " -|> " .- showsPrec (dir_prec+1) x-- f s (File x) =- showsPrec (dir_prec+1) s .- showString " -.> " .- showsPrec (dir_prec+1) x- dir_prec = 5---instance Semigroup a => Semigroup (FileMap a) where- FileMap as <> FileMap bs =- FileMap (Map.unionWith (<>) as bs)--instance Semigroup a => Monoid (FileMap a) where- mempty = emptyFileMap--instance FunctorWithIndex String FileMap-instance FoldableWithIndex String FileMap-instance TraversableWithIndex String FileMap where- itraverse f (FileMap fs) = FileMap <$> itraverse f fs- {-# INLINE itraverse #-}--data Anchored a = (:/)- { base :: FilePath- , dirTree :: a- } deriving (Show, Eq, Ord, Functor, Foldable, Traversable, NFData, Generic)-+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}+{-|+Module : System.DirTree+Copyright : (c) Christian Gram Kalhauge, 2019+License : MIT+Maintainer : kalhauge@cs.ucla.edu++A directory tree, with helper functions to do different cool stuff. Contrary to+`directory-tree`, this package does try to add as many accessors and handlers as+possible. This is alos the reason that it depends on the Lens library.++-}+module System.DirTree+ (+ -- * 'DirTreeNode'+ -- $DirTreeNode++ DirTreeNode (..)+ , RelativeFile (..)++ -- ** Helpers+ , FileType+ , fileTypeOfNode++ , AsDirTreeNode (..)+ , AsRelativeFile (..)++ -- ** IO+ , getFileType+ , readPath++ -- * 'FileMap'+ -- $FileMap++ , FileMap (..)++ -- ** Constructors+ , emptyFileMap+ , singletonFileMap+ , toFileList+ , fromFileList+ , (.*), (./), (.*>), (.*.)++ -- ** Accessors+ , lookupFileMap++ -- * 'DirTree'+ -- $DirTree+ , DirTree (..)+ , RelativeDirTree+ , asRelativeDirTree++ -- ** Constructors+ , file+ , realfile+ , symlink+ , directory++ , directory'++ , emptyDirectory++ , createDeepFile+ , createDeepTree++ -- ** Accessors+ , FileKey++ , fileKeyFromPath+ , fileKeyToPath++ , diffFileKey+ , diffPath++ , alterFile++ -- ** Iterators+ -- Most of the iterators can be done with the 'FunctorWithIndex',+ -- 'FoldableWithIndex', and 'TraversableWithIndex', but some accumilations+ -- are easier.+ , iflattenDirTree+ , flattenDirTree++ , depthfirst+ , findNode+ , listNodes++ -- ** IO++ , readDirTree+ , writeDirTree+ , Link (..)+ , toLink+ , readRelativeDirTree+ , followLinks+ , writeRelativeDirTree++ -- * 'DirForest'+ -- $DirForest++ , DirForest (..)+ , RelativeDirForest++ , ForestFileKey+ , fromForestFileKey+ , toForestFileKey++ -- ** Constructors+ , asRelativeDirForest+ , emptyForest+ , singletonForest+ , createDeepForest++ -- ** Iterators+ , alterForest++ ) where++-- containers+import qualified Data.Map as Map++-- deepseq+import Control.DeepSeq++-- directory+import System.Directory hiding (findFile)++-- filepath+import System.FilePath++-- lens+import Control.Lens.Combinators+import Control.Lens+-- import Control.Lens.Indexed++-- base+import Data.Functor+import Data.Foldable+import Data.Bifunctor+import Data.Maybe+import Data.List.NonEmpty (NonEmpty (..), nonEmpty)+import Data.Semigroup (sconcat)+import Data.Monoid+import Data.Bitraversable+import Data.Bifoldable+import Control.Monad+import Text.Show+import GHC.Generics++-- $DirTreeNode+-- The basic item of this library is a DirTreeNode.++-- | A directory tree node. Everything is either a file, or a+-- directory.+data DirTreeNode r a+ = Directory r+ | File a+ deriving (Show, Eq, Ord, Functor, Foldable, Traversable, NFData, Generic)++instance Bifunctor DirTreeNode where+ bimap fr fa = \case+ Directory r -> Directory (fr r)+ File a -> File (fa a)++instance Bifoldable DirTreeNode where+ bifoldMap fr fa = \case+ Directory r -> fr r+ File a -> fa a++instance Bitraversable DirTreeNode where+ bitraverse fr fa = \case+ Directory r -> Directory <$> fr r+ File a -> File <$> fa a++makeClassyPrisms ''DirTreeNode++-- | A DirTree can contain relativeFile files. This means that some files might be+-- symlinks.+data RelativeFile s a+ = Symlink s+ | Real a+ deriving (Show, Eq, Ord, Functor, Foldable, Traversable, NFData, Generic)++instance Bifunctor RelativeFile where+ bimap fr fa = \case+ Symlink r -> Symlink (fr r)+ Real a -> Real (fa a)++instance Bifoldable RelativeFile where+ bifoldMap fr fa = \case+ Symlink r -> fr r+ Real a -> fa a++instance Bitraversable RelativeFile where+ bitraverse fr fa = \case+ Symlink r -> Symlink <$> fr r+ Real a -> Real <$> fa a++makeClassyPrisms ''RelativeFile++-- | It is quite offten that a node will be used as a relative file.+instance AsRelativeFile (DirTreeNode a (RelativeFile b c)) b c where+ _RelativeFile = _File++-- | A `FileType` is just a `DirTreeNode` with no contents.+type FileType = DirTreeNode () (RelativeFile () ())++-- | Gets the `FileType` of a `DirTreeNode`+fileTypeOfNode :: DirTreeNode a (RelativeFile b c) -> FileType+fileTypeOfNode = bimap (const ()) (bimap (const ()) (const ()))++-- | Check a filepath for Type, throws an IOException if path does not exist.+getFileType :: FilePath -> IO FileType+getFileType fp =+ pathIsSymbolicLink fp >>= \case+ True ->+ return $ File (Symlink ())+ False ->+ doesDirectoryExist fp >>= \case+ True ->+ return $ Directory ()+ False ->+ return $ File (Real ())++-- | Reads the structure of the filepath+readPath ::+ FilePath+ -> IO (DirTreeNode [String] (RelativeFile FilePath ()))+readPath fp = bitraverse+ (const $ listDirectory fp)+ (bitraverse+ (const $ getSymbolicLinkTarget fp)+ return+ ) =<< getFileType fp+++-- $FileMap+-- The 'FileMap' is used to represent the content of a directory.++-- | A map from file names to+newtype FileMap a =+ FileMap { fileMapAsMap :: Map.Map String a }+ deriving (Eq, Ord, NFData, Generic, Functor, Foldable, Traversable)++-- | Single File+singletonFileMap :: String -> a -> FileMap a+singletonFileMap s a = FileMap (Map.singleton s a)++-- | empty filemap+emptyFileMap :: FileMap a+emptyFileMap = FileMap Map.empty++-- | The 'FileMap' is a semigroup if the contnent is. It tries+-- to union the content under each item.+instance Semigroup a => Semigroup (FileMap a) where+ FileMap as <> FileMap bs =+ FileMap (Map.unionWith (<>) as bs)++-- | The empty monoid is the emptyFileMap+instance Semigroup a => Monoid (FileMap a) where+ mempty = emptyFileMap++instance FunctorWithIndex String FileMap+instance FoldableWithIndex String FileMap+instance TraversableWithIndex String FileMap where+ itraverse f (FileMap fs) = FileMap <$> itraverse f fs+ {-# INLINE itraverse #-}++instance Show a => Show (DirForest a) where+ showsPrec d m = showParen (d > 9) $ showString "DirForest . fromFileList " . showFileList m+ where+ showFileList =+ showListWith (\(s, x) -> f s $ dirTreeNode x) . toFileList . getInternalFileMap++ f s (Directory x) =+ showsPrec (dir_prec+1) s .+ showString " ./ " .+ showFileList x++ f s (File x) =+ showsPrec (dir_prec+1) s .+ showString " .* " .+ showsPrec (dir_prec+1) x+ dir_prec = 5+++-- | Create a list of pairs of filenames and file values.+toFileList :: FileMap a -> [(String, a)]+toFileList (FileMap a) = Map.toList a++-- | Create a `FileMap` from a list of pairs of filenames a file values.+fromFileList :: [(String, a)] -> FileMap a+fromFileList = FileMap . Map.fromList++-- | Find a list of names used in the FileMap+toFileNames :: FileMap a -> [String]+toFileNames = map fst . toFileList++-- | Lookup a file using a filename+lookupFileMap :: String -> FileMap a -> Maybe a+lookupFileMap s (FileMap a) = Map.lookup s a++-- | The 'Map.alterF' version to the FileMap.+alterFileMap ::+ Functor f+ => (Maybe a -> f (Maybe a))+ -> String+ -> FileMap a+ -> f (FileMap a)+alterFileMap fn key (FileMap fm) =+ FileMap <$> Map.alterF fn key fm++type instance Index (FileMap a) = String+type instance IxValue (FileMap a) = a++instance Ixed (FileMap a) where+ ix k f m = FileMap <$> ix k f (fileMapAsMap m)+ {-# INLINE ix #-}++instance At (FileMap a) where+ at = flip alterFileMap+ {-# INLINE at #-}++-- $DirTree+-- A 'DirTree' is a recursive difined tree.+--++-- | A 'FileKey' is a list of filenames to get to the final file+type FileKey = [String]++-- | A 'DirTreeN' represents a single level in the DirTree.+type DirTreeN a = DirTreeNode (DirForest a) a++-- | A specialized traversal of the DirTreeNode+itraverseDirTreeN ::+ Applicative f+ => (FileKey -> a -> f b)+ -> DirTreeN a+ -> f (DirTreeN b)+itraverseDirTreeN fia = \case+ Directory m ->+ Directory <$> itraverse (fia . fromForestFileKey) m+ File a ->+ File <$> fia [] a+++-- | A dir tree is a tree of nodes.+newtype DirTree a = DirTree+ { dirTreeNode :: DirTreeNode (DirForest a) a+ }+ deriving (Eq, Ord, NFData, Generic)+++instance Functor DirTree where+ fmap f (DirTree a) = DirTree $ bimap (fmap f) f a++instance Foldable DirTree where+ foldMap f (DirTree e) = bifoldMap (foldMap f) f e++instance Traversable DirTree where+ traverse f (DirTree e) = DirTree <$> bitraverse (traverse f) f e++instance FunctorWithIndex FileKey DirTree+instance FoldableWithIndex FileKey DirTree+instance TraversableWithIndex FileKey DirTree where+ itraverse f (DirTree fs) = DirTree <$> itraverseDirTreeN f fs+ {-# INLINE itraverse #-}++-- | A relative dir tree also exists.+type RelativeDirTree s a = DirTree (RelativeFile s a)++-- | All 'DirTree's are also relative.+asRelativeDirTree :: DirTree a -> RelativeDirTree s a+asRelativeDirTree = fmap Real+++instance (Show a) => Show (DirTree a) where+ showsPrec d c = showParen (d >9) (f $ dirTreeNode c)+ where+ f = \case+ Directory a ->+ showString "directory " . showsPrec 11 a+ File a ->+ showString "file " . showsPrec 11 a++-- | A DirTree is a semigroup, where it merges directories and take the last+-- entry if there files.+--+-- >>> file 'a' <> file 'b'+-- file 'b'+--+-- >>> directory' [ "a" .* 'a', "b" .* 'b'] <> directory' [ "b" .* 'd', "c" .* 'c']+-- directory (fromFileList ["a" .* 'a',"b" .* 'd',"c" .* 'c'])+instance Semigroup (DirTree a) where+ DirTree (Directory as) <> DirTree (Directory bs) =+ DirTree (Directory (as <> bs))+ _ <> a = a++-- | Constructs a dirtree with only a file+file :: a -> DirTree a+file = DirTree . File+{-# INLINE file #-}++-- | Constructs a relative dirtree with only a real file+realfile :: a -> RelativeDirTree s a+realfile = file . Real+{-# INLINE realfile #-}++-- | Constructs a dirtree with a symlink+symlink :: s -> RelativeDirTree s a+symlink = file . Symlink+{-# INLINE symlink #-}++-- | Constructs a dirtree with a directory+directory :: DirForest a -> DirTree a+directory = DirTree . Directory+{-# INLINE directory #-}++-- | Constructs a dirtree with a file list+directory' :: [(String, DirTree a)] -> DirTree a+directory' = DirTree . Directory . DirForest . fromFileList+{-# INLINE directory' #-}++-- | Constructs a dirtree with a empty directory+emptyDirectory :: DirTree a+emptyDirectory = directory' []+{-# INLINE emptyDirectory #-}++-- | Create a file+(.*) :: String -> a -> (String, DirTree a)+(.*) s a = (s, file a)++-- | Create a symbolic link+(.*>) :: String -> s -> (String, RelativeDirTree s a)+(.*>) s a = (s, symlink a)++-- | Create a real file+(.*.) :: String -> a -> (String, RelativeDirTree s a)+(.*.) s a = (s, realfile a)++-- | Create a directory+(./) :: String -> [(String, DirTree a)] -> (String, DirTree a)+(./) s a = (s, directory' a)++-- | Get a `FileKey` from a `FilePath`+fileKeyFromPath :: FilePath -> FileKey+fileKeyFromPath =+ splitDirectories++-- | Get a `FilePath` from a `FileKey`+fileKeyToPath :: FileKey -> FilePath+fileKeyToPath =+ joinPath++-- | 'diffFileKey' produces a filepath which is needed to+-- navigate from one FileKey to a other.+--+-- >>> diffFileKey ["hello", "world"] ["hello"]+-- ".."+--+-- >>> diffFileKey ["hello"] ["hello", "world", "test"]+-- "world/test"+--+-- >>> diffFileKey ["world", "test"] ["hello"]+-- "../../hello"+diffFileKey :: FileKey -> FileKey -> FilePath+diffFileKey f to' =+ let (n, bs) = prefix f to'+ in fileKeyToPath (replicate n ".." ++ bs)+ where+ prefix al@(a:as) bl@(b:bs)+ | a == b = prefix as bs+ | otherwise =+ (length al, bl)+ prefix (_:as) [] =+ (1 + length as, [])+ prefix [] bs =+ (0, bs)++-- | 'diffPath' produces a the filekey at the end of+-- a relative filepath, from one filekey.+--+-- >>> diffPath ["hello", "world"] ".."+-- Just ["hello"]+--+-- >>> diffPath ["hello"] "world/test"+-- Just ["hello","world","test"]+--+-- >>> diffPath ["world", "test"] "../../hello"+-- Just ["hello"]+--+-- >>> diffPath ["world", "test"] "/hello"+-- Nothing+--+-- >>> diffPath ["world", "test"] "../../.."+-- Nothing+diffPath :: FileKey -> FilePath -> Maybe FileKey+diffPath f path+ | isAbsolute path = Nothing+ | otherwise = go (fileKeyFromPath path) (reverse f)+ where+ go = \case+ "..":rest -> \case+ _:as -> go rest as+ [] -> Nothing+ rest -> \m -> Just (reverse m ++ rest)++-- | Alter File is the 'DirTree' version of 'Map.alterF'.+--+-- >>> alterFile (\x -> [Nothing, x, Just (file 'b')]) [] (Just (file 'a'))+-- [Nothing,Just (file 'a'),Just (file 'b')]+alterFile ::+ forall f a. Functor f+ => (Maybe (DirTree a) -> f (Maybe (DirTree a)))+ -> FileKey+ -> Maybe (DirTree a)+ -> f (Maybe (DirTree a))+alterFile fn key = maybe (newFile key) (go key) where+ go key' tree@(DirTree node) =+ case key' of+ [] -> fn (Just tree)+ k : rest ->+ case node of+ Directory a -> Just . directory <$> alterForest fn (k :| rest) a+ File _ -> newFile rest++ newFile :: FileKey -> f (Maybe (DirTree a))+ newFile key' = fmap (createDeepTree key') <$> fn Nothing+{-# INLINE alterFile #-}++-- | Create a recursive `DirTree` from a FileKey and a value.+createDeepFile :: FileKey -> a -> DirTree a+createDeepFile key a =+ createDeepTree key (file a)+{-# INLINE createDeepFile #-}++-- | Create a recursive `DirTree` from a FileKey and a value.+createDeepTree :: FileKey -> DirTree a -> DirTree a+createDeepTree key a =+ foldr (\s f -> directory (singletonForest s f)) a key+{-# INLINE createDeepTree #-}++type instance Index (DirTree a) = FileKey+type instance IxValue (DirTree a) = DirTree a++instance Ixed (DirTree a) where+ ix key fn = go key where+ go key' tree@(DirTree node) =+ case nonEmpty key' of+ Nothing -> fn tree+ Just fk ->+ case node of+ Directory a -> directory <$> ix fk fn a+ File _ -> pure tree+ {-# INLINE ix #-}++-- | Not a completly correct Lens, since it is implossible to+-- delete the current DirTree. To use a correct Lens, see+-- 'alterFile'.+--+-- >>> emptyDirectory & at ["file", "path"] ?~ file 'x'+-- directory (fromFileList ["file" ./ ["path" .* 'x']])+instance At (DirTree a) where+ at k f m = fromMaybe m <$> alterFile f k (Just m)+ {-# INLINE at #-}++-- | This method enables eta reduction of a DirTree a with an index.+iflattenDirTree ::+ (FileKey -> DirTreeNode (FileMap m) a -> m)+ -> DirTree a+ -> m+iflattenDirTree f = go id where+ go fk =+ f (fk []) . first (imap (\k -> go (fk . (k:))) . getInternalFileMap) . dirTreeNode+{-# inline iflattenDirTree #-}++-- | This method enables eta reduction of a DirTree a.+flattenDirTree ::+ (DirTreeNode (FileMap m) a -> m)+ -> DirTree a+ -> m+flattenDirTree f = go where+ go = f . first (fmap go . getInternalFileMap) . dirTreeNode+{-# inline flattenDirTree #-}++-- | Uses a semigroup to join together the results, This is slightly+-- less powerfull than 'iflattenDirTree', but more convinient for+-- summations.+depthfirst ::+ (Semigroup m)+ => (FileKey -> DirTreeNode [String] a -> m)+ -> DirTree a+ -> m+depthfirst f = iflattenDirTree $ \k -> \case+ Directory fm -> sconcat $+ f k (Directory . toFileNames $ fm) :| Data.Foldable.toList fm+ File a -> f k (File a)+{-# inline depthfirst #-}++-- | Find a file given a predicate that takes a `FileKey` and `DirTreeNode`.+findNode ::+ (FileKey -> DirTreeNode [String] a -> Bool)+ -> DirTree a+ -> Maybe (FileKey, DirTreeNode [String] a)+findNode f =+ getFirst . depthfirst (\k a -> First $ guard (f k a) $> (k, a))+{-# inline findNode #-}++-- List all the nodes in the dirtree+listNodes :: DirTree a -> [(FileKey, DirTreeNode [String] a)]+listNodes =+ (`appEndo` []) . depthfirst (\k a -> Endo ((k, a):))+{-# inline listNodes #-}+++-- ** IO Methods++-- | A `Link` can either be `Internal`, pointing to something in the `DirTree` or+-- `External` pointing to an absolute `FilePath`.+data Link+ = Internal !FileKey+ | External !FilePath+ deriving (Show, Eq, Generic, NFData)++-- | Figure out a link from the FileKey and FilePath of Link+toLink :: FileKey -> FilePath -> Link+toLink key f =+ maybe (External f) Internal (diffPath (Prelude.init key) f)+++-- | Reads a DirTree. All file paths are absolute to the filepath+readRelativeDirTree ::+ (FilePath -> IO a)+ -> FilePath+ -> IO (RelativeDirTree Link a)+readRelativeDirTree reader' fp = do+ from' <- canonicalizePath fp+ go from' fp+ where+ go from' fp' = do+ node <- readPath fp'+ DirTree <$> bimapM+ ( fmap (DirForest . fromFileList) . mapM (\k -> (k,) <$> go from' (fp' </> k)) )+ ( bimapM absolute (const $ reader' fp') )+ node+ where+ absolute a+ | isAbsolute a =+ return $ External a+ | otherwise = do+ a' <- canonicalizePath (takeDirectory fp' </> a)+ let a'' = makeRelative from' a'+ return $ if a'' /= a'+ then Internal (fileKeyFromPath a'')+ else External a'++-- | Reads a DirTree and follow all the relative links. Might recurse forever.+readDirTree ::+ (FilePath -> IO a)+ -> FilePath+ -> IO (DirTree a)+readDirTree fn fp =+ readRelativeDirTree fn fp >>= followLinks fn++-- | Follow the links to create the tree. This function might recurse forever.+followLinks :: forall a. (FilePath -> IO a) -> RelativeDirTree Link a -> IO (DirTree a)+followLinks fn tree = go tree where+ go = flattenDirTree $ \case+ File (Symlink a) -> case a of+ Internal s ->+ case tree ^? ix s of+ Just a' ->+ go a'+ Nothing ->+ error $ "Could not find " ++ show s+ ++ " in the dirtree " ++ show (void tree)+ External s ->+ readDirTree fn s++ File (Real a) ->+ return $ file a++ Directory a ->+ directory . DirForest <$> sequence a++-- | Writes a Relative DirTree to a file+writeRelativeDirTree ::+ (FilePath -> a -> IO ())+ -> FilePath+ -> RelativeDirTree Link a+ -> IO ()+writeRelativeDirTree writer fp = depthfirst go where+ go key = \case+ Directory _ ->+ createDirectory fp'+ File a ->+ case a of+ Symlink (External target) ->+ createFileLink target fp'+ Symlink (Internal key') ->+ createFileLink+ (case (key, key') of+ (_:fk', _) -> diffFileKey fk' key'+ ([], []) -> "."+ ([], _) -> error "Fail"+ ) fp'+ Real a' ->+ writer fp' a'+ where fp' = fp </> fileKeyToPath key+{-# INLINE writeRelativeDirTree #-}++-- | Writes a Relative DirTree to a file+writeDirTree ::+ (FilePath -> a -> IO ())+ -> FilePath+ -> DirTree a+ -> IO ()+writeDirTree writer fp = writeRelativeDirTree writer fp . asRelativeDirTree+{-# INLINE writeDirTree #-}+++-- $DirForest+-- A 'DirForest' is the content of a directory. A 'DirForest' is more+-- useful in some cases++newtype DirForest a = DirForest+ { getInternalFileMap :: FileMap (DirTree a)+ } deriving (Eq, Ord, NFData, Generic)++instance Functor DirForest where+ fmap f (DirForest a) = DirForest $ fmap (fmap f) a++instance Foldable DirForest where+ foldMap f (DirForest e) = foldMap (foldMap f) e++instance Traversable DirForest where+ traverse f (DirForest e) = DirForest <$> traverse (traverse f) e++instance FunctorWithIndex ForestFileKey DirForest+instance FoldableWithIndex ForestFileKey DirForest+instance TraversableWithIndex ForestFileKey DirForest where+ itraverse f (DirForest fs) =+ DirForest <$> itraverse (\k -> itraverse (f . (k:|))) fs+ {-# INLINE itraverse #-}++instance Semigroup (DirForest a) where+ (DirForest a) <> (DirForest b) = DirForest (a <> b)++instance Monoid (DirForest a) where+ mempty = DirForest mempty++-- | All entries in a DirForest has to be non-empty+type ForestFileKey = NonEmpty String++-- | Convert a 'ForestFileKey' to a 'FileKey'+fromForestFileKey :: ForestFileKey -> FileKey+fromForestFileKey = toList++-- | Convert a 'FileKey' to a 'ForestFileKey'+toForestFileKey :: FileKey -> Maybe ForestFileKey+toForestFileKey = nonEmpty++-- | Creates an empty forest+emptyForest :: DirForest a+emptyForest = mempty++-- | Creates an singleton forest+singletonForest :: String -> DirTree a -> DirForest a+singletonForest k f =+ DirForest $ singletonFileMap k f++-- | Creates an deep file in a forest+createDeepForest :: ForestFileKey -> DirTree a -> DirForest a+createDeepForest (k :| rest) f =+ singletonForest k (createDeepTree rest f)++-- | A relative dir forest also exists.+type RelativeDirForest s a = DirForest (RelativeFile s a)++-- | All 'DirTree's are also relative.+asRelativeDirForest :: DirForest a -> RelativeDirForest s a+asRelativeDirForest = fmap Real++type instance Index (DirForest a) = ForestFileKey+type instance IxValue (DirForest a) = DirTree a++instance Ixed (DirForest a) where+ ix (k :| key) fn a = DirForest <$> ix k (ix key fn) (getInternalFileMap a)+ -- -- ix key fn . getInternalFileMap+ -- go key' tree@(DirTree node) =+ -- case key' of+ -- [] -> fn tree+ -- k : rest ->+ -- case node of+ -- Directory a -> directory <$> ix k (ix rest fn) a+ -- File _ -> pure tree+ {-# INLINE ix #-}++alterForest ::+ forall f a. Functor f+ => (Maybe (DirTree a) -> f (Maybe (DirTree a)))+ -> ForestFileKey+ -> DirForest a+ -> f (DirForest a)+alterForest fn (k :| key) a =+ DirForest <$> alterFileMap (alterFile fn key) k (getInternalFileMap a)++-- >>> emptyDirForest & at ("file" :| ["path"]) ?~ file 'x'+-- fromFileList ["file" ./ ["path" .* 'x']]+instance At (DirForest a) where+ at k f = alterForest f k+ {-# INLINE at #-}++makeWrapped ''DirTree+makeWrapped ''DirForest++instance AsDirTreeNode (DirTree a) (DirForest a) a where+ _DirTreeNode = _Wrapped+ {-# INLINE _DirTreeNode #-}
+ src/System/DirTree/Zip.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}+{-|+Module : System.DirTree.Zip+Copyright : (c) Christian Gram Kalhauge, 2019+License : MIT+Maintainer : kalhauge@cs.ucla.edu++Enables reading and writeing zipfiles using dirtrees. It is+not a complete 1-1 mapping but for many usecases it gets the+job done.++It is based of the `zip-archive` library, which can be used+if more control is needed.+-}++module System.DirTree.Zip+ (+ entriesToDirForest+ , entriesFromDirForest++ -- * Helpers+ , entryToDirForest+ , entryFromFile++ , files+ , entries+ -- * Re-Exports+ , toArchive+ , fromArchive+ )+where++-- base+import Data.Foldable+import Data.Maybe+import Data.Bits+import System.Posix.Files (symbolicLinkMode, stdFileMode)++-- lens+import Control.Lens++-- zip-archive+import Codec.Archive.Zip++-- dirtree+import System.DirTree++-- bytestring+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString.Lazy.Char8 as BLC+++-- | Convert a entry to a single filemap, fails if the entry path is empty.+entryToDirForest ::+ Entry+ -> Maybe (DirForest Entry)+entryToDirForest e =+ flip createDeepForest (file e)+ <$> toForestFileKey (fileKeyFromPath (eRelativePath e))++-- | Convert entries to a 'FileMap' of 'RelativeDirTree'+entriesToDirForest ::+ [Entry]+ -> Maybe (RelativeDirForest Link BL.ByteString)+entriesToDirForest =+ fmap (imap parseEntry . fold)+ . traverse entryToDirForest+ where+ parseEntry key e =+ case symbolicLinkEntryTarget e of+ Just f -> Symlink . toLink (fromForestFileKey key) $ f+ Nothing -> Real $ fromEntry e++-- | Create a single entry from a file. This also handles symlinks, but changes+-- saves all files with the 'stdFileMode'.+entryFromFile :: Integer -> FileKey -> RelativeFile Link BL.ByteString -> Entry+entryFromFile i key = \case+ Real bs -> toEntry (fileKeyToPath key) i bs+ Symlink x ->+ toSymlinkEntry (fileKeyToPath key) $ case x of+ Internal trgt -> diffFileKey (init key) trgt+ External f -> f+ where+ toSymlinkEntry path t =+ let e = toEntry path i (BLC.pack t)+ in e { eExternalFileAttributes =+ eExternalFileAttributes e .|. shiftL (fromIntegral ( symbolicLinkMode .|. stdFileMode)) 16+ , eVersionMadeBy = 798 -- Random high number+ }++-- | Create a list of enties from a FileMap.+entriesFromDirForest ::+ Integer+ -> RelativeDirForest Link BL.ByteString+ -> [Entry]+entriesFromDirForest i =+ toList . imap (\k -> entryFromFile i (fromForestFileKey k))++-- | A simple lens into the entries of an archive.+entries :: Lens' Archive [Entry]+entries = lens zEntries (\a b -> a { zEntries = b })++-- | A list of entries can be seen as a FileMap of+entriesAsDirForest :: Integer -> Iso' [Entry] (RelativeDirForest Link BL.ByteString)+entriesAsDirForest i = iso from' to' where+ from' = fromJust . entriesToDirForest+ to' = entriesFromDirForest i++-- | A lens to get and set the files of an archive. Uses sparingly on+-- big archvies as it will convert forth and back.+files :: Lens' Archive (RelativeDirForest Link BL.ByteString)+files = entries . entriesAsDirForest 0
+ test/System/DirTree/ZipSpec.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE OverloadedStrings #-}+-- |+module System.DirTree.ZipSpec where++import Test.Hspec hiding (shouldBe)+import Test.Hspec.Expectations.Pretty++import qualified Data.ByteString.Lazy as BL++import Control.Lens++import Codec.Archive.Zip++import System.Directory hiding (findFile)+import System.IO.Error++import System.DirTree+import System.DirTree.Zip++spec :: Spec+spec = do+ describe "reading a zipfile" $ do+ it "can read more.zip" $ do+ zipfile <- toArchive <$> BL.readFile "test/more.zip"+ (zipfile ^. entries . to entriesToDirForest)+ `shouldBe` Just+ ( DirForest . fromFileList $+ [ "data" ./+ [ "abslink" .*> External "/dev/null"+ , "deeplink" .*> Internal ["data","folder","deepfile"]+ , "file" .*. ""+ , "folder" ./+ [ "deepfile" .*. ""+ , "revlink" .*> Internal ["data","file"]+ ]+ , "folderlink" .*> Internal ["data","folder"]+ , "symlink" .*> Internal ["data", "file"]+ ]+ ]+ )++ describe "writing a zipfile" $ do+ before (do _ <- tryIOError $ removeDirectoryRecursive "test/zip-output/"+ createDirectory "test/zip-output"+ ) $ do+ it "can write more.zip" $ do+ zipfile <- toArchive <$> BL.readFile "test/more.zip"+ let Just forest = zipfile ^. entries . to entriesToDirForest++ let bc = fromArchive $ zipfile & entries .~ entriesFromDirForest 0 forest+ BL.writeFile "test/zip-output/more.zip" bc+ toArchive bc ^. entries . to entriesToDirForest `shouldBe` Just forest
test/System/DirTreeSpec.hs view
@@ -1,6 +1,6 @@ module System.DirTreeSpec where -import Test.Hspec (Spec, describe, it, before, describe)+import Test.Hspec (Spec, it, before, describe) import Test.Hspec.Expectations.Pretty import System.DirTree@@ -17,10 +17,10 @@ getFileType "test/data" `shouldReturn` Directory () it "should find a file" $ do- getFileType "test/data/file" `shouldReturn` File ()+ getFileType "test/data/file" `shouldReturn` File (Real ()) it "should find a symbolic link" $ do- getFileType "test/data/symlink" `shouldReturn` Symlink ()+ getFileType "test/data/symlink" `shouldReturn` File (Symlink ()) it "should throw an IOException if nothing is found" $ do getFileType "test/data/nothing" `shouldThrow` anyException@@ -32,84 +32,85 @@ it "should find a file" $ do readPath "test/data/file" `shouldReturn`- File ()+ File (Real ()) it "should find a symbolic link" $ do readPath "test/data/symlink" `shouldReturn`- Symlink "file"+ File (Symlink "file") it "should find a symbolic to a folder" $ do readPath "test/data/folderlink" `shouldReturn`- Symlink "folder"+ File (Symlink "folder") it "should throw an IOException if nothing exists" $ do readPath "test/data/nothing" `shouldThrow` anyException - describe "readDirTree" $ do+ describe "readRelativeDirTree" $ do it "should read the data directory" $ do- readDirTree (\f -> return $ makeRelative "test/data" f) "test/data" `shouldReturn`- directoryFromFiles- [ ("abslink", symlink (External "/dev/null"))- , ("file", file "file")- , ("folderlink", symlink (Internal ["folder"]))- , ("folder", directoryFromFiles- [ ("revlink", symlink (Internal ["file"]))- , ("deepfile", file "folder/deepfile")- ])- , ("deeplink", symlink (Internal ["deepfile", "folder"]))- , ("symlink", symlink (Internal ["file"]))+ readRelativeDirTree (\f -> return $ makeRelative "test/data" f) "test/data" `shouldReturn`+ directory'+ [ "abslink" .*> External "/dev/null"+ , "file" .*. "file"+ , "folderlink" .*> Internal ["folder"]+ , "folder" ./+ [ "revlink" .*> Internal ["file"]+ , "deepfile" .*. "folder/deepfile"+ ]+ , "deeplink" .*> Internal ["folder", "deepfile"]+ , "symlink" .*> Internal ["file"] ] it "should read the folder in the data directory" $ do x <- makeAbsolute "test/data/file"- readDirTree (\f -> return $ makeRelative "test/data/folder" f) "test/data/folder" `shouldReturn`- directoryFromFiles- [ ("revlink", symlink (External x))- , ("deepfile", file "deepfile")+ readRelativeDirTree (\f -> return $ makeRelative "test/data/folder" f) "test/data/folder" `shouldReturn`+ directory'+ [ "revlink" .*> External x+ , "deepfile" .*. "deepfile" ] describe "followLinks" $ do it "should read and follow the links in the data directory" $ do let relname f = return $ makeRelative "test/data" f- (readDirTree relname "test/data" >>= followLinks relname)+ (readRelativeDirTree relname "test/data" >>= followLinks relname) `shouldReturn`- directoryFromFiles- [ ("symlink", file "file")- , ("file", file "file")- , ("folderlink", directoryFromFiles- [ ("revlink", file "file")- , ("deepfile", file "folder/deepfile")- ])- , ("folder", directoryFromFiles- [ ("revlink", file "file")- , ("deepfile", file "folder/deepfile")- ])- , ("abslink", file "/dev/null")- , ("deeplink", file "folder/deepfile")+ directory'+ [ "symlink" .* "file"+ , "file" .* "file"+ , "folderlink" ./+ [ "revlink" .* "file"+ , "deepfile" .* "folder/deepfile"+ ]+ , "folder" ./+ [ "revlink" .* "file"+ , "deepfile" .* "folder/deepfile"+ ]+ , "abslink" .* "/dev/null"+ , "deeplink" .* "folder/deepfile" ] describe "listNodes" $ do it "should read the data directory" $ do- x <- listNodes <$> readDirTree return "test/data"+ x <- listNodes <$> readRelativeDirTree return "test/data" map fst x `shouldBe` [ [] , ["abslink"] , ["deeplink"] , ["file"] , ["folder"]- , ["deepfile", "folder"]- , ["revlink", "folder"]+ , ["folder", "deepfile"]+ , ["folder", "revlink"] , ["folderlink"] , ["symlink"] ] describe "findNode" $ do+ let isFile f fp _ = (takeBaseName (fileKeyToPath fp)) == f it "can find deepfile" $ do- x <- findNode (\fp _ -> takeBaseName (fileKeyToPath fp) == "deepfile") <$> readDirTree return "test/data"- fmap fst x `shouldBe` Just ["deepfile", "folder"]+ x <- findNode (isFile "deepfile") <$> readDirTree return "test/data"+ fmap fst x `shouldBe` Just ["folder", "deepfile"] it "can't find notafile" $ do- x <- findNode (\fp _ -> takeBaseName (fileKeyToPath fp) == "notafile") <$> readDirTree return "test/data"+ x <- findNode (isFile "notafile") <$> readDirTree return "test/data" fmap fst x `shouldBe` Nothing describe "writeDirTree" $ do@@ -123,61 +124,53 @@ readFile "test/output/newfile" `shouldReturn` "Hello, World!" it "can write a folder" $ do- let folder = directoryFromFiles- [ ("file1", file "Hello, World!" )- , ("file2", file "Some other file" )- , ("file3", symlink (Internal ["file2"]))+ let folder = directory'+ [ "file1" .*. "Hello, World!"+ , "file2" .*. "Some other file"+ , "folder1" ./+ [ "file4" .*. "More files"+ ]+ , "symfile1" .*> Internal ["file2"]+ , "symfile2" .*> Internal ["folder1", "file4"]+ , "symfile3" .*> External "/dev/null" ]- writeDirTree writeFile "test/output/folder" folder- readDirTree readFile "test/output/folder" `shouldReturn` folder+ writeRelativeDirTree writeFile "test/output/folder" folder+ readRelativeDirTree readFile "test/output/folder" `shouldReturn` folder it "can copy a folder" $ do- datatree1 <- readDirTree return "test/data"- writeDirTree (flip copyFile) "test/output/data" datatree1- datatree2 <- readDirTree (return . ("test/data" </>) . makeRelative "test/output/data") "test/output/data"+ datatree1 <- readRelativeDirTree return "test/data"+ writeRelativeDirTree (flip copyFile) "test/output/data" datatree1+ datatree2 <- readRelativeDirTree (return . ("test/data" </>) . makeRelative "test/output/data") "test/output/data" datatree2 `shouldBe` datatree1 describe "semigroup" $ do it "can join trees together" $ do- directoryFromFiles ["a" -.> "x"]- <> directoryFromFiles [ "b" -|> "x" ]- <> directoryFromFiles [ "x" -/> [ "a" -.> "y"]]- <> directoryFromFiles [ "x" -/> [ "b" -.> "y"]]+ directory' ["a" .* "x"]+ <> directory' [ "b" .* "x" ]+ <> directory' [ "x" ./ [ "a" .* "y"]]+ <> directory' [ "x" ./ [ "b" .* "y"]] `shouldBe`- directoryFromFiles- [ "a" -.> "x"- , "b" -|> "x"- , "x" -/>- [ "a" -.> "y"- , "b" -.> "y"+ directory'+ [ "a" .* "x"+ , "b" .* "x"+ , "x" ./+ [ "a" .* "y"+ , "b" .* "y" ] ] it "joins different types to latest file" $ do- directoryFromFiles ["a" -.> "x"]- <> directoryFromFiles [ "a" -|> "y" ]+ directory' ["a" .* "x"]+ <> directory' [ "a" .* "y" ] `shouldBe`- directoryFromFiles- [ "a" -|> "y"- ]+ directory' [ "a" .* "y"] it "can join FileMaps together" $ do- fromFileList ["a" -.> "x"]- <> fromFileList [ "b" -|> "x" ]+ (DirForest . fromFileList) ["a" .* "x"]+ <> (DirForest . fromFileList) [ "b" .* "x" ] `shouldBe`- fromFileList- [ "a" -.> "x"- , "b" -|> "x"+ (DirForest . fromFileList)+ [ "a" .* "x"+ , "b" .* "x" ]-- describe "fromFiles" $ do- it "can create a DirTree from an list of files" $ do- fromFiles [(["a"], Right "x"), (["b", "c"], Left "y") ]- `shouldBe`- (Just $ directoryFromFiles- [ "a" -.> "x"- , "b" -/>- [ "c" -|> "y"- ]- ])