packages feed

directory-layout 0.2.0.0 → 0.3.0.0

raw patch · 11 files changed

+615/−418 lines, 11 filesdep +HUnitdep +data-defaultdep +directory-layoutdep −parsecdep −transformersPVP ok

version bump matches the API change (PVP)

Dependencies added: HUnit, data-default, directory-layout, doctest, lens, mtl, process, semigroupoids, semigroups, wordexp

Dependencies removed: parsec, transformers

API changes (from Hackage documentation)

- System.Directory.Layout: DirectoryDoesExist :: FilePath -> DLMakeWarning
- System.Directory.Layout: DirectoryDoesNotExist :: FilePath -> DLCheckFailure
- System.Directory.Layout: FileDoesExist :: FilePath -> DLMakeWarning
- System.Directory.Layout: FileDoesNotExist :: FilePath -> DLCheckFailure
- System.Directory.Layout: FileWrongContents :: FilePath -> Text -> DLCheckFailure
- System.Directory.Layout: data DLCheckFailure
- System.Directory.Layout: data DLMakeWarning
- System.Directory.Layout: layout :: Text -> Either String Layout
- System.Directory.Layout: layout' :: Text -> Either String Layout
- System.Directory.Layout.Check: DirectoryDoesNotExist :: FilePath -> DLCheckFailure
- System.Directory.Layout.Check: FileDoesNotExist :: FilePath -> DLCheckFailure
- System.Directory.Layout.Check: FileWrongContents :: FilePath -> Text -> DLCheckFailure
- System.Directory.Layout.Check: check :: Layout -> FilePath -> IO [DLCheckFailure]
- System.Directory.Layout.Check: data DLCheckFailure
- System.Directory.Layout.Check: instance Eq DLCheckFailure
- System.Directory.Layout.Check: instance Ord DLCheckFailure
- System.Directory.Layout.Check: instance Read DLCheckFailure
- System.Directory.Layout.Check: instance Show DLCheckFailure
- System.Directory.Layout.Make: DirectoryDoesExist :: FilePath -> DLMakeWarning
- System.Directory.Layout.Make: FileDoesExist :: FilePath -> DLMakeWarning
- System.Directory.Layout.Make: data DLMakeWarning
- System.Directory.Layout.Make: instance Eq DLMakeWarning
- System.Directory.Layout.Make: instance Ord DLMakeWarning
- System.Directory.Layout.Make: instance Read DLMakeWarning
- System.Directory.Layout.Make: instance Show DLMakeWarning
- System.Directory.Layout.Make: make :: Layout -> FilePath -> IO [DLMakeWarning]
- System.Directory.Layout.Parser: layout :: Text -> Either String Layout
- System.Directory.Layout.Parser: layout' :: Text -> Either String Layout
+ System.Directory.Layout: CD :: IOErrorType -> FilePath -> LayoutException
+ System.Directory.Layout: CF :: IOErrorType -> FilePath -> LayoutException
+ System.Directory.Layout: DE :: IOErrorType -> FilePath -> LayoutException
+ System.Directory.Layout: FE :: IOErrorType -> FilePath -> LayoutException
+ System.Directory.Layout: RF :: IOErrorType -> FilePath -> Text -> LayoutException
+ System.Directory.Layout: data LayoutException
+ System.Directory.Layout.Errored: CD :: IOErrorType -> FilePath -> LayoutException
+ System.Directory.Layout.Errored: CF :: IOErrorType -> FilePath -> LayoutException
+ System.Directory.Layout.Errored: DE :: IOErrorType -> FilePath -> LayoutException
+ System.Directory.Layout.Errored: FE :: IOErrorType -> FilePath -> LayoutException
+ System.Directory.Layout.Errored: RF :: IOErrorType -> FilePath -> Text -> LayoutException
+ System.Directory.Layout.Errored: anyfail :: MonadWriter [w] m => m (Either w a) -> m ()
+ System.Directory.Layout.Errored: createDirectory :: MonadIO m => FilePath -> m (Either LayoutException ())
+ System.Directory.Layout.Errored: createFile :: MonadIO m => FilePath -> Maybe Text -> m (Either LayoutException ())
+ System.Directory.Layout.Errored: data LayoutException
+ System.Directory.Layout.Errored: directoryExists :: MonadIO m => FilePath -> m (Either LayoutException ())
+ System.Directory.Layout.Errored: fileExists :: MonadIO m => FilePath -> m (Either LayoutException ())
+ System.Directory.Layout.Errored: instance Eq LayoutException
+ System.Directory.Layout.Errored: instance Show LayoutException
+ System.Directory.Layout.Errored: readFile :: MonadIO m => FilePath -> Text -> m (Either LayoutException ())
+ System.Directory.Layout.Errored: relative :: FilePath -> LayoutException -> LayoutException
+ System.Directory.Layout.Internal: D :: !FilePath -> !Layout -> !(DL a) -> DL a
+ System.Directory.Layout.Internal: E :: !a -> DL a
+ System.Directory.Layout.Internal: F :: !FilePath -> !Layout -> !(DL a) -> DL a
+ System.Directory.Layout.Internal: T :: !Text -> !a -> DL a
+ System.Directory.Layout.Internal: data DL a
+ System.Directory.Layout.Internal: instance Applicative DL
+ System.Directory.Layout.Internal: instance Apply DL
+ System.Directory.Layout.Internal: instance Bind DL
+ System.Directory.Layout.Internal: instance Default a => Default (DL a)
+ System.Directory.Layout.Internal: instance Default a => Monoid (DL a)
+ System.Directory.Layout.Internal: instance Eq a => Eq (DL a)
+ System.Directory.Layout.Internal: instance Foldable DL
+ System.Directory.Layout.Internal: instance Functor DL
+ System.Directory.Layout.Internal: instance Monad DL
+ System.Directory.Layout.Internal: instance Ord a => Ord (DL a)
+ System.Directory.Layout.Internal: instance Read a => Read (DL a)
+ System.Directory.Layout.Internal: instance Semigroup (DL a)
+ System.Directory.Layout.Internal: instance Show a => Show (DL a)
+ System.Directory.Layout.Internal: instance Traversable DL
+ System.Directory.Layout.Internal: type Layout = DL ()
+ System.Directory.Layout.Lens: directory :: FilePath -> IndexedTraversal' FilePath Layout Layout
+ System.Directory.Layout.Lens: file :: FilePath -> IndexedTraversal' FilePath Layout Layout
+ System.Directory.Layout.Lens: text :: Prism Layout Layout Text Text
+ System.Directory.Layout.Traverse: check :: Layout -> FilePath -> IO [LayoutException]
+ System.Directory.Layout.Traverse: make :: Layout -> FilePath -> IO [LayoutException]
- System.Directory.Layout: check :: Layout -> FilePath -> IO [DLCheckFailure]
+ System.Directory.Layout: check :: Layout -> FilePath -> IO [LayoutException]
- System.Directory.Layout: data DL f
+ System.Directory.Layout: data DL a
- System.Directory.Layout: make :: Layout -> FilePath -> IO [DLMakeWarning]
+ System.Directory.Layout: make :: Layout -> FilePath -> IO [LayoutException]

Files

directory-layout.cabal view
@@ -1,5 +1,5 @@ name:          directory-layout-version:       0.2.0.0+version:       0.3.0.0 synopsis:      Declare, construct and verify directory layout description:   Language to express directory layouts category:      System@@ -8,22 +8,49 @@ author:        Matvey Aksenov maintainer:    matvey.aksenov@gmail.com build-type:    Simple-cabal-version: >= 1.8+cabal-version: >= 1.10  library+  default-language: Haskell2010   exposed-modules: System.Directory.Layout-                   System.Directory.Layout.Check-                   System.Directory.Layout.Make-                   System.Directory.Layout.Parser                    System.Directory.Layout.Internal+                   System.Directory.Layout.Errored+                   System.Directory.Layout.Traverse+                   System.Directory.Layout.Lens   hs-source-dirs: src   build-depends: base >= 3 && < 5,                  directory,                  filepath,-                 transformers,-                 parsec,-                 text,+                 mtl,+                 data-default,+                 semigroups,+                 semigroupoids,+                 lens,+                 text+  ghc-options: -Wall+               -fno-warn-unused-do-bind++test-suite basics-suite+  default-language: Haskell2010+  type: exitcode-stdio-1.0+  build-depends: base >= 3 && < 5,+                 directory-layout,+                 HUnit,+                 process,                  QuickCheck+  main-is: tests/Main.hs+  ghc-options: -Wall+               -fno-warn-unused-do-bind++test-suite doctests+  default-language: Haskell2010+  type: exitcode-stdio-1.0+  build-depends: base >= 3 && < 5,+                 directory-layout,+                 doctest,+                 wordexp,+                 lens+  main-is: tests/doctests.hs   ghc-options: -Wall                -fno-warn-unused-do-bind 
src/System/Directory/Layout.hs view
@@ -1,38 +1,40 @@-{-# LANGUAGE UnicodeSyntax #-}+-- | Language to express directory layouts module System.Directory.Layout   ( -- * Layout declaration     DL, Layout, file, file_, directory, directory_-    -- * Layout construction-  , DLMakeWarning(..), make-    -- * Layout verification-  , DLCheckFailure(..), check-    -- * Layout parsers-  , layout, layout'+    -- * Layout traverses+  , make, check+    -- * Errors+  , LayoutException(..)   ) where +import Data.Default (def) import Data.Text (Text)  import System.Directory.Layout.Internal (DL(..), Layout)-import System.Directory.Layout.Check (DLCheckFailure(..), check)-import System.Directory.Layout.Make (DLMakeWarning(..), make)-import System.Directory.Layout.Parser (layout, layout')+import System.Directory.Layout.Traverse (make, check)+import System.Directory.Layout.Errored (LayoutException(..))   -- | Declare file with specified contents-file ∷ FilePath → Text → Layout-file x c = F x (Just c) (return ())+file :: FilePath -> Text -> Layout+file x t = F x (T t ()) def+{-# INLINE file #-}   -- | Declare empty file-file_ ∷ FilePath → Layout-file_ x = F x Nothing (return ())+file_ :: FilePath -> Layout+file_ x = F x def def+{-# INLINE file_ #-}   -- | Declare directory with specified listing-directory ∷ FilePath → Layout → Layout-directory x d = D x d (return ())+directory :: FilePath -> Layout -> Layout+directory x d = D x d def+{-# INLINE directory #-}   -- | Declare empty directory-directory_ ∷ FilePath → Layout-directory_ x = D x (return ()) (return ())+directory_ :: FilePath -> Layout+directory_ x = D x def def+{-# INLINE directory_ #-}
− src/System/Directory/Layout/Check.hs
@@ -1,126 +0,0 @@-{-# LANGUAGE UnicodeSyntax #-}--- | Check if current directory layout agrees with specified one------ For example, suppose there is a tree:------ @--- % tree--- .--- ├── baz--- │   └── twey--- └── foo---     ├── bar---     │   ├── quuz---     │   └── tatata---     └── quux--- @------ then you can write:------ @--- layout = do---   directory \"baz\" $---     file_ \"twey\"---   directory \"foo\" $ do---     directory \"bar\" $ do---       file_ \"quuz\"---       file_ \"tatata\"---     file_ \"quux\"--- @------ and running @check layout \".\"@ should result in @[]@-module System.Directory.Layout.Check-  ( DLCheckFailure(..), check-  ) where--import Control.Arrow (second)-import Control.Monad (unless, when)--import           Control.Monad.Trans.Reader (ReaderT, runReaderT, ask, local)-import           Control.Monad.Trans.Writer (WriterT, execWriterT, tell)-import           Control.Monad.IO.Class (liftIO)-import           Control.Monad.Trans.Class (lift)-import           Data.Text (Text)-import qualified Data.Text.IO as T-import           System.FilePath ((</>), makeRelative)-import           System.Directory--import System.Directory.Layout.Internal----- | Check directory layout corresponds to specified one-check ∷ Layout-      → FilePath            -- ^ Root directory-      → IO [DLCheckFailure] -- ^ List of failures-check z fp = do-  d ← getCurrentDirectory-  fp' ← canonicalizePath fp-  setCurrentDirectory fp'-  xs ← runCheckT (fp', fp') (f z)-  setCurrentDirectory d-  return xs- where-  f (E _) = return ()-  f (F p Nothing x) = fileExists p >> f x-  f (F p (Just c) x) = fileExists p >>= \t → when t (fileContains p c) >> f x-  f (D p x y) = dirExists p >>= \t → when t (changeDir p (f x)) >> f y----- | Data type representing various failures--- that may occur while checking directory layout-data DLCheckFailure =-    FileDoesNotExist FilePath-  | FileWrongContents FilePath Text-  | DirectoryDoesNotExist FilePath-    deriving (Show, Read, Eq, Ord)---type CheckT = ReaderT (FilePath, FilePath) (WriterT [DLCheckFailure] IO)---runCheckT ∷ (FilePath, FilePath) → CheckT a → IO [DLCheckFailure]-runCheckT e = execWriterT . flip runReaderT e----- | File existence check--- emits 'FileDoesNotExist' on failure-fileExists ∷ FilePath → CheckT Bool-fileExists p = do-  (r, d) ← ask-  z ← io $ doesFileExist (d </> p)-  unless z $-    tell' [FileDoesNotExist (makeRelative r d </> p)]-  return z----- | Directory existence check--- emits 'DirectoryDoesNotExist' on failure-dirExists ∷ FilePath → CheckT Bool-dirExists p = do-  (r, d) ← ask-  z ← io $ doesDirectoryExist (d </> p)-  unless z $-    tell' [DirectoryDoesNotExist (makeRelative r d </> p)]-  return z----- | File contents check--- emits 'FileDoesNotExist' on failure-fileContains ∷ FilePath → Text → CheckT ()-fileContains p c = do-  (r, d) ← ask-  z ← io $ T.readFile (d </> p)-  unless (z == c) $-    tell' [FileWrongContents (makeRelative r d </> p) z]---changeDir ∷ FilePath → CheckT () → CheckT ()-changeDir fp = local (second (</> fp))---io ∷ IO a → CheckT a-io = liftIO---tell' ∷ [DLCheckFailure] → CheckT ()-tell' = lift . tell
+ src/System/Directory/Layout/Errored.hs view
@@ -0,0 +1,112 @@+{-# LANGUAGE FlexibleContexts #-}+-- | Wrappers around exception throwing functions and related routines+module System.Directory.Layout.Errored+  ( LayoutException(..)+  , createDirectory, createFile+  , fileExists, directoryExists, readFile+  , anyfail+  , relative+  ) where++import           Control.Exception hiding (try)+import qualified Control.Exception as E+import           Prelude hiding (readFile)+import           System.IO.Error++import           Control.Lens+import           Control.Monad.Trans (MonadIO, liftIO)+import           Control.Monad.Writer.Class (MonadWriter, tell)+import           Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.IO as T+import qualified System.Directory as D+import           System.FilePath (makeRelative)+++try :: IO a -> IO (Either IOException a)+try = E.try++io :: MonadIO m => IO a -> m a+io = liftIO+++-- | Log failures+anyfail :: MonadWriter [w] m => m (Either w a) -> m ()+anyfail mewa = do+  ewa <- mewa+  case ewa of+    Left e -> tell [e]+    _      -> return ()+++-- | Information about cought exceptions in various routines+data LayoutException =+    CD IOErrorType FilePath      -- ^ 'createDirectory' exceptions+  | CF IOErrorType FilePath      -- ^ 'createFile' eceptions+  | FE IOErrorType FilePath      -- ^ 'fileExists' eceptions+  | DE IOErrorType FilePath      -- ^ 'directoryExists' eceptions+  | RF IOErrorType FilePath Text -- ^ 'readFile' eceptions+    deriving (Show, Eq)+++-- | IO-exceptions-free 'System.Directory.createDirectory'+createDirectory :: MonadIO m => FilePath -> m (Either LayoutException ())+createDirectory fp = io $ try (D.createDirectory fp) <&> \x -> case x of+  Right () -> Right ()+  Left  e  -> Left (CD (ioeGetErrorType e) fp)++-- | IO-exceptions-free 'Data.Text.writeFile'+createFile :: MonadIO m => FilePath -> Maybe Text -> m (Either LayoutException ())+createFile fp text = io $ try (createFileX fp text) <&> \x -> case x of+  Right () -> Right ()+  Left  e  -> Left (CF (ioeGetErrorType e) fp)++createFileX :: FilePath -> Maybe Text -> IO ()+createFileX fp text = do+  x <- D.doesFileExist fp+  if x then+    ioError (mkIOError alreadyExistsErrorType "?" Nothing (Just fp))+  else+    T.writeFile fp (maybe T.empty id text)+++-- | 'System.Directory.doesFileExists' that returns 'Either' instead of 'Bool'+fileExists :: MonadIO m => FilePath -> m (Either LayoutException ())+fileExists fp = io $ do+  p <- D.doesFileExist fp+  if p then+    return (Right ())+  else+    return (Left (FE doesNotExistErrorType fp))++-- | 'System.Directory.doesDirectoryExists' that returns 'Either' instead of 'Bool'+directoryExists :: MonadIO m => FilePath -> m (Either LayoutException ())+directoryExists fp = io $ do+  p <- D.doesDirectoryExist fp+  if p then+    return (Right ())+  else+    return (Left (DE doesNotExistErrorType fp))++-- | IO-exceptions-free 'Data.Text.readFile'+readFile :: MonadIO m => FilePath -> Text -> m (Either LayoutException ())+readFile fp text = io $ try (readFileX fp text) <&> \x -> case x of+  Right () -> Right ()+  Left  e  -> Left (RF (ioeGetErrorType e) fp text)++readFileX :: FilePath -> Text -> IO ()+readFileX fp text = do+  text' <- T.readFile fp+  if text /= text' then+    ioError (mkIOError userErrorType "?" Nothing (Just fp))+  else+    return ()+++-- | Make paths in 'LayoutException' relative to given 'FilePath'+relative :: FilePath -> LayoutException -> LayoutException+relative r (CD t fp)   = CD t (makeRelative r fp)+relative r (CF t fp)   = CF t (makeRelative r fp)+relative r (FE t fp)   = FE t (makeRelative r fp)+relative r (DE t fp)   = DE t (makeRelative r fp)+relative r (RF t fp c) = RF t (makeRelative r fp) c
src/System/Directory/Layout/Internal.hs view
@@ -1,53 +1,103 @@-{-# LANGUAGE UnicodeSyntax #-}-{-# OPTIONS_HADDOCK hide #-}+-- | Free monad based directory layouts module System.Directory.Layout.Internal   ( DL(..), Layout   ) where -import Control.Applicative-import Control.Arrow+import Control.Applicative (Applicative(..), (<$>))+import Data.Foldable (Foldable(..))+import Data.Traversable (Traversable(..), fmapDefault, foldMapDefault)+import Data.Monoid (Monoid(..)) +import Data.Default (Default(..))+import Data.Functor.Apply (Apply(..))+import Data.Functor.Bind (Bind(..))+import Data.Semigroup (Semigroup(..)) import Data.Text (Text)-import Test.QuickCheck  --- | Abstract data type representing directory tree is nice-data DL f-  = E f-  | F FilePath (Maybe Text) (DL f)-  | D FilePath (DL ()) (DL f)-    deriving (Show, Read)+-- | Type synonym to save some acrobatics+type Layout = DL ()  --- | But type synonym is nicer-type Layout = DL ()+-- | Representation of directory layouts+--+-- Invariants:+--+--  * 'F' second argument is never @D _ _ _@ or @F _ _ _@ itself+--+--  * 'F' third argument is never @T _ _@+--+--  * 'D' second argument is never @T _ _@+--+--  * 'D' third argument is never @T _ _@+data DL a+  = E !a                        -- ^ Emptyness, nothing found here+  | T !Text !a                  -- ^ File contents+  | F !FilePath !Layout !(DL a) -- ^ File node+  | D !FilePath !Layout !(DL a) -- ^ Directory node+    deriving (Show, Read, Eq, Ord) +instance Default a => Default (DL a) where+  def = E def+  {-# INLINE def #-} +instance Semigroup (DL a) where+  E _      <> b   = b+  T _ _    <> b   = b+  F f t l  <> b   = F f t (l  <> b)+  D f l l' <> b   = D f l (l' <> b)+  {-# INLINE (<>) #-}++instance Default a => Monoid (DL a) where+  mempty = def+  {-# INLINE mempty #-}++  mappend = (<>)+  {-# INLINE mappend #-}+ instance Functor DL where-  fmap f (E x) = E (f x)-  fmap f (F fp c x) = F fp c (fmap f x)-  fmap f (D fp x y) = D fp x (fmap f y)+  fmap = fmapDefault+  {-# INLINE fmap #-} +instance Apply DL where+  E f      <.> E x      = E (f x)+  E f      <.> T t x    = T t (f x)+  T t f    <.> E x      = T t (f x)+  T t f    <.> T _ x    = T t (f x)+  f        <.> F fp c x = F fp c (f <.> x)+  f        <.> D fp l x = D fp l (f <.> x)+  F fp c f <.> x        = F fp c (f <.> x)+  D fp l f <.> x        = D fp l (f <.> x)+  {-# INLINE (<.>) #-} +instance Applicative DL where+  pure = E+  {-# INLINE pure #-}++  (<*>) = (<.>)+  {-# INLINE (<*>) #-}++instance Bind DL where+  E x      >>- f = f x+  T _ x    >>- f = f x+  F fp c x >>- f = F fp c (x >>- f)+  D fp x y >>- f = D fp x (y >>- f)+  {-# INLINE (>>-) #-}+ instance Monad DL where-  return = E-  E x >>= f = f x-  F fp c x >>= f = F fp c (x >>= f)-  D fp x y >>= f = D fp x (y >>= f)+  return = pure+  {-# INLINE return #-} +  (>>=) = (>>-)+  {-# INLINE (>>=) #-} --- Make arbitrary layout of reasonable size--- Frequencies are pretty /arbitrary/ chosen with layout construction termination in mind-instance Arbitrary a ⇒ Arbitrary (DL a) where-  arbitrary = snd <$> generator 0-   where-    generator ∷ Arbitrary a ⇒ Int → Gen (Int, DL a)-    generator n = frequency-      [ (8, do-          (n', g') ← generator (succ n)-          generator n' <&> second (D (show n) g'))-      , (20, generator (succ n) <&> second (F (show n) Nothing))-      , (10, arbitrary <&> \r → (n, E r))-      ]-     where-      (<&>) = flip fmap+instance Foldable DL where+  foldMap = foldMapDefault+  {-# INLINE foldMap #-}++instance Traversable DL where+  traverse f (E x)      = E      <$> f x+  traverse f (T t x)    = T t    <$> f x+  traverse f (F fp t x) = F fp t <$> traverse f x+  traverse f (D fp x y) = D fp x <$> traverse f y+  {-# INLINE traverse #-}
+ src/System/Directory/Layout/Lens.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RankNTypes #-}+-- | "Control.Lens" based extractors for 'DL'+module System.Directory.Layout.Lens+  ( -- $setup+    text, file, directory+  ) where++import Control.Applicative (pure)++import Control.Lens+import Data.Text (Text)++import System.Directory.Layout.Internal (DL(..), Layout)+++-- $setup+--+-- >>> :set -XOverloadedStrings+-- >>> import           Control.Lens+-- >>> let layout = F "foo" (T "not empty" ()) (D "bar" (F "baz" (E ()) (F "quux" (T "something" ()) (E ()))) (F "swaks" (E ()) (E ())))+++-- | Get 'Text' out of the current 'Layout' (if possible)+--+-- >>> layout ^? text+-- Nothing+-- >>> layout ^? file "foo" . text+-- Just "not empty"+-- >>> layout ^? directory "bar" . file "quux" . text+-- Just "something"+text :: Prism Layout Layout Text Text+text = prism' (\t -> T t ()) $ \s -> case s of+  T t _ -> Just t+  _     -> Nothing+{-# INLINE text #-}+++-- | Look into the file in the current 'Layout' (if possible)+--+-- >>> layout ^? file "biz"+-- Nothing+-- >>> layout ^? file "swaks"+-- Just (E ())+-- >>> layout ^? directory "bar" . file "baz"+-- Just (E ())+file :: FilePath -> IndexedTraversal' FilePath Layout Layout+file k f = go+ where+  go (E x)      = pure (E x)+  go (T t x)    = pure (T t x)+  go (F k' l x)+    | k == k'   = indexed f k l <&> \l' -> F k' l' x+    | otherwise = go x+  go (D _ _ x)  = go x+  {-# INLINE go #-}+{-# INLINE file #-}+++-- | Go into the directory in the current 'Layout' (if possible)+--+-- >>> layout ^? directory "foo"+-- Nothing+-- >>> layout ^? directory "bar"+-- Just (F "baz" (E ()) (F "quux" (T "something" ()) (E ())))+directory :: FilePath -> IndexedTraversal' FilePath Layout Layout+directory k f = go+ where+  go (E x)      = pure (E x)+  go (T t x)    = pure (T t x)+  go (F _ _ x)  = go x+  go (D k' l x)+    | k == k'   = indexed f k l <&> \l' -> D k' l' x+    | otherwise = go x+  {-# INLINE go #-}+{-# INLINE directory #-}
− src/System/Directory/Layout/Make.hs
@@ -1,127 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE UnicodeSyntax #-}--- | Make layout as specified------ For example, suppose you are in an empty directory------ @--- % tree--- .--- @------ and you've written simple layout:------ @--- layout = do---   directory \"baz\" $---     file_ \"twey\"---   directory \"foo\" $ do---     directory \"bar\" $ do---       file_ \"quuz\"---       file_ \"tatata\"---     file_ \"quux\"--- @------ then running it should result in this directory tree:------ @--- % tree--- .--- ├── baz--- │   └── twey--- └── foo---     ├── bar---     │   ├── quuz---     │   └── tatata---     └── quux--- @----module System.Directory.Layout.Make-  ( DLMakeWarning(..), make-  ) where--import Control.Arrow (second)--import           Control.Monad.Trans.Reader (ReaderT, runReaderT, ask, local)-import           Control.Monad.Trans.Writer (WriterT, execWriterT, tell)-import           Control.Monad.IO.Class (liftIO)-import           Control.Monad.Trans.Class (lift)-import           Data.Text (Text)-import qualified Data.Text.IO as T-import           System.FilePath ((</>), makeRelative)-import           System.Directory--import System.Directory.Layout.Internal----- | Infect file layout with stuff from script-make ∷ Layout-     → FilePath          -- ^ Root directory-     → IO [DLMakeWarning] -- ^ List of warnings-make z fp = do-  d ← getCurrentDirectory-  fp' ← canonicalizePath fp-  setCurrentDirectory fp'-  xs ← runRunT (fp', fp') (f z)-  setCurrentDirectory d-  return xs- where-  f (E _) = return ()-  f (F p Nothing x) = touchFile p >> f x-  f (F p (Just c) x) = touchFile p >> infectFile p c >> f x-  f (D p x y) = createDir p >> changeDir p (f x) >> f y----- | Data type representing various warnings--- that may occur while infecting directory layout-data DLMakeWarning =-    FileDoesExist FilePath-  | DirectoryDoesExist FilePath-    deriving (Show, Read, Eq, Ord)---type RunT = ReaderT (FilePath, FilePath) (WriterT [DLMakeWarning] IO)---runRunT ∷ (FilePath, FilePath) → RunT a → IO [DLMakeWarning]-runRunT e = execWriterT . flip runReaderT e----- | File creation--- emits 'FileDoesExist' if file exists already-touchFile ∷ FilePath → RunT ()-touchFile p = do-  (r, d) ← ask-  z ← io $ doesFileExist (d </> p)-  if z-    then tell' [FileDoesExist (makeRelative r d </> p)]-    else io $ T.writeFile (d </> p) ""---infectFile ∷ FilePath → Text → RunT ()-infectFile p c = do-  (_, d) ← ask-  io $ T.writeFile (d </> p) c----- | Directory creation--- emits 'DirectoryDoesExist' if directory exists already-createDir ∷ FilePath → RunT ()-createDir p = do-  (r, d) ← ask-  z ← io $ doesDirectoryExist (d </> p)-  if z-    then tell' [DirectoryDoesExist (makeRelative r d </> p)]-    else io $ createDirectory (d </> p)---changeDir ∷ FilePath → RunT () → RunT ()-changeDir fp = local (second (</> fp))---io ∷ IO a → RunT a-io = liftIO---tell' ∷ [DLMakeWarning] → RunT ()-tell' = lift . tell
− src/System/Directory/Layout/Parser.hs
@@ -1,104 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE UnicodeSyntax #-}--- | Parser for text format for DL data structure, for example------ @--- c/--- ..x--- ..y--- ....n--- ....n--- ....--- ..z--- ....t--- ....t--- ....--- d/--- @------ where '.' stands for space is equivalent of------ @--- do directory \"c\" $ do---      file_ \"x\"---      file \"y\" \"n\nn\n\"---      file \"z\" \"t\nt\n\"---    directory_ \"d\"--- @----module System.Directory.Layout.Parser-  ( layout, layout'-  ) where--import Control.Applicative-import Control.Arrow (left)-import Control.Monad (guard)-import Data.Functor.Identity (Identity)-import Data.List (intercalate)--import qualified Data.Text as T-import qualified Data.Text.Lazy as LT-import           Text.Parsec hiding ((<|>), many)-import           Text.Parsec.Text ()-import           Text.Parsec.Text.Lazy ()--import System.Directory.Layout.Internal----- | lazy 'Text' parser-layout ∷ LT.Text → Either String Layout-layout = glayout----- | strict 'Text' parser-layout' ∷ T.Text → Either String Layout-layout' = glayout---glayout ∷ Stream s Identity Char ⇒ s → Either String Layout-glayout = left show . parse (sequence_ <$> many (p_any 0)) "(layout parser)"---p_any ∷ Stream s Identity Char ⇒ Int → Parsec s u Layout-p_any n = try (p_directory n) <|> p_file n---p_directory ∷ Stream s Identity Char ⇒ Int → Parsec s u Layout-p_directory n = do-  name ← p_directory_name-  inner ← sequence_ <$> try (inners p_any n) <|> return (E ())-  return $ D name inner (E ())---p_file ∷ Stream s Identity Char ⇒ Int → Parsec s u Layout-p_file n = do-  name ← p_file_name-  inner ← Just . T.intercalate "\n" <$> try (inners (const p_text) n) <|> return Nothing-  return $ F name inner (E ())---inners ∷ Stream s Identity Char ⇒ (Int → Parsec s u a) → Int → Parsec s u [a]-inners p n = do-  indent ← length <$> many (char ' ')-  guard (indent > n)-  (:) <$> p indent <*> generous_many (string (replicate indent ' ') *> p indent)---generous_many ∷ Parsec s u a → Parsec s u [a]-generous_many p = f- where-  f = try ((:) <$> p <*> f) <|> return []-{-# INLINE generous_many #-}---p_directory_name ∷ Stream s Identity Char ⇒ Parsec s u String-p_directory_name = some (noneOf "/\n") <* char '/' <* char '\n'---p_file_name ∷ Stream s Identity Char ⇒ Parsec s u String-p_file_name = intercalate "." <$> ((some (noneOf "/.\n") `sepBy` char '.') <* char '\n')---p_text ∷ Stream s Identity Char ⇒ Parsec s u T.Text-p_text = T.pack <$> many (noneOf "\n") <* char '\n'
+ src/System/Directory/Layout/Traverse.hs view
@@ -0,0 +1,131 @@+-- | 'Layout' traverses+module System.Directory.Layout.Traverse+  ( make, check+  ) where++import Prelude hiding (readFile)++import Control.Monad.Reader (ReaderT, runReaderT, ask, local)+import Control.Monad.Writer (WriterT, execWriterT)+import Data.Text (Text)+import System.FilePath ((</>))++import System.Directory.Layout.Internal+import System.Directory.Layout.Errored+++type RunT = ReaderT FilePath (WriterT [LayoutException] IO)++runRunT :: FilePath -> RunT a -> IO [LayoutException]+runRunT e = execWriterT . flip runReaderT e++applyTraverse :: (Layout -> RunT ()) -> Layout -> FilePath -> IO [LayoutException]+applyTraverse f z fp = map (relative fp) `fmap` runRunT fp (f z)++changeDir :: FilePath -> RunT () -> RunT ()+changeDir fp = local (</> fp)+++-- | Make layout as specified+--+-- For example, suppose you are in an empty directory+--+-- @+-- % tree+-- .+-- @+--+-- and you've written simple layout:+--+-- @+-- layout = do+--   directory \"baz\" $+--     file_ \"twey\"+--   directory \"foo\" $ do+--     directory \"bar\" $ do+--       file_ \"quuz\"+--       file_ \"tatata\"+--     file_ \"quux\"+-- @+--+-- then running it should result in this directory tree:+--+-- @+-- % tree+-- .+-- ├── baz+-- │   └── twey+-- └── foo+--     ├── bar+--     │   ├── quuz+--     │   └── tatata+--     └── quux+-- @+--+make :: Layout+     -> FilePath             -- ^ Root directory+     -> IO [LayoutException] -- ^ List of warnings+make = applyTraverse go+ where+  go (E _)           = return ()+  go (F p (E _) x)   = makeFile p Nothing >> go x+  go (F p (T t _) x) = makeFile p (Just t) >> go x+  go (D p x y)       = makeDirectory p >> changeDir p (go x) >> go y+  go _               = error "Broken DL () invariant"++makeFile :: FilePath -> Maybe Text -> RunT ()+makeFile p t = ask >>= \d -> anyfail $ createFile (d </> p) t++makeDirectory :: FilePath -> RunT ()+makeDirectory p = ask >>= \d -> anyfail $ createDirectory (d </> p)+++-- | Check directory layout agrees with specified one+--+-- For example, suppose there is a tree:+--+-- @+-- % tree+-- .+-- ├── baz+-- │   └── twey+-- └── foo+--     ├── bar+--     │   ├── quuz+--     │   └── tatata+--     └── quux+-- @+--+-- then you can write:+--+-- @+-- layout = do+--   directory \"baz\" $+--     file_ \"twey\"+--   directory \"foo\" $ do+--     directory \"bar\" $ do+--       file_ \"quuz\"+--       file_ \"tatata\"+--     file_ \"quux\"+-- @+--+-- and running @check layout \".\"@ should result in @[]@+check :: Layout+      -> FilePath             -- ^ Root directory+      -> IO [LayoutException] -- ^ List of failures+check = applyTraverse go+ where+  go :: Layout -> RunT ()+  go (E _)           = return ()+  go (F p (E _) x)   = checkFile p Nothing >> go x+  go (F p (T t _) x) = checkFile p (Just t) >> go x+  go (D p x y)       = checkDirectory p >> changeDir p (go x) >> go y+  go _               = error "Broken DL () invariant"++checkFile :: FilePath -> Maybe Text -> RunT ()+checkFile p t = ask >>= \d -> anyfail $ case t of+  Nothing -> fileExists (d </> p)+  Just t' -> readFile (d </> p) t'++checkDirectory :: FilePath -> RunT ()+checkDirectory p = ask >>= \d -> anyfail $ directoryExists (d </> p)
+ tests/Main.hs view
@@ -0,0 +1,144 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module Main where++import Control.Exception (finally)+import System.Exit (exitSuccess, exitFailure)+import System.IO.Error++import System.Directory.Layout+import Test.HUnit hiding (assert)+import System.Process (rawSystem)+++main ∷ IO ()+main = do+  z ← runTestTT tests+  if errors z + failures z > 0+    then exitFailure+    else exitSuccess+ where+  tests = TestList+    [ TestLabel "trivia1" testTrivia1+    , TestLabel "trivia2" testTrivia2+    , TestLabel "dual1" testDual1+    ]+++testTrivia1 ∷ Test+testTrivia1 = TestCase $ do+  install+  check' >>= assertEqual "correct" []++  install+  rawSystem "rm" ["-rf", "directory-layout-test/x/y"]+  check' >>= assertEqual "deleted y"+    [ DE doesNotExistErrorType "x/y"+    , FE doesNotExistErrorType "x/y/s"+    , FE doesNotExistErrorType "x/y/v"+    ]++  install+  rawSystem "rm" ["-rf", "directory-layout-test/z"]+  check' >>= assertEqual "deleted z"+    [ DE doesNotExistErrorType "z"+    , RF doesNotExistErrorType "z/w" "text"+    ]++  install+  rawSystem "rm" ["-rf", "directory-layout-test/x/y/s"]+  check' >>= assertEqual "deleted s" [FE doesNotExistErrorType "x/y/s"]++  install+  writeFile "directory-layout-test/z/w" "foo"+  check' >>= assertEqual "changed z/w" [RF userErrorType "z/w" "text"]+ `finally`+  rawSystem "rm" ["-rf", "directory-layout-test"]+ where+  install = do+    rawSystem "mkdir" ["--parents", "directory-layout-test"]+    rawSystem "mkdir" ["--parents", "directory-layout-test/x/y"]+    rawSystem "mkdir" ["--parents", "directory-layout-test/z"]+    rawSystem "touch" ["directory-layout-test/x/y/s"]+    rawSystem "touch" ["directory-layout-test/x/y/v"]+    rawSystem "touch" ["directory-layout-test/z/w"]+    writeFile "directory-layout-test/z/w" "text"++  script = do+    directory "x" $+      directory "y" $ do+        file_ "s"+        file_ "v"+    directory "z" $+      file "w" "text"++  check' = check script "directory-layout-test"+++testTrivia2 ∷ Test+testTrivia2 = TestCase $ do+  rawSystem "mkdir" ["--parents", "directory-layout-test"]+  install+  check' >>= assertEqual "correct" []++  install+  rawSystem "rm" ["-rf", "directory-layout-test/x/y"]+  check' >>= assertEqual "deleted y"+    [ DE doesNotExistErrorType "x/y"+    , FE doesNotExistErrorType "x/y/s"+    , FE doesNotExistErrorType "x/y/v"+    ]++  install+  rawSystem "rm" ["-rf", "directory-layout-test/z"]+  check' >>= assertEqual "deleted z"+    [ DE doesNotExistErrorType "z"+    , RF doesNotExistErrorType "z/w" "text"+    ]++  install+  rawSystem "rm" ["-rf", "directory-layout-test/x/y/s"]+  check' >>= assertEqual "deleted s" [FE doesNotExistErrorType "x/y/s"]++  install+  writeFile "directory-layout-test/z/w" "foo"+  check' >>= assertEqual "changed z/w" [RF userErrorType "z/w" "text"]+ `finally`+  rawSystem "rm" ["-rf", "directory-layout-test"]+ where+  install = make script "directory-layout-test"++  script = do+    directory "x" $+      directory "y" $ do+        file_ "s"+        file_ "v"+    directory "z" $+      file "w" "text"++  check' = check script "directory-layout-test"+++testDual1 ∷ Test+testDual1 = TestCase $ do+  rawSystem "mkdir" ["--parents", "directory-layout-test"]+  let s = do+        directory "x" $ do+          directory_ "xx"+          directory "xy" $+            file "xyx" "test1"+          file "xz" "test2"+          file "xz'" "test3"+          file_ "xz''"+        directory "y" $ do+          file "yx" "test4"+          file "yy" "test5"+          file_ "yz"+        directory_ "z"+  test' s+ `finally`+  rawSystem "rm" ["-rf", "directory-layout-test"]+ where+  test' s = make' s >> check' s+  make' s = make s "directory-layout-test"+  check' s = check s "directory-layout-test" >>= assertEqual "dual" []
+ tests/doctests.hs view
@@ -0,0 +1,12 @@+module Main where++import System.Wordexp.Simple+import Test.DocTest+++main :: IO ()+main = do+  x <- wordexp "cabal-dev/packages-*.conf"+  case x of+    y:_ | '*' `notElem` y -> doctest ["-isrc", "-package-db=" ++ y, "src/System/Directory/Layout/Lens.hs"]+    _                     -> doctest ["-isrc", "src/System/Directory/Layout/Lens.hs"]