packages feed

directory-layout 0.4.0.1 → 0.6.0.0

raw patch · 20 files changed

+1447/−892 lines, 20 filesdep +Unixutilsdep +bifunctorsdep +bytestringdep −HUnitdep −QuickCheckdep −data-defaultdep ~basedep ~hspecdep ~lenssetup-changedPVP ok

version bump matches the API change (PVP)

Dependencies added: Unixutils, bifunctors, bytestring, free, template-haskell, transformers, unix, unordered-containers

Dependencies removed: HUnit, QuickCheck, data-default, directory-layout, mtl, process, semigroupoids, wordexp

Dependency ranges changed: base, hspec, lens, semigroups, text

API changes (from Hackage documentation)

- 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: check :: Layout -> FilePath -> IO [LayoutException]
- System.Directory.Layout: data LayoutException
- System.Directory.Layout: data Node a
- System.Directory.Layout: directory :: FilePath -> Layout -> Layout
- System.Directory.Layout: directory_ :: FilePath -> Layout
- System.Directory.Layout: file_ :: FilePath -> Layout
- System.Directory.Layout: fromDirectory :: FilePath -> IO (Either IOException Layout)
- System.Directory.Layout: make :: Layout -> FilePath -> IO [LayoutException]
- System.Directory.Layout: type Layout = Node ()
- 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: T :: !Text -> !a -> Node a
- System.Directory.Layout.Internal: data Node a
- System.Directory.Layout.Internal: instance Applicative Node
- System.Directory.Layout.Internal: instance Apply Node
- System.Directory.Layout.Internal: instance Bind Node
- System.Directory.Layout.Internal: instance Default a => Default (Node a)
- System.Directory.Layout.Internal: instance Default a => Monoid (Node a)
- System.Directory.Layout.Internal: instance Eq a => Eq (Node a)
- System.Directory.Layout.Internal: instance Foldable Node
- System.Directory.Layout.Internal: instance Functor Node
- System.Directory.Layout.Internal: instance Monad Node
- System.Directory.Layout.Internal: instance Ord a => Ord (Node a)
- System.Directory.Layout.Internal: instance Read a => Read (Node a)
- System.Directory.Layout.Internal: instance Semigroup (Node a)
- System.Directory.Layout.Internal: instance Show a => Show (Node a)
- System.Directory.Layout.Internal: instance Traversable Node
- System.Directory.Layout.Internal: type Layout = Node ()
- System.Directory.Layout.Lens: directory :: FilePath -> IndexedTraversal' FilePath Layout Layout
- System.Directory.Layout.Lens: file :: FilePath -> IndexedTraversal' FilePath Layout Layout
- System.Directory.Layout.Lens: name :: Traversal' Layout FilePath
- System.Directory.Layout.Lens: names :: Traversal' Layout FilePath
- System.Directory.Layout.Lens: next :: Traversal' Layout Layout
- System.Directory.Layout.Lens: node :: FilePath -> IndexedTraversal' FilePath Layout Layout
- System.Directory.Layout.Lens: text :: Prism' Layout Text
- System.Directory.Layout.Traverse: check :: Layout -> FilePath -> IO [LayoutException]
- System.Directory.Layout.Traverse: make :: Layout -> FilePath -> IO [LayoutException]
+ System.Directory.Layout: Binary :: ByteString -> Contents
+ System.Directory.Layout: CopyOf :: FilePath -> Contents
+ System.Directory.Layout: GroupID :: GroupID -> Group
+ System.Directory.Layout: Groupname :: String -> Group
+ System.Directory.Layout: Text :: Text -> Contents
+ System.Directory.Layout: UserID :: UserID -> User
+ System.Directory.Layout: Username :: String -> User
+ System.Directory.Layout: anything :: Maybe a
+ System.Directory.Layout: binary :: ByteString -> Contents
+ System.Directory.Layout: contents :: Traversal' (Layout a) (Maybe Contents)
+ System.Directory.Layout: copyOf :: FilePath -> Contents
+ System.Directory.Layout: data Contents
+ System.Directory.Layout: data Group
+ System.Directory.Layout: data Layout a
+ System.Directory.Layout: data User
+ System.Directory.Layout: dedent :: QuasiQuoter
+ System.Directory.Layout: dir :: String -> Layout a -> Layout ()
+ System.Directory.Layout: dirs :: [String] -> Layout () -> Layout ()
+ System.Directory.Layout: emptydir :: String -> Layout ()
+ System.Directory.Layout: exists :: Traversal' (Layout a) Bool
+ System.Directory.Layout: focus :: String -> Traversal' (Layout ()) (Layout ())
+ System.Directory.Layout: gid :: GroupID -> Group
+ System.Directory.Layout: group :: Traversal' (Layout a) (Maybe Group)
+ System.Directory.Layout: groupname :: String -> Group
+ System.Directory.Layout: into :: String -> Traversal' (Layout ()) (Layout ())
+ System.Directory.Layout: mode :: Traversal' (Layout a) (Maybe FileMode)
+ System.Directory.Layout: source :: Traversal' (Layout a) String
+ System.Directory.Layout: symlink :: String -> FilePath -> Layout ()
+ System.Directory.Layout: text :: Text -> Contents
+ System.Directory.Layout: uid :: UserID -> User
+ System.Directory.Layout: user :: Traversal' (Layout a) (Maybe User)
+ System.Directory.Layout: username :: String -> User
+ System.Directory.Layout.Internal: Aux :: (Maybe User) -> (Maybe Group) -> (Maybe FileMode) -> Aux
+ System.Directory.Layout.Internal: Binary :: ByteString -> Contents
+ System.Directory.Layout.Internal: CopyOf :: FilePath -> Contents
+ System.Directory.Layout.Internal: GroupID :: GroupID -> Group
+ System.Directory.Layout.Internal: Groupname :: String -> Group
+ System.Directory.Layout.Internal: L :: Free F a -> Layout a
+ System.Directory.Layout.Internal: SL :: String -> FilePath -> Bool -> Aux -> a -> F a
+ System.Directory.Layout.Internal: Text :: Text -> Contents
+ System.Directory.Layout.Internal: UserID :: UserID -> User
+ System.Directory.Layout.Internal: Username :: String -> User
+ System.Directory.Layout.Internal: anything :: Maybe a
+ System.Directory.Layout.Internal: aux :: Traversal' (Layout a) Aux
+ System.Directory.Layout.Internal: binary :: ByteString -> Contents
+ System.Directory.Layout.Internal: contents :: Traversal' (Layout a) (Maybe Contents)
+ System.Directory.Layout.Internal: copyOf :: FilePath -> Contents
+ System.Directory.Layout.Internal: data Aux
+ System.Directory.Layout.Internal: data Contents
+ System.Directory.Layout.Internal: data F a
+ System.Directory.Layout.Internal: data Group
+ System.Directory.Layout.Internal: data User
+ System.Directory.Layout.Internal: defaux :: Aux
+ System.Directory.Layout.Internal: dir :: String -> Layout a -> Layout ()
+ System.Directory.Layout.Internal: dirs :: [String] -> Layout () -> Layout ()
+ System.Directory.Layout.Internal: emptydir :: String -> Layout ()
+ System.Directory.Layout.Internal: exists :: Traversal' (Layout a) Bool
+ System.Directory.Layout.Internal: file :: String -> Layout ()
+ System.Directory.Layout.Internal: focus :: String -> Traversal' (Layout ()) (Layout ())
+ System.Directory.Layout.Internal: gid :: GroupID -> Group
+ System.Directory.Layout.Internal: group :: Traversal' (Layout a) (Maybe Group)
+ System.Directory.Layout.Internal: groupname :: String -> Group
+ System.Directory.Layout.Internal: innards :: Traversal' (Layout a) (Layout a)
+ System.Directory.Layout.Internal: instance Applicative Layout
+ System.Directory.Layout.Internal: instance Constructor C1_0Aux
+ System.Directory.Layout.Internal: instance Constructor C1_0Contents
+ System.Directory.Layout.Internal: instance Constructor C1_0F
+ System.Directory.Layout.Internal: instance Constructor C1_0Group
+ System.Directory.Layout.Internal: instance Constructor C1_0Layout
+ System.Directory.Layout.Internal: instance Constructor C1_0User
+ System.Directory.Layout.Internal: instance Constructor C1_1Contents
+ System.Directory.Layout.Internal: instance Constructor C1_1F
+ System.Directory.Layout.Internal: instance Constructor C1_1Group
+ System.Directory.Layout.Internal: instance Constructor C1_1User
+ System.Directory.Layout.Internal: instance Constructor C1_2Contents
+ System.Directory.Layout.Internal: instance Constructor C1_2F
+ System.Directory.Layout.Internal: instance Constructor C1_3F
+ System.Directory.Layout.Internal: instance Data Contents
+ System.Directory.Layout.Internal: instance Datatype D1Aux
+ System.Directory.Layout.Internal: instance Datatype D1Contents
+ System.Directory.Layout.Internal: instance Datatype D1F
+ System.Directory.Layout.Internal: instance Datatype D1Group
+ System.Directory.Layout.Internal: instance Datatype D1Layout
+ System.Directory.Layout.Internal: instance Datatype D1User
+ System.Directory.Layout.Internal: instance Eq (Layout a)
+ System.Directory.Layout.Internal: instance Eq Aux
+ System.Directory.Layout.Internal: instance Eq Contents
+ System.Directory.Layout.Internal: instance Eq Group
+ System.Directory.Layout.Internal: instance Eq User
+ System.Directory.Layout.Internal: instance Eq a => Eq (F a)
+ System.Directory.Layout.Internal: instance Foldable F
+ System.Directory.Layout.Internal: instance Foldable Layout
+ System.Directory.Layout.Internal: instance Functor F
+ System.Directory.Layout.Internal: instance Functor Layout
+ System.Directory.Layout.Internal: instance Generic (F a)
+ System.Directory.Layout.Internal: instance Generic (Layout a)
+ System.Directory.Layout.Internal: instance Generic Aux
+ System.Directory.Layout.Internal: instance Generic Contents
+ System.Directory.Layout.Internal: instance Generic Group
+ System.Directory.Layout.Internal: instance Generic User
+ System.Directory.Layout.Internal: instance IsString Contents
+ System.Directory.Layout.Internal: instance IsString Group
+ System.Directory.Layout.Internal: instance IsString User
+ System.Directory.Layout.Internal: instance Monad Layout
+ System.Directory.Layout.Internal: instance Selector S1_0_0Layout
+ System.Directory.Layout.Internal: instance Semigroup (Layout a)
+ System.Directory.Layout.Internal: instance Show Aux
+ System.Directory.Layout.Internal: instance Show Group
+ System.Directory.Layout.Internal: instance Show User
+ System.Directory.Layout.Internal: instance Traversable F
+ System.Directory.Layout.Internal: instance Traversable Layout
+ System.Directory.Layout.Internal: instance Typeable Aux
+ System.Directory.Layout.Internal: instance Typeable Contents
+ System.Directory.Layout.Internal: instance Typeable Group
+ System.Directory.Layout.Internal: instance Typeable User
+ System.Directory.Layout.Internal: instance Typeable1 F
+ System.Directory.Layout.Internal: instance Typeable1 Layout
+ System.Directory.Layout.Internal: into :: String -> Traversal' (Layout ()) (Layout ())
+ System.Directory.Layout.Internal: mode :: Traversal' (Layout a) (Maybe FileMode)
+ System.Directory.Layout.Internal: newtype Layout a
+ System.Directory.Layout.Internal: source :: Traversal' (Layout a) String
+ System.Directory.Layout.Internal: symlink :: String -> FilePath -> Layout ()
+ System.Directory.Layout.Internal: text :: Text -> Contents
+ System.Directory.Layout.Internal: uid :: UserID -> User
+ System.Directory.Layout.Internal: unL :: Layout a -> Free F a
+ System.Directory.Layout.Internal: user :: Traversal' (Layout a) (Maybe User)
+ System.Directory.Layout.Internal: username :: String -> User
+ System.Directory.Layout.Interpreter: Error :: e -> Validation e a
+ System.Directory.Layout.Interpreter: FitBadBinary :: ByteString -> ByteString -> FitContentsError
+ System.Directory.Layout.Interpreter: FitBadCopyOf :: FilePath -> FitContentsError
+ System.Directory.Layout.Interpreter: FitBadFileContents :: FilePath -> FitContentsError -> FitError
+ System.Directory.Layout.Interpreter: FitBadFileMode :: FilePath -> FileMode -> FileMode -> FitError
+ System.Directory.Layout.Interpreter: FitBadLinkSource :: FilePath -> String -> String -> FitError
+ System.Directory.Layout.Interpreter: FitBadOwnerGroup :: FilePath -> Group -> Group -> FitError
+ System.Directory.Layout.Interpreter: FitBadOwnerUser :: FilePath -> User -> User -> FitError
+ System.Directory.Layout.Interpreter: FitBadText :: Text -> Text -> FitContentsError
+ System.Directory.Layout.Interpreter: FitIOException :: FilePath -> IOErrorType -> FitError
+ System.Directory.Layout.Interpreter: MakeIOException :: FilePath -> IOErrorType -> MakeError
+ System.Directory.Layout.Interpreter: Result :: a -> Validation e a
+ System.Directory.Layout.Interpreter: data FitContentsError
+ System.Directory.Layout.Interpreter: data FitError
+ System.Directory.Layout.Interpreter: data MakeError
+ System.Directory.Layout.Interpreter: data Validation e a
+ System.Directory.Layout.Interpreter: fit :: FilePath -> Layout a -> IO (Validation (NonEmpty FitError) ())
+ System.Directory.Layout.Interpreter: instance (Data e, Data a) => Data (Validation e a)
+ System.Directory.Layout.Interpreter: instance (Eq e, Eq a) => Eq (Validation e a)
+ System.Directory.Layout.Interpreter: instance (Ord e, Ord a) => Ord (Validation e a)
+ System.Directory.Layout.Interpreter: instance (Show e, Show a) => Show (Validation e a)
+ System.Directory.Layout.Interpreter: instance Bifoldable Validation
+ System.Directory.Layout.Interpreter: instance Bifunctor Validation
+ System.Directory.Layout.Interpreter: instance Bitraversable Validation
+ System.Directory.Layout.Interpreter: instance Constructor C1_0FitContentsError
+ System.Directory.Layout.Interpreter: instance Constructor C1_0FitError
+ System.Directory.Layout.Interpreter: instance Constructor C1_0MakeError
+ System.Directory.Layout.Interpreter: instance Constructor C1_0Validation
+ System.Directory.Layout.Interpreter: instance Constructor C1_1FitContentsError
+ System.Directory.Layout.Interpreter: instance Constructor C1_1FitError
+ System.Directory.Layout.Interpreter: instance Constructor C1_1Validation
+ System.Directory.Layout.Interpreter: instance Constructor C1_2FitContentsError
+ System.Directory.Layout.Interpreter: instance Constructor C1_2FitError
+ System.Directory.Layout.Interpreter: instance Constructor C1_3FitError
+ System.Directory.Layout.Interpreter: instance Constructor C1_4FitError
+ System.Directory.Layout.Interpreter: instance Constructor C1_5FitError
+ System.Directory.Layout.Interpreter: instance Datatype D1FitContentsError
+ System.Directory.Layout.Interpreter: instance Datatype D1FitError
+ System.Directory.Layout.Interpreter: instance Datatype D1MakeError
+ System.Directory.Layout.Interpreter: instance Datatype D1Validation
+ System.Directory.Layout.Interpreter: instance Eq FitContentsError
+ System.Directory.Layout.Interpreter: instance Eq FitError
+ System.Directory.Layout.Interpreter: instance Eq MakeError
+ System.Directory.Layout.Interpreter: instance Exception FitError
+ System.Directory.Layout.Interpreter: instance Exception MakeError
+ System.Directory.Layout.Interpreter: instance Foldable (Validation e)
+ System.Directory.Layout.Interpreter: instance Functor (Validation e)
+ System.Directory.Layout.Interpreter: instance Generic (Validation e a)
+ System.Directory.Layout.Interpreter: instance Generic FitContentsError
+ System.Directory.Layout.Interpreter: instance Generic FitError
+ System.Directory.Layout.Interpreter: instance Generic MakeError
+ System.Directory.Layout.Interpreter: instance Semigroup e => Applicative (Validation e)
+ System.Directory.Layout.Interpreter: instance Show FitError
+ System.Directory.Layout.Interpreter: instance Show MakeError
+ System.Directory.Layout.Interpreter: instance Traversable (Validation e)
+ System.Directory.Layout.Interpreter: instance Typeable FitContentsError
+ System.Directory.Layout.Interpreter: instance Typeable FitError
+ System.Directory.Layout.Interpreter: instance Typeable MakeError
+ System.Directory.Layout.Interpreter: instance Typeable2 Validation
+ System.Directory.Layout.Interpreter: make :: FilePath -> Layout a -> IO (Validation (NonEmpty MakeError) ())
+ System.Directory.Layout.Interpreter: pretty :: Layout a -> String
+ System.Directory.Layout.Interpreter: spec :: FilePath -> Layout a -> Spec
+ System.Directory.Layout.QQ: dedent :: QuasiQuoter
- System.Directory.Layout: file :: FilePath -> Text -> Layout
+ System.Directory.Layout: file :: String -> Layout ()
- System.Directory.Layout.Internal: D :: !FilePath -> !Layout -> !(Node a) -> Node a
+ System.Directory.Layout.Internal: D :: String -> a -> Aux -> a -> F a
- System.Directory.Layout.Internal: E :: !a -> Node a
+ System.Directory.Layout.Internal: E :: F a
- System.Directory.Layout.Internal: F :: !FilePath -> !Layout -> !(Node a) -> Node a
+ System.Directory.Layout.Internal: F :: String -> (Maybe Contents) -> Aux -> a -> F a

Files

+ CHANGELOG.md view
@@ -0,0 +1,46 @@+0.6.0.0+=======++  * Complete rewrite++  * Added `pretty` and `spec` interpreters++  * Layout nodes can be configured with user/group and file permissions++  * Added symlinks with optionally checked source existence++  * File contents can be specified as raw bytes or text data++  * File can be declared the copy of another file++0.4.0.0+=======++  * Self-balancing `Semigroup`,`Apply`,`Applicative`,`Bind`, and `Monad` instance for `Node`.+    That way we have reasonable auto-derived `Eq` and `Ord` instances++  * Added `name`, `names`, `next`, `node` traversals and fixed `file` and `directory`++  * Added `Layout` construction from existing directories via `fromDirectory`++0.3.1.0+=======++  * Fixed build for GHC 7.4++0.3.0.0+=======++  * Removed parser stuff++  * Added `Default`, `Semigroup`, `Monoid`, and `Applicative` instances for `Layout`++  * Added "extractors" (`System.Directory.Layout.Lens`)++  * Added `Apply` and `Bind` instances for `Layout`++  * Added `Ord` instance for `Layout`++  * Added `Foldable` and `Traversable` instances for `Layout`++  * Rewrote Check and Make (unified errors, simpler implementation)
LICENSE view
@@ -1,19 +1,30 @@-Copyright (C) 2012 Matvey Aksenov+Copyright (c) 2014, Matvey Aksenov -Permission is hereby granted, free of charge, to any person obtaining a copy of-this software and associated documentation files (the "Software"), to deal in-the Software without restriction, including without limitation the rights to-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies-of the Software, and to permit persons to whom the Software is furnished to do-so, subject to the following conditions:+All rights reserved. -The above copyright notice and this permission notice shall be included in all-copies or substantial portions of the Software.+Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE-SOFTWARE.+    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Matvey Aksenov nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,5 @@+# directory-layout+[![Hackage](https://budueba.com/hackage/directory-layout)](https://hackage.haskell.org/package/directory-layout)+[![Build Status](https://secure.travis-ci.org/supki/directory-layout.png?branch=master)](https://travis-ci.org/supki/directory-layout)++Directory layout DSL
Setup.hs view
@@ -1,4 +1,2 @@ import Distribution.Simple--main :: IO () main = defaultMain
directory-layout.cabal view
@@ -1,80 +1,98 @@-name:          directory-layout-version:       0.4.0.1-synopsis:      Declare, construct and verify directory layout-description:   Language to express directory layouts-category:      System-license:       MIT-license-file:  LICENSE-author:        Matvey Aksenov-maintainer:    matvey.aksenov@gmail.com-build-type:    Simple-cabal-version: >= 1.10+name:                directory-layout+version:             0.6.0.0+synopsis:            Directory layout DSL+description:+  Making, fitting, printing directory layouts+license:             BSD3+license-file:        LICENSE+author:              Matvey Aksenov+maintainer:          matvey.aksenov@gmail.com+category:            System+build-type:          Simple+cabal-version:       >=1.10+extra-source-files:+  README.md+  CHANGELOG.md +source-repository head+  type:     git+  location: https://github.com/supki/directory-layout++source-repository this+  type:     git+  location: https://github.com/supki/directory-layout+  tag:      0.6.0.0+ library-  default-language: Haskell2010+  default-language:+    Haskell2010+  build-depends:+      base                 >= 4.6   && < 5+    , bifunctors           >= 4+    , bytestring+    , directory+    , filepath+    , free                 >= 4.7+    , hspec                >= 1.8+    , lens                 >= 4.0.1+    , semigroups           >= 0.12+    , template-haskell+    , text                 >= 0.11+    , transformers         >= 0.3+    , unix+    , unordered-containers >= 0.2.3   exposed-modules:     System.Directory.Layout     System.Directory.Layout.Internal-    System.Directory.Layout.Errored-    System.Directory.Layout.Traverse-    System.Directory.Layout.Lens-  hs-source-dirs: src-  build-depends:-    base >= 4 && < 5,-    data-default,-    directory,-    filepath,-    lens,-    mtl,-    semigroupoids,-    semigroups,-    text+    System.Directory.Layout.Interpreter+    System.Directory.Layout.QQ+  hs-source-dirs:+    src   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+test-suite doctest+  default-language:+    Haskell2010+  type:+    exitcode-stdio-1.0   build-depends:-    base >= 3 && < 5,-    directory-layout,-    doctest,-    lens,-    wordexp-  main-is: tests/doctests.hs-  ghc-options:-    -Wall-    -fno-warn-unused-do-bind+      base          == 4.*+    , directory+    , doctest+    , filepath+  hs-source-dirs:+    test+  main-is:+    Doctest.hs -test-suite layout-laws-  default-language: Haskell2010-  type: exitcode-stdio-1.0+test-suite spec+  default-language:+    Haskell2010+  type:+    exitcode-stdio-1.0   build-depends:-    base >= 3 && < 5,-    directory-layout,-    hspec,-    semigroups-  main-is: tests/Hspec.hs-  ghc-options:-    -Wall-    -fno-warn-unused-do-bind---source-repository head-  type: git-  location: https://github.com/supki/directory-layout+      base >= 4.6+    , bifunctors+    , bytestring+    , directory+    , filepath+    , free+    , hspec+    , lens >= 4+    , semigroups+    , template-haskell+    , text+    , transformers+    , unix+    , unordered-containers+    , Unixutils+  hs-source-dirs:+    src+    test+  main-is:+    Spec.hs+  other-modules:+    System.Directory.LayoutSpec+    System.Directory.Layout.InternalSpec+    System.Directory.Layout.InterpreterSpec
src/System/Directory/Layout.hs view
@@ -1,74 +1,43 @@--- | Language to express directory layouts+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE ViewPatterns #-}+-- | Directory layout DSL module System.Directory.Layout-  ( -- * Layout declaration-    Node, Layout, file, file_, directory, directory_-    -- * Layout construction-  , fromDirectory-    -- * Layout traverses-  , make, check-    -- * Errors-  , LayoutException(..)+  ( -- * Describe layouts+    Layout+    -- ** Nodes+  , file+  , symlink+  , dir+  , dirs+  , emptydir+    -- ** Nodes augmentation+  , contents+  , Contents(..)+  , binary+  , text+  , dedent+  , copyOf+  , source+  , exists+  , User(..)+  , user+  , uid+  , username+  , Group(..)+  , group+  , gid+  , groupname+  , mode+  , anything+  , into+  , focus+    -- * Run layouts+  , module System.Directory.Layout.Interpreter   ) where -import           Control.Lens-import           Control.Monad ((>=>))-import qualified Control.Exception as E-import           Data.Default (def)-import           Data.Monoid (mconcat)-import           Data.Text (Text)-import qualified System.Directory as D-import           System.FilePath (combine)-import           System.FilePath.Lens (filename)--import System.Directory.Layout.Internal (Node(..), 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 t = F x (T t ()) def-{-# INLINE file #-}----- | Declare empty file-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 def-{-# INLINE directory #-}----- | Declare empty directory-directory_ :: FilePath -> Layout-directory_ x = D x def def-{-# INLINE directory_ #-}----- | Create layout from directory------ Canonicalizes path before traversing, generally understands only--- regular files and directories and ignores anything else it could not understand-fromDirectory :: FilePath -> IO (Either E.IOException Layout)-fromDirectory = E.try . (D.canonicalizePath >=> traverseDirectory)- where-  traverseDirectory :: FilePath -> IO Layout-  traverseDirectory path = getDirectoryContents path >>=-    traverse (traverseFilePath . combine path) <&> directory (path^.filename) . mconcat--  traverseFilePath :: FilePath -> IO Layout-  traverseFilePath path = do-    isFile      <- D.doesFileExist path-    isDirectory <- D.doesDirectoryExist path-    case (isFile, isDirectory) of-      (True, _) -> return (file_ (path^.filename))-      (_, True) -> traverseDirectory path-      -- Should be pretty rare in practice: broken symlinks and stuff-      (_, _)    -> return def--  getDirectoryContents :: FilePath -> IO [FilePath]-  getDirectoryContents = fmap (filter (not . (`elem` [".", ".."]))) . D.getDirectoryContents+import System.Directory.Layout.Internal+import System.Directory.Layout.Interpreter+import System.Directory.Layout.QQ
− src/System/Directory/Layout/Errored.hs
@@ -1,117 +0,0 @@-{-# LANGUAGE CPP #-}-{-# 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)-#if __GLASGOW_HASKELL__ >= 706-import           System.IO.Error-#else-import           System.IO.Error hiding (catch, try)-#endif--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 'D.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 'T.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)----- | 'D.doesFileExist' 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))---- | 'D.doesDirectoryExist' 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 'T.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,145 +1,277 @@--- | Free monad based directory layouts-module System.Directory.Layout.Internal-  ( Node(..), Layout-  ) where+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE TypeFamilies #-}+-- | directory-layout internals+module System.Directory.Layout.Internal where -import Control.Applicative (Applicative(..), (<$>))-import Data.Foldable (Foldable(..))-import Data.Traversable (Traversable(..), fmapDefault, foldMapDefault)-import Data.Monoid (Monoid(..))-import Unsafe.Coerce (unsafeCoerce)+import           Control.Applicative+import           Control.Lens+import           Control.Monad.Free+import           Data.ByteString (ByteString)+import qualified Data.ByteString as ByteString+import           Data.Data (Data, Typeable)+import           Data.Foldable (Foldable)+import qualified Data.HashMap.Strict as HashMap+import           Data.Semigroup (Semigroup(..))+import           Data.String (IsString(..))+import           Data.Word (Word8)+import           Data.Text (Text)+#if __GLASGOW_HASKELL__ >= 708+import           GHC.Exts (IsList(..))+#endif+import           GHC.Generics (Generic)+import           System.FilePath ((</>))+import qualified System.Posix as Posix -import Data.Default (Default(..))-import Data.Functor.Apply (Apply(..))-import Data.Functor.Bind (Bind(..))-import Data.Semigroup (Semigroup(..))-import Data.Text (Text)+-- | Directory layout description+newtype Layout a = L { unL :: Free F a }+  deriving (Functor, Applicative, Monad, Foldable, Traversable, Typeable, Generic) +-- | The underlying 'Functor'+data F a =+    F String (Maybe Contents) Aux a+  | SL String FilePath Bool Aux a+  | D String a Aux a+  | E+    deriving (Eq, Functor, Foldable, Traversable, Typeable, Generic) --- | Type synonym to save some acrobatics-type Layout = Node ()+-- | Regular file contents+data Contents =+    Binary ByteString+  | Text Text+  | CopyOf FilePath+    deriving (Eq, Typeable, Data, Generic) +instance IsString Contents where+  fromString = Text . fromString --- | A representation of directory layouts+#if __GLASGOW_HASKELL__ >= 708+instance IsList Contents where+  type Item Contents = Word8+  fromList = Binary . ByteString.pack+  toList = error "Contents.toList: not implemented"+#endif++-- | Auxiliary data+data Aux = Aux (Maybe User) (Maybe Group) (Maybe Posix.FileMode)+    deriving (Show, Eq, Typeable, Generic)++-- | File owner+data User =+    UserID Posix.UserID+  | Username String+    deriving (Show, Eq, Typeable, Generic)++instance IsString User where+  fromString = Username++-- | File group+data Group =+    GroupID Posix.GroupID+  | Groupname String+    deriving (Show, Eq, Typeable, Generic)++instance IsString Group where+  fromString = Groupname++-- | Equality check does not care about the order the files are listed insofar+-- they are consistent, i.e. different things aren't named the same+instance Eq (Layout a) where+  L xs == L ys = go "." xs == go "." ys+   where+    go root (Free t@(F n _ _ m)) =+      HashMap.singleton (root </> n) (() <$ t) <> go root m+    go root (Free t@(SL n _ _ _ m)) =+      HashMap.singleton (root </> n) (() <$ t) <> go root m+    go root (Free t@(D n is _ m)) =+      HashMap.singleton (root </> n) (() <$ t) <> go (root </> n) is <> go root m+    go _ (Free E) = HashMap.empty+    go _ (Pure _) = HashMap.empty++instance Semigroup (Layout a) where+  (<>) = (>>)++-- | Regular file with some contents or empty ----- Invariants:+-- >>> let layout = file "foo"+file :: String -> Layout ()+file name = L (liftF (F name anything defaux ()))++-- | Symbolic link -----  * 'F' second argument is never @D _ _ _@ or @F _ _ _@ itself+-- >>> let layout = symlink "foo" "bar"+symlink+  :: String   -- link's name+  -> FilePath -- link's source+  -> Layout ()+symlink name s = L (liftF (SL name s False defaux ()))++-- | Directory -----  * 'F' third argument is never @T _ _@+-- >>> :{+-- let layout = dir "foo" $ do+--       file "bar"+--       file "baz"+-- :}+dir :: String -> Layout a -> Layout ()+dir name is = L (Free (D name (unL is >> liftF E) defaux (Pure ())))++-- | Empty directory -----  * 'D' second argument is never @T _ _@+-- >>> let layout = emptydir "foo"+emptydir :: String -> Layout ()+emptydir name = dir name (return ())++-- | A nested list of directories -----  * 'D' third argument is never @T _ _@-data Node a =-    E !a                          -- ^ Emptyness, nothing found here-  | T !Text !a                    -- ^ File contents-  | F !FilePath !Layout !(Node a) -- ^ File node-  | D !FilePath !Layout !(Node a) -- ^ Directory node-    deriving (Show, Read, Eq, Ord)+-- >>> :{+-- let layout = dirs ["foo", "bar"] $ do+--                file "qux"+--                file "quux"+-- :}+dirs :: [String] -> Layout () -> Layout ()+dirs names l = foldr dir l names -compareFilePath :: Node a -> Node b -> Ordering-compareFilePath (E _)      (E _)       = EQ-compareFilePath (E _)      _           = LT-compareFilePath _          (E _)       = GT-compareFilePath (T _ _)    (T _ _)     = EQ-compareFilePath (T _ _)    _           = LT-compareFilePath _          (T _ _)     = GT-compareFilePath (F fp _ _) (F fp' _ _) = compare fp fp'-compareFilePath (F _ _ _)  _           = LT-compareFilePath _          (F _ _ _)   = GT-compareFilePath (D fp _ _) (D fp' _ _) = compare fp fp'-{-# INLINE compareFilePath #-}+-- | The default (empty) auxiliary data+defaux :: Aux+defaux = Aux Nothing Nothing Nothing -instance Default a => Default (Node a) where-  def = E def-  {-# INLINE def #-}+-- | An optic into file contents+contents :: Traversal' (Layout a) (Maybe Contents)+contents f (L (Free (F n cs a x@(Pure _)))) = f cs <&> \cs' -> L (Free (F n cs' a x))+contents _ l = pure l+{-# INLINE contents #-} -instance Semigroup (Node a) where-  (<>) = (>>)-  {-# INLINE (<>) #-}+-- | Binary contents+--+-- >>> let layout = file "foo" & contents ?~ binary (ByteString.pack [1..10])+binary :: ByteString -> Contents+binary = Binary -instance Default a => Monoid (Node a) where-  mempty = def-  {-# INLINE mempty #-}+-- | Plain text contents+--+-- >>> let layout = file "foo" & contents ?~ text (Data.Text.pack "hello")+text :: Text -> Contents+text = Text -  mappend = (<>)-  {-# INLINE mappend #-}+-- | Contents are the copy of whose of the real file+--+-- >>> let layout = file "foo" & contents ?~ copyOf "/home/user/.vimrc"+copyOf :: FilePath -> Contents+copyOf = CopyOf -instance Functor Node where-  fmap = fmapDefault-  {-# INLINE fmap #-}+-- | Anything+--+-- >>> let layout = file "foo" & contents .~ anything+-- >>> let layout = file "foo" & user .~ anything+anything :: Maybe a+anything = Nothing -instance Apply Node where-  f <.> x =-    f >>- \f' ->-    x >>- \x' ->-    pure (f' x')-  {-# INLINE (<.>) #-}+-- | An optic into symbolic link source+--+-- >>> symlink "foo" "bar" ^? source+-- Just "bar"+source :: Traversal' (Layout a) String+source f (L (Free (SL n s e a x@(Pure _)))) = f s <&> \s' -> L (Free (SL n s' e a x))+source _ l = pure l+{-# INLINE source #-} -instance Applicative Node where-  pure = E-  {-# INLINE pure #-}+-- | An optic into symbolic link source expected existence+--+-- >>> let layout = symlink "foo" "bar" & exists .~ True+exists :: Traversal' (Layout a) Bool+exists f (L (Free (SL n s e a x@(Pure _)))) = f e <&> \e' -> L (Free (SL n s e' a x))+exists _ l = pure l+{-# INLINE exists #-} -  (<*>) = (<.>)-  {-# INLINE (<*>) #-}+-- | An optic into file auxiliary data+aux :: Traversal' (Layout a) Aux+aux f (L (Free (F n cs a x@(Pure _)))) = f a <&> \a' -> L (Free (F n cs a' x))+aux f (L (Free (SL n s e a x@(Pure _)))) = f a <&> \a' -> L (Free (SL n s e a' x))+aux f (L (Free (D n is a x@(Pure _)))) = f a <&> \a' -> L (Free (D n is a' x))+aux _ l = pure l+{-# INLINE aux #-} -instance Bind Node where-  E x         >>- f = f x-  T _ x       >>- f = f x-  n@(F _ _ x) >>- f = n >>* (x >>- f)-  n@(D _ _ x) >>- f = n >>* (x >>- f)-  {-# INLINE (>>-) #-}+-- | An optic into file owner+--+-- >>> let layout = file "foo" & user ?~ uid 0+user :: Traversal' (Layout a) (Maybe User)+user = aux . \f (Aux x y z) -> f x <&> \x' -> Aux x' y z -(>>*) :: Node a -> Node b -> Node b-a >>* b =-  case compareFilePath a b of-    GT -> case b of-      E _      -> unsafeCoerce a-      T _ _    -> unsafeCoerce a-      F f t l  -> F f t (a >>* l)-      D f l l' -> D f l (a >>* l')-    _  -> case a of-      E _     -> b-      T _ _   -> b-      F f t _ -> F f t b-      D f l _ -> D f l b-{-# INLINE (>>*) #-}+-- | Set the file owner by uid+uid :: Posix.UserID -> User+uid = UserID --- | All this crazy stuff is only to get do-notation basically.+-- | Set the file owner by username ----- Bind (@<-@) in that do-notation is useless at best--- (You only can get @()@s from 'Layout') and harmful at worst--- (If you manage to create your own 'Node' values with something more--- interesting than @()@)-instance Monad Node where-  return = pure-  {-# INLINE return #-}+-- >>> let layout = file "foo" & user ?~ username "root"+username :: String -> User+username = Username -  a >> b =-    case compareFilePath a b of-      GT -> case b of-        E _      -> unsafeCoerce a-        T _ _    -> unsafeCoerce a-        F f t l  -> F f t (a >> l)-        D f l l' -> D f l (a >> l')-      _  -> case a of-        E _      -> b-        T _ _    -> b-        F f t l  -> F f t (l  >> b)-        D f l l' -> D f l (l' >> b)-  {-# INLINE (>>) #-}+-- | An optic into file group+--+-- >>> let layout = file "foo" & group ?~ gid 0+group :: Traversal' (Layout a) (Maybe Group)+group = aux . \f (Aux x y z) -> f y <&> \y' -> Aux x y' z -  (>>=) = (>>-)-  {-# INLINE (>>=) #-}+-- | Set the file group by groupname+gid :: Posix.GroupID -> Group+gid = GroupID -instance Foldable Node where-  foldMap = foldMapDefault-  {-# INLINE foldMap #-}+-- | Set the file group by groupname+--+-- >>> let layout = file "foo" & group ?~ groupname "wheel"+groupname :: String -> Group+groupname = Groupname -instance Traversable Node 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 #-}+-- | An optic into file mode+--+-- >>> let layout = file "foo" & mode ?~ 0o100777+mode :: Traversal' (Layout a) (Maybe Posix.FileMode)+mode = aux . \f (Aux x y z) -> f z <&> \z' -> Aux x y z'++-- | An optic into directory contents+innards :: Traversal' (Layout a) (Layout a)+innards f (L (Free (D n is a x@(Pure _)))) = fmap unL (f (L is)) <&> \is' -> L (Free (D n is' a x))+innards _ l = pure l+{-# INLINE innards #-}++-- | An optic into the directory contents of the particular directory+--+-- >>> :{+-- dirs ["foo", "bar", "baz"] (symlink "qux" "quux")+--   ^? into "foo".into "bar".into "baz".focus "qux".source+-- :}+-- Just "quux"+into :: String -> Traversal' (Layout ()) (Layout ())+into s = focus s.innards++-- | An optic into the particular node+focus :: String -> Traversal' (Layout ()) (Layout ())+focus k f = fmap L . go . unL where+  go (Free (F n cs a x))+    | n == k =+      g (liftF (F n cs a ())) <&> \(Free (F _ cs' a' _)) -> Free (F n cs' a' x)+    | otherwise =+      go x <&> Free . F n cs a+  go (Free (D n is a x))+    | n == k =+      g (Free (D n is a (Pure ()))) <&> \(Free (D _ is' a' _)) -> Free (D n is' a' x)+    | otherwise =+      go x <&> Free . D n is a+  go (Free (SL n s e a x))+    | n == k =+      g (liftF (SL n s e a ())) <&> \(Free (SL _ s' e' a' _)) -> Free (SL n s' e' a' x)+    | otherwise =+      go x <&> Free . SL n s e a+  go (Free E) = pure (Free E)+  go (Pure x) = pure (Pure x)++  g = fmap unL . f . L+{-# INLINE focus #-}
+ src/System/Directory/Layout/Interpreter.hs view
@@ -0,0 +1,341 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ViewPatterns #-}+-- | A bunch of 'Layout' description interpreters+module System.Directory.Layout.Interpreter+  ( pretty+  , spec+  , Validation(..)+  , fit+  , FitError(..)+  , FitContentsError(..)+  , make+  , MakeError(..)+  ) where++import           Control.Applicative+import           Control.Exception (Exception(..), SomeException(..), throwIO, try)+import           Control.Monad+import           Control.Monad.Free+import           Data.Bifoldable (Bifoldable(..))+import           Data.Bifunctor (Bifunctor(..))+import           Data.Bitraversable (Bitraversable(..))+import           Data.ByteString (ByteString)+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Lazy as ByteStringLazy+import           Data.Data (Data, Typeable)+import           Data.Foldable (Foldable, sequenceA_, for_)+import           Data.Functor.Compose (Compose(..))+import           Data.List.NonEmpty (NonEmpty)+import           Data.Maybe (fromMaybe)+import           Data.Semigroup (Semigroup(..))+import           Data.Text (Text)+import qualified Data.Text.IO as Text+import           Data.Traversable (Traversable)+import           Data.Typeable (cast)+import           GHC.Generics (Generic)+import           Numeric (showOct)+import           System.Directory (createDirectoryIfMissing)+import           System.FilePath (combine)+import           System.IO.Error (IOErrorType, ioeGetErrorType, ioeGetFileName, ioeGetLocation)+import qualified System.Posix as Posix+import           Test.Hspec (Spec, context, it)+import           Text.Printf (printf)++import           System.Directory.Layout.Internal++-- | Pretty print the directory layout+pretty :: Layout a -> String+pretty = unlines . iter go . unL . fmap (const []) where+  go f@(F _ _ _ other) = prettyF f : other+  go f@(SL _ _ _ _ other) = prettyF f : other+  go f@(D _ is _ other) = prettyF f : map indent is ++ other+  go E = []++  indent :: String -> String+  indent s = "┆ " ++ s++prettyF :: F a -> String+prettyF (F name cs _ _) = printf "‘%s’, %s" name (prettyC cs)+prettyF (SL name s _ _ _) = printf "‘%s’, a link to ‘%s’" name s+prettyF (D name _ _ _) = '/' : name+prettyF E = ""++prettyC :: Maybe Contents -> String+prettyC (Just (Binary _)) = "raw bytes"+prettyC (Just (Text _)) = "text"+prettyC (Just (CopyOf p)) = printf "(copy of ‘%s’)" p+prettyC Nothing = "anything"++-- | Interpret the directory layout as a 'Spec'+spec :: FilePath -> Layout a -> Spec+spec p = go p . unL where+  go root (Free f@(F _ _ _ m)) = do+    specF root f+    go root m+  go root (Free f@(SL _ _ _ _ m)) = do+    specF root f+    go root m+  go root (Free f@(D (combine root -> fullpath) is _ m)) = do+    specF root f+    context (printf "directory ‘%s’" fullpath) (go fullpath is)+    go root m+  go _ (Free E) = return ()+  go _ (Pure _) = return ()++specF :: FilePath -> F a -> Spec+specF root = go where+  go f@(F name cs _ _) = it (printf "has a %s file ‘%s’" (examplesC cs) name) (fitIO root f)+  go f@(SL name s _ _ _) = it (printf "has a symlink ‘%s’ pointing to ‘%s’" name s) (fitIO root f)+  go f@(D (combine root -> fullpath) _ _ _) = it (printf "has a subdirectory ‘%s’" fullpath) (fitIO root f)+  go E = return ()++examplesC :: Maybe Contents -> String+examplesC (Just (Binary _)) = "binary"+examplesC (Just (Text _)) = "plain text"+examplesC (Just (CopyOf p)) = printf "(copy of ‘%s’)" p+examplesC Nothing = "regular"++validate+  :: Exception e+  => (forall a. FilePath -> F a -> IO ()) -> FilePath -> Layout b -> IO (Validation (NonEmpty e) ())+validate g p = getCompose . go p . unL where+  go root (Free f@(F _ _ _ m)) =+    sequenceA_ [Compose (validateF root f), go root m]+  go root (Free f@(SL _ _ _ _ m)) =+    sequenceA_ [Compose (validateF root f), go root m]+  go root (Free f@(D (combine root -> fullpath) is _ m)) =+    sequenceA_ [Compose (validateF root f), go fullpath is, go root m]+  go _ (Free E) = pure ()+  go _ (Pure _) = pure ()++  validateF root l = first pure . fromEither <$> try (g root l)++-- | Check the real directory layout fits the description+fit :: FilePath -> Layout a -> IO (Validation (NonEmpty FitError) ())+fit = validate fitIO++fitIO :: FilePath -> F a -> IO ()+fitIO root = go where+  go (F (combine root -> fullpath) cs a _) = do+    for_ cs $ \cs' -> case cs' of+      Binary bs -> do+        real <- ByteString.readFile fullpath+        when (real /= bs) $+          throwIO (FitBadFileContents fullpath (FitBadBinary bs real))+      Text t -> do+        real <- Text.readFile fullpath+        when (real /= t) $+          throwIO (FitBadFileContents fullpath (FitBadText t real))+      CopyOf f -> do+        origin <- ByteStringLazy.readFile f+        copy <- ByteStringLazy.readFile fullpath+        when (origin /= copy) $+          throwIO (FitBadFileContents fullpath (FitBadCopyOf f))+    fitIOAux a fullpath+  go (SL (combine root -> fullpath) s e a _) = do+    path <- Posix.readSymbolicLink fullpath+    when (path /= s) $+      throwIO (FitBadLinkSource fullpath s path)+    when e $+      () <$ Posix.getFileStatus fullpath+    fitIOAux a fullpath+  go (D (combine root -> fullpath) _ a _) = () <$ do+    fitIOAux a fullpath+  go E = return ()++fitIOAux :: Aux -> FilePath -> IO ()+fitIOAux (Aux muid mgid mperm) path = do+  status <- Posix.getSymbolicLinkStatus path+  for_ muid $ \case+    UserID i ->+      unless (Posix.fileOwner status == i) $+        throwIO (FitBadOwnerUser path (UserID i) (UserID (Posix.fileOwner status)))+    Username name -> do+      i <- getUserID name+      n <- getUsername (Posix.fileOwner status)+      unless (Posix.fileOwner status == i) $+        throwIO (FitBadOwnerUser path (Username name) (Username n))+  for_ mgid $ \case+    GroupID i ->+      unless (Posix.fileGroup status == i) $+        throwIO (FitBadOwnerGroup path (GroupID i) (GroupID (Posix.fileGroup status)))+    Groupname name -> do+      i <- getGroupID name+      n <- getGroupname (Posix.fileGroup status)+      unless (Posix.fileGroup status == i) $+        throwIO (FitBadOwnerGroup path (Groupname name) (Groupname n))+  for_ mperm $ \perm ->+    unless (Posix.fileMode status == perm) $+      throwIO (FitBadFileMode path perm (Posix.fileMode status))++-- | Errors encountered while running 'fit'+data FitError =+    FitBadFileContents FilePath FitContentsError+  | FitBadLinkSource FilePath String {- expected -} String {- actual -}+  | FitBadOwnerUser FilePath User {- expected -} User {- actual -}+  | FitBadOwnerGroup FilePath Group {- expected -} Group {- actual -}+  | FitBadFileMode FilePath Posix.FileMode {- expected -} Posix.FileMode {- actual -}+  | FitIOException FilePath IOErrorType+    deriving (Eq, Typeable, Generic)++-- | Expected/actual file contents mismatch+data FitContentsError =+    FitBadBinary ByteString ByteString+  | FitBadText Text Text+  | FitBadCopyOf FilePath+    deriving (Eq, Typeable, Generic)++instance Show FitError where+  show (FitBadFileContents path mismatch) = unlines $+    printf "Bad contents at ‘%s’" path : showCE mismatch+   where+    showCE :: FitContentsError -> [String]+    showCE (FitBadBinary expected actual) =+      [ "expected:"+      , printf "  %s" (show (ByteString.unpack expected))+      , "actual:"+      , printf "  %s" (show (ByteString.unpack actual))+      ]+    showCE (FitBadText expected actual) =+      [ "expected:"+      , printf "  %s" (show expected)+      , "actual:"+      , printf "  %s" (show actual)+      ]+    showCE (FitBadCopyOf f) =+      [ "expected:"+      , printf "  a copy of ‘%s’" f+      , "actual:"+      , "  something else"+      ]+  show (FitBadLinkSource path expected actual) = unlines $+    [ printf "Bad symlink source at ‘%s’" path+    , "expected:"+    , printf "  ‘%s’" expected+    , "actual:"+    , printf "  ‘%s’" actual+    ]+  show (FitBadOwnerUser path expected actual) = unlines $+    [ printf "Bad owner user id at ‘%s’" path+    , "expected:"+    , printf "  %s" (show expected)+    , "actual:"+    , printf "  %s" (show actual)+    ]+  show (FitBadOwnerGroup path expected actual) = unlines $+    [ printf "Bad owner group id at ‘%s’" path+    , "expected:"+    , printf "  %s" (show expected)+    , "actual:"+    , printf "  %s" (show actual)+    ]+  show (FitBadFileMode path expected actual) = unlines $+    [ printf "Bad file permissions id at ‘%s’" path+    , "expected:"+    , printf "  %s" (showOct expected "")+    , "actual:"+    , printf "  %s" (showOct actual "")+    ]+  show (FitIOException eloc etype) =+    printf "Generic IO exception of type ‘%s’ happened at ‘%s’\n" (show etype) eloc++instance Exception FitError where+  toException = SomeException+  fromException e'@(SomeException e)+    | Just ioe <- fromException e' =+        Just (FitIOException (fromMaybe (ioeGetLocation ioe) (ioeGetFileName ioe)) (ioeGetErrorType ioe))+    | otherwise = cast e++-- | Make the real directory layout from the description+make :: FilePath -> Layout a -> IO (Validation (NonEmpty MakeError) ())+make = validate makeIO++makeIO :: FilePath -> F a -> IO ()+makeIO root = go where+  go (F (combine root -> fullpath) cs a _) = do+    case cs of+      Just (Binary bs) -> ByteString.writeFile fullpath bs+      Just (Text t) -> Text.writeFile fullpath t+      Just (CopyOf p) -> ByteStringLazy.readFile p >>= ByteStringLazy.writeFile fullpath+      Nothing -> ByteString.writeFile fullpath (ByteString.pack [])+    makeIOAux a fullpath+  go (SL (combine root -> fullpath) s _ a _) = do+    Posix.createSymbolicLink s fullpath+    makeIOAux a fullpath+  go (D (combine root -> fullpath) _ a _) = do+    createDirectoryIfMissing False fullpath+    makeIOAux a fullpath+  go E = return ()++makeIOAux :: Aux -> FilePath -> IO ()+makeIOAux (Aux muid mgid mperm) path = do+  for_ muid $ \case+    UserID i ->+      Posix.setSymbolicLinkOwnerAndGroup path i (-1)+    Username name -> do+      i <- getUserID name+      Posix.setSymbolicLinkOwnerAndGroup path i (-1)+  for_ mgid $ \case+    GroupID i ->+      Posix.setSymbolicLinkOwnerAndGroup path (-1) i+    Groupname name -> do+      i <- getGroupID name+      Posix.setSymbolicLinkOwnerAndGroup path (-1) i+  for_ mperm $+    Posix.setFileMode path++-- | Errors encountered while running 'make'+data MakeError =+  MakeIOException FilePath IOErrorType+  deriving (Show, Eq, Typeable, Generic)++instance Exception MakeError where+  toException = SomeException+  fromException e'@(SomeException e)+    | Just ioe <- fromException e' =+        Just (MakeIOException (fromMaybe (ioeGetLocation ioe) (ioeGetFileName ioe)) (ioeGetErrorType ioe))+    | otherwise = cast e++getUserID :: String -> IO Posix.UserID+getUserID = fmap Posix.userID . Posix.getUserEntryForName++getUsername :: Posix.UserID -> IO String+getUsername = fmap Posix.userName . Posix.getUserEntryForID++getGroupID :: String -> IO Posix.GroupID+getGroupID = fmap Posix.groupID . Posix.getGroupEntryForName++getGroupname :: Posix.GroupID -> IO String+getGroupname = fmap Posix.groupName . Posix.getGroupEntryForID++-- | This type is isomorphic to 'Either' but its 'Applicative' instance accumulates errors+data Validation e a = Error e | Result a+  deriving (Show, Eq, Ord, Functor, Foldable, Traversable, Typeable, Data, Generic)++instance Bifunctor Validation where+  bimap f _ (Error a) = Error (f a)+  bimap _ g (Result a) = Result (g a)++instance Bifoldable Validation where+  bifoldMap f _ (Error a) = f a+  bifoldMap _ g (Result a) = g a++instance Bitraversable Validation where+  bitraverse f _ (Error a) = Error <$> f a+  bitraverse _ g (Result a) = Result <$> g a++instance Semigroup e => Applicative (Validation e) where+  pure = Result+  Error f  <*> Error x  = Error (f <> x)+  Error f  <*> _        = Error f+  _        <*> Error x  = Error x+  Result f <*> Result x = Result (f x)++fromEither :: Either e a -> Validation e a+fromEither = either Error Result
− src/System/Directory/Layout/Lens.hs
@@ -1,151 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE RankNTypes #-}--- | "Control.Lens" based extractors for 'Layout'-module System.Directory.Layout.Lens-  ( -- * Usage-    -- $setup-    text, name, names, next, file, directory, node-  ) where--import Control.Applicative ((<$>), (<*>), pure)--import Control.Lens-import Data.Text (Text)--import System.Directory.Layout.Internal (Node(..), 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 ())))----- | Target 'Text' from the current 'Layout' top (if possible)------ >>> layout ^? text--- Nothing--- >>> layout ^? file "foo" . text--- Just "not empty"--- >>> layout ^? directory "bar" . file "quux" . text--- Just "something"-text :: Prism' Layout Text-text = prism' (\t -> T t ()) $ \s -> case s of-  T t _ -> Just t-  _     -> Nothing-{-# INLINE text #-}---- | Target 'FilePath' from the current 'Layout' top (if possible)------ >>> layout ^? name--- Just "foo"--- >>> layout ^? directory "bar" . name--- Just "baz"--- >>> layout ^? directory "quux" . name--- Nothing--- >>> layout & name .~ "boo"--- F "boo" (T "not empty" ()) (D "bar" (F "baz" (E ()) (F "quux" (T "something" ()) (E ()))) (F "swaks" (E ()) (E ())))-name :: Traversal' Layout FilePath-name f = go- where-  go (E x)   = pure (E x)-  go (T t x) = pure (T t x)-  go (F n l x) = f n <&> \n' -> F n' l x-  go (D n l x) = f n <&> \n' -> D n' l x-{-# INLINE name #-}---- | Target all 'Filpath's from current 'Layout' layer------ >>> layout ^? names--- Just "foo"--- >>> layout ^.. names--- ["foo","bar","swaks"]--- >>> layout ^.. directory "bar" . names--- ["baz","quux"]--- >>> layout & directory "bar" . names %~ reverse--- F "foo" (T "not empty" ()) (D "bar" (F "zab" (E ()) (F "xuuq" (T "something" ()) (E ()))) (F "swaks" (E ()) (E ())))-names :: Traversal' Layout FilePath-names f = go- where-  go (E x)   = pure (E x)-  go (T t x) = pure (T t x)-  go (F n l x) = (\n' x' -> F n' l x') <$> f n <*> go x-  go (D n l x) = (\n' x' -> D n' l x') <$> f n <*> go x-{-# INLINE names #-}---- | Target next 'Node'------ >>> layout ^? name--- Just "foo"--- >>> layout ^? next . name--- Just "bar"--- >>> layout ^? next . next . name--- Just "swaks"--- >>> layout ^? next . next . next . name--- Nothing-next :: Traversal' Layout Layout-next f = go- where-  go (E x)   = pure (E x)-  go (T t x) = pure (T t x)-  go (F n l x) = f x <&> \x' -> F n l x'-  go (D n l x) = f x <&> \x' -> D n l x'-{-# INLINE next #-}---- | Target 'Layout' under the current 'Layout' top if it happens to be a file------ >>> 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 <&> \x' -> F k' l x'-  go (D n l x)  = go x <&> \x' -> D n l x'-{-# INLINE file #-}---- | Target 'Layout' under the current 'Layout' top if it happens to be a directory------ >>> 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 n l x)  = go x <&> \x' -> F n l x'-  go (D k' l x)-    | k == k'   = indexed f k l <&> \l' -> D k' l' x-    | otherwise = go x <&> \x' -> D k' l x'-{-# INLINE directory #-}---- | Target 'Layout' under the current 'Layout' top------ >>> layout ^? node "foo"--- Just (T "not empty" ())--- >>> layout ^? node "bar"--- Just (F "baz" (E ()) (F "quux" (T "something" ()) (E ())))--- >>> layout ^? node "what"--- Nothing-node :: FilePath -> IndexedTraversal' FilePath Layout Layout-node 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 <&> \x' -> F k' l x'-  go (D k' l x)-    | k == k'   = indexed f k l <&> \l' -> D k' l' x-    | otherwise = go x <&> \x' -> D k' l x'-{-# INLINE node #-}
+ src/System/Directory/Layout/QQ.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE TemplateHaskell #-}+-- | Convenience quasiquoter to ease the pain working with multiline strings+module System.Directory.Layout.QQ (dedent) where++import Data.Char (isSpace)+import Data.List (intercalate)+import Language.Haskell.TH.Quote (QuasiQuoter(..))+import Language.Haskell.TH.Syntax (liftString)+import Language.Haskell.TH (Q, Exp)+++-- | A handy quasiquoter to work with the multiline file contents+--+-- Strips the longest common leading spaces segment. All spacey characters are treated+-- equally. The first line is ignored if it's spaces only.+--+-- >>> :set -XQuasiQuotes+-- >>> :{+-- putStr [dedent|+--   hello+--     world+--     !+--   |]+-- :}+-- hello+--   world+--   !+dedent :: QuasiQuoter+dedent = quoter $+  liftString . intercalate "\n" . stripCommonLeadingWhitespace . dropFirst (all isSpace) . lines++dropFirst :: (a -> Bool) -> [a] -> [a]+dropFirst _ [] = []+dropFirst p (x : xs)+  | p x = xs+  | otherwise = x : xs++stripCommonLeadingWhitespace :: [String] -> [String]+stripCommonLeadingWhitespace xs = map (drop (commonLeadingWhitespace xs)) xs++commonLeadingWhitespace :: [String] -> Int+commonLeadingWhitespace = minimumOr 0 . map (length . takeWhile isSpace)++minimumOr :: Ord a => a -> [a] -> a+minimumOr n [] = n+minimumOr _ xs = minimum xs++quoter :: (String -> Q Exp) -> QuasiQuoter+quoter quote = QuasiQuoter+  { quoteExp  = quote+  , quotePat  = failure "patterns"+  , quoteType = failure "types"+  , quoteDec  = failure "declarations"+  }+ where+  failure kind =+    fail $ "this quasiquoter does not support splicing " ++ kind
− src/System/Directory/Layout/Traverse.hs
@@ -1,131 +0,0 @@--- | '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)
+ test/Doctest.hs view
@@ -0,0 +1,28 @@+module Main where++import Control.Applicative+import Control.Monad+import Data.List (isSuffixOf)+import System.Directory (getDirectoryContents, doesDirectoryExist)+import System.FilePath ((</>))+import Test.DocTest (doctest)+++main :: IO ()+main = sources "src" >>= doctest++sources :: FilePath -> IO [FilePath]+sources root = do+  files <- contents root+  liftM concat . forM files $ \file -> do+    let path = root </> file+    is_dir <- doesDirectoryExist path+    case is_dir of+      True              -> sources path+      _ | isSource path -> return [path]+        | otherwise     -> return []+ where+  isSource path = any (`isSuffixOf` path) [".hs", ".lhs"]++contents :: FilePath -> IO [FilePath]+contents dir = filter (`notElem` [".", ".."]) <$> getDirectoryContents dir
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/System/Directory/Layout/InternalSpec.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module System.Directory.Layout.InternalSpec+  ( spec+  ) where++import           Control.Lens+import           Control.Monad.Free+import qualified Data.ByteString as ByteString+import           Data.Char (toUpper)+import           Test.Hspec++import           System.Directory.Layout.Internal++deriving instance Show Contents+deriving instance Show a => Show (F a)+deriving instance Show a => Show (Layout a)+++spec :: Spec+spec = do+  describe "Eq instance" $ do+    it "does not care about the order inside a layer" $+      l1 `shouldBe` l2++    it "does care about the file contents" $+      (l1 & into "bar".focus "baz".contents ?~ "baz'es innards") == l2 `shouldBe` False++    it "does care about the link source" $+      (l1 & focus "xyzzy".source.mapped %~ toUpper) == l2 `shouldBe` False++  describe "contents" $ do+    it "can change contents of the file to the specified text" $+      (file "foo" & contents ?~ "bar") `shouldBe` L (liftF (F "foo" (Just "bar") defaux ()))++    it "can change contents of the file to the specified bytes" $ do+      (file "foo" & contents ?~ binary (ByteString.pack [1..10]))+     `shouldBe`+      L (liftF (F "foo" (Just (binary (ByteString.pack [1..10]))) defaux ()))++    it "can change contents of the file to anything!" $+      (file "foo" & contents .~ anything) `shouldBe` L (liftF (F "foo" anything defaux ()))++  describe "source" $ do+    it "can change source of the symlink to the specified string" $+      (symlink "foo" "bar" & source .~ "baz") `shouldBe` symlink "foo" "baz"++  describe "ix" $ do+    it "can focus a file" $ do+      let l = do+            file "foo"+            file "bar"+            file "baz"++      (l & focus "bar".contents ?~ "qux") `shouldBe` do+         file "foo"+         file "bar"+           & contents ?~ "qux"+         file "baz"++    it "can focus a file inside the directory" $ do+      let l = do+            dir "foo" $+              dir "bar" $+                file "baz"++      (l & into "foo".into "bar".focus "baz".contents ?~ "qux") `shouldBe` do+         dir "foo" $+           dir "bar" $+             file "baz"+               & contents ?~ "qux"++    it "can focus a directory" $ do+      let l = do+            dir "foo" $+              dir "bar" $+                file "baz"++      (l & into "foo".focus "bar".mode ?~ 0o040777) `shouldBe` do+         dir "foo" $ do+           dir "bar" $ do+             file "baz"+          & mode ?~ 0o040777++l1, l2 :: Layout ()+l1 = do+  file "foo"+  dir "bar" $ do+    file "baz"+    file "qux"+      & contents ?~ "qux's innards"+    file "quux"+      & contents ?~ "quux's innards"+  file "xyz"+  symlink "xyzzy" "yzzyx"+l2 = do+  dir "bar" $ do+    file "baz"+    file "quux"+      & contents ?~ "quux's innards"+    file "qux"+      & contents ?~ "qux's innards"+  file "foo"+  symlink "xyzzy" "yzzyx"+  file "xyz"
+ test/System/Directory/Layout/InterpreterSpec.hs view
@@ -0,0 +1,416 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+module System.Directory.Layout.InterpreterSpec+  ( spec+  ) where++import           Control.Applicative+import           Control.Lens+import qualified Data.ByteString as ByteString+import           Data.Foldable (traverse_)+import           Data.List.NonEmpty (NonEmpty)+import           System.Directory (createDirectoryIfMissing)+import           System.FilePath ((</>))+import           System.IO.Error (doesNotExistErrorType, permissionErrorType)+import qualified System.Posix as Posix+import           Test.Hspec++import           SpecHelper+import           System.Directory.Layout hiding (spec)+++spec :: Spec+spec = do+  describe "Validation" $+    it "combines failures with the Semigroup instance's (<>)" $+      traverse_ tonel ([1, 2, 3, 4] :: [Int]) `shouldBe` errors [1,2,3,4]++  describe "fit" $ do+    it "tests regular file existence" $ do+      temporary $ \p -> do+        r <- fit p $ do+          file "foo"+        r `shouldBe` errors [FitIOException (p </> "foo") doesNotExistErrorType]++    it "does not test regular file contents" $ do+      temporary $ \p -> do+        writeFile (p </> "foo") "foo"+        r <- fit p $ do+          file "foo"+        r `shouldBe` errors []++    it "tests text file existence" $ do+      temporary $ \p -> do+        r <- fit p $ do+          file "foo"+            & contents ?~ text "bar"+        r `shouldBe` errors [FitIOException (p </> "foo") doesNotExistErrorType]++    it "tests text file contents" $ do+      temporary $ \p -> do+        writeFile (p </> "foo") "foo"+        r <- fit p $ do+          file "foo"+            & contents ?~ text "bar"+        r `shouldBe` errors+          [ FitBadFileContents (p </> "foo") $+              FitBadText "bar" "foo"+          ]++    it "tests text file contents specified with the quasiquoter" $ do+      temporary $ \p -> do+        writeFile (p </> "foo") "foo"+        r <- fit p $ do+          file "foo"+            & contents ?~ [dedent|+                foo+                bar+                |]+        r `shouldBe` errors+          [ FitBadFileContents (p </> "foo") $+              FitBadText "foo\nbar\n" "foo"+          ]++    it "tests binary file existence" $ do+      temporary $ \p -> do+        r <- fit p $ do+          file "foo"+            & contents ?~ binary (ByteString.pack [1, 2, 3, 4])+        r `shouldBe` errors [FitIOException (p </> "foo") doesNotExistErrorType]++    it "tests binary file contents" $ do+      temporary $ \p -> do+        ByteString.writeFile (p </> "foo") (ByteString.pack [5, 6, 7, 8])+        r <- fit p $ do+          file "foo"+            & contents ?~ binary (ByteString.pack [1, 2, 3, 4])+        r `shouldBe` errors+         [ FitBadFileContents (p </> "foo") $+             FitBadBinary (ByteString.pack [1, 2, 3, 4]) (ByteString.pack [5, 6, 7, 8])+         ]++    it "tests copy file contents" $ do+      temporary $ \p -> do+        ByteString.writeFile (p </> "foo") (ByteString.pack [1, 2, 3, 4])+        ByteString.writeFile (p </> "bar") (ByteString.pack [5, 6, 7, 8])+        r <- fit p $ do+          file "foo"+            & contents ?~ copyOf (p </> "bar")+        r `shouldBe` errors+          [ FitBadFileContents (p </> "foo") $+              FitBadCopyOf (p </> "bar")+          ]++    it "tests copy file contents" $ do+      temporary $ \p -> do+        ByteString.writeFile (p </> "foo") (ByteString.pack [1, 2, 3, 4])+        ByteString.writeFile (p </> "bar") (ByteString.pack [1, 2, 3, 4])+        r <- fit p $ do+          file "foo"+            & contents ?~ copyOf (p </> "bar")+        r `shouldBe` errors []++    it "tests symbolic link existence" $ do+      temporary $ \p -> do+        r <- fit p $ do+          symlink "foo" "bar"+        r `shouldBe` errors [FitIOException (p </> "foo") doesNotExistErrorType]++    it "tests symbolic link source" $ do+      temporary $ \p -> do+        Posix.createSymbolicLink "baz" (p </> "foo")+        r <- fit p $ do+          symlink "foo" "bar"+        r `shouldBe` errors [FitBadLinkSource (p </> "foo") "bar" "baz"]++    it "combines multiple errors on one layer" $ do+      temporary $ \p -> do+        writeFile (p </> "bar") "qux"+        r <- fit p $ do+          file "foo"+          file "bar"+            & contents ?~ text "quux"+          file "baz"+        r `shouldBe` errors+          [ FitIOException (p </> "foo") doesNotExistErrorType+          , FitBadFileContents (p </> "bar") $+              FitBadText "quux" "qux"+          , FitIOException (p </> "baz") doesNotExistErrorType+          ]++    it "combines multiple errors on multiple layers" $ do+      temporary $ \p -> do+        createDirectoryIfMissing True (p </> "xyz" </> "xyzzy")+        writeFile (p </> "xyz" </> "xyzzy" </> "bar") "qux"+        r <- fit p $ do+          dirs ["xyz", "xyzzy"] $ do+            file "foo"+            file "bar"+              & contents ?~ text "quux"+          dir "boo" $+            file "hoo"+        r `shouldBe` errors+          [ FitIOException (p </> "xyz" </> "xyzzy" </> "foo") doesNotExistErrorType+          , FitBadFileContents (p </> "xyz" </> "xyzzy" </> "bar") $+              FitBadText "quux" "qux"+          , FitIOException (p </> "boo") doesNotExistErrorType+          , FitIOException (p </> "boo" </> "hoo") doesNotExistErrorType+          ]++    it "tests file owner user id" $ do+      temporary $ \p -> do+        writeFile (p </> "foo") ""+        r <- fit p $ do+          file "foo"+            & user ?~ uid 0+        r `shouldBe` errors [FitBadOwnerUser (p </> "foo") (uid 0) (uid 1000)]++    it "tests file owner user name" $ do+      temporary $ \p -> do+        writeFile (p </> "foo") ""+        n <- Posix.getEffectiveUserName+        r <- fit p $ do+          file "foo"+            & user ?~ username "root"+        r `shouldBe` errors [FitBadOwnerUser (p </> "foo") (username "root") (username n)]++    it "tests file owner group id" $ do+      temporary $ \p -> do+        writeFile (p </> "foo") ""+        r <- fit p $ do+          file "foo"+            & group ?~ gid 0+        r `shouldBe` errors [FitBadOwnerGroup (p </> "foo") (gid 0) (gid 1000)]++    it "tests file owner group id" $ do+      temporary $ \p -> do+        writeFile (p </> "foo") ""+        n <- Posix.getEffectiveUserName+        r <- fit p $ do+          file "foo"+            & group ?~ groupname "root"+        r `shouldBe` errors [FitBadOwnerGroup (p </> "foo") (groupname "root") (groupname n)]++    it "tests file permissions" $ do+      temporary $ \p -> do+        writeFile (p </> "foo") ""+        Posix.setFileMode (p </> "foo") 0o100644+        r <- fit p $ do+          file "foo"+            & mode ?~ 0o100777+        r `shouldBe` errors [FitBadFileMode (p </> "foo") 0o100777 0o100644]++    it "tests symbolic link's source exists" $ do+      temporary $ \p -> do+        let l = symlink "boo" "hoo"+        Posix.createSymbolicLink "hoo" (p </> "boo")+        fit p l `shouldReturn` errors []+        fit p (l & exists .~ True) `shouldReturn` errors [FitIOException (p </> "boo") doesNotExistErrorType]+        writeFile (p </> "hoo") ""+        fit p (l & exists .~ True) `shouldReturn` errors []++  describe "make" $ do+    -- examples use 'fit' because if the above spec passes then+    -- we can be reasonably sure 'fit' works as expected+    it "creates a file" $+      makefit $+        file "foo"++    it "creates a file with the specified text" $ do+      makefit $+        file "foo"+          & contents ?~ text "bar"++    it "creates a copy of the file with the specified text" $ do+      temporary $ \p -> do+        writeFile (p </> "qux") "quux"+        makefit $+          file "foo"+            & contents ?~ copyOf (p </> "qux")++    it "creates two files with the specified text" $ do+      makefit $ do+        file "foo"+          & contents ?~ text "bar"+        file "qux"+          & contents ?~ text "quux"++    it "creates two files and a symlink" $ do+      makefit $ do+        file "foo"+          & contents ?~ text "bar"+        file "qux"+          & contents ?~ text "quux"+        symlink "boo" "hoo"++    it "creates a directory with a file" $ do+      makefit $+        dir "foo" $+          file "bar"++    it "creates a directory with two files" $ do+      makefit $+        dir "foo" $ do+          file "qux"+            & contents ?~ binary (ByteString.pack [104, 101, 108, 108, 111])+          file "quux"+            & contents ?~ binary (ByteString.pack [98, 121, 101])++    it "creates a nested directory with two files" $ do+      makefit $+        dirs ["foo", "bar"] $ do+          file "qux"+            & contents ?~ binary (ByteString.pack [104, 101, 108, 108, 111])+          file "quux"+            & contents ?~ binary (ByteString.pack [98, 121, 101])++    it "creates a nested directory with two files and a directory" $ do+      makefit $+        dirs ["foo", "bar"] $ do+          file "qux"+            & contents ?~ binary (ByteString.pack [104, 101, 108, 108, 111])+          file "quux"+            & contents ?~ binary (ByteString.pack [98, 121, 101])++    it "creates a tree of directories with files" $ do+      makefit $+        dir "foo" $ do+          dir "bar" $ do+            file "qux"+              & contents ?~ binary (ByteString.pack [104, 101, 108, 108, 111])+            file "quux"+              & contents ?~ binary (ByteString.pack [98, 121, 101])+          dir "baz" $+            symlink "boo" "hoo"++    it "changes the user id of the file owner" $ do+      temporary $ \p -> do+        r <- make p $+          file "qux"+            & contents ?~ binary (ByteString.pack [104, 101, 108, 108, 111])+            & user ?~ uid 0+        r `shouldBe` errors [MakeIOException (p </> "qux") permissionErrorType]++    it "changes the user id of the symbolic link owner" $ do+      temporary $ \p -> do+        r <- make p $+          symlink "foo" "bar"+            & user ?~ uid 0+        r `shouldBe` errors [MakeIOException (p </> "foo") permissionErrorType]++    it "changes the user name of the file owner" $ do+      temporary $ \p -> do+        r <- make p $+          file "qux"+            & contents ?~ binary (ByteString.pack [104, 101, 108, 108, 111])+            & user ?~ username "root"+        r `shouldBe` errors [MakeIOException (p </> "qux") permissionErrorType]++    it "changes the user name of the symbolic link owner" $ do+      temporary $ \p -> do+        r <- make p $+          symlink "foo" "bar"+            & user ?~ username "root"+        r `shouldBe` errors [MakeIOException (p </> "foo") permissionErrorType]++    it "changes the user id of the directory owner" $ do+      temporary $ \p -> do+        r <- make p $+          emptydir "boo"+            & user ?~ uid 0+        r `shouldBe` errors [MakeIOException (p </> "boo") permissionErrorType]++    it "changes the user name of the directory owner" $ do+      temporary $ \p -> do+        r <- make p $+          emptydir "boo"+            & user ?~ username "root"+        r `shouldBe` errors [MakeIOException (p </> "boo") permissionErrorType]++    it "changes the group id of the file owner" $ do+      temporary $ \p -> do+        r <- make p $+          file "qux"+            & contents ?~ binary (ByteString.pack [104, 101, 108, 108, 111])+            & group ?~ gid 0+        r `shouldBe` errors [MakeIOException (p </> "qux") permissionErrorType]++    it "changes the group name of the file owner" $ do+      temporary $ \p -> do+        r <- make p $+          file "qux"+            & contents ?~ binary (ByteString.pack [104, 101, 108, 108, 111])+            & group ?~ groupname "root"+        r `shouldBe` errors [MakeIOException (p </> "qux") permissionErrorType]++    it "changes the group id of the symbolic link owner" $ do+      temporary $ \p -> do+        r <- make p $+          symlink "foo" "bar"+            & group ?~ gid 0+        r `shouldBe` errors [MakeIOException (p </> "foo") permissionErrorType]++    it "changes the group name of the symbolic link owner" $ do+      temporary $ \p -> do+        r <- make p $+          symlink "foo" "bar"+            & group ?~ groupname "root"+        r `shouldBe` errors [MakeIOException (p </> "foo") permissionErrorType]++    it "changes the group id of the directory owner" $ do+      temporary $ \p -> do+        r <- make p $+          file "boo"+            & group ?~ gid 0+        r `shouldBe` errors [MakeIOException (p </> "boo") permissionErrorType]++    it "changes the group name of the directory owner" $ do+      temporary $ \p -> do+        r <- make p $+          file "boo"+            & group ?~ groupname "root"+        r `shouldBe` errors [MakeIOException (p </> "boo") permissionErrorType]++    it "changes the file permissions" $ do+      makefit $+        file "qux"+          & contents ?~ binary (ByteString.pack [104, 101, 108, 108, 111])+          & mode ?~ 0o100777++    it "changes the directory permissions" $ do+      makefit $+        emptydir "boo"+          & mode ?~ 0o040777++    it "tolerates redundant directories" $ do+      makefit $ do+        dir "foo" $+          file "qux"+            & contents ?~ binary (ByteString.pack [104, 101, 108, 108, 111])+        dir "foo" $+          file "quux"+            & contents ?~ binary (ByteString.pack [98, 121, 101])++    it "the latter write wins" $ do+      temporary $ \p -> do+        let l = do+              dir "foo" $+                file "qux"+                  & contents ?~ binary (ByteString.pack [104, 101, 108, 108, 111])+              dir "foo" $+                file "qux"+                  & contents ?~ binary (ByteString.pack [98, 121, 101])+        _ <- make p l+        fit p l `shouldReturn` errors+          [ FitBadFileContents (p </> "foo" </> "qux") $ FitBadBinary+              (ByteString.pack [104, 101, 108, 108, 111])+              (ByteString.pack [98, 121, 101])+          ]++tonel :: a -> Validation (NonEmpty a) b+tonel = Error . pure++makefit :: Layout a -> IO ()+makefit l = temporary $ \p -> do+  make p l `shouldReturn` errors []+  fit p l `shouldReturn` errors []
+ test/System/Directory/LayoutSpec.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+module System.Directory.LayoutSpec+  ( spec+  ) where++import           Control.Lens+import           Test.Hspec++import           System.Directory.Layout hiding (spec)+import qualified System.Directory.Layout.Interpreter as L+++spec :: Spec+spec = do+  describe "directory-layout project structure" $ do+    rel L.spec project++project :: Layout ()+project = do+  dir "src" $ do+    dirs ["System", "Directory"] $ do+      dir "Layout" $ do+        file "Internal.hs"+        file "Interpreter.hs"+      file "Layout.hs"+  dir "test" $ do+    dirs ["System", "Directory"] $ do+      dir "Layout" $ do+        file "InternalSpec.hs"+        file "InterpreterSpec.hs"+      file "LayoutSpec.hs"+    file "Spec.hs"+      & contents ?~ [dedent|+        {-# OPTIONS_GHC -F -pgmF hspec-discover #-}+        |]+    file "SpecHelper.hs"+  file "LICENSE"+  file "Guardfile"+  file "Setup.hs"+  file "directory-layout.cabal"++rel :: (FilePath -> Layout a -> b) -> Layout a -> b+rel f = f "."
− tests/Hspec.hs
@@ -1,62 +0,0 @@-module Main where--import Control.Monad ((>=>))-import Data.Semigroup ((<>))-import System.Directory.Layout-import Test.Hspec---main :: IO ()-main = hspec $ do-  describe "basic equalities" $ do-    it "is sane" $ do-      layout_0 == layout_1  `shouldBe` False-      layout_1 == layout_1' `shouldBe` True-  describe "indentity monad laws" $ do-    it "holds for layout 0" $ do-      (layout_0 >>= return) `shouldBe` layout_0-      (return () >>= \() -> layout_0) `shouldBe` layout_0-    it "holds for layout 1" $ do-      (layout_1 >>= return) `shouldBe` layout_1-      (return () >>= \() -> layout_1) `shouldBe` layout_1-  describe "associativity monad law" $ do-    it "holds for layouts 2 and 3" $ do-      layout_2 (>>) `shouldBe` layout_3 (>>)-    it "holds for layouts 4 and 5" $ do-      layout_4 () `shouldBe` layout_5 ()-  describe "associativity semigroup law" $ do-    it "holds for layouts 2 and 3" $ do-      layout_2 (<>) `shouldBe` layout_3 (<>)---layout_0, layout_1, layout_1' :: Layout-layout_0 = do-  file_ "foo"-  file_ "bar"-  file_ "baz"-layout_1 = do-  file_ "foo"-  file_ "bar"-  directory "quux" $ do-    file_ "zem"-    file_ "zek"-  file_ "baz"-layout_1' = do-  file_ "foo"-  file_ "bar"-  directory "quux" $ do-    file_ "zek"-    file_ "zem"-  file_ "baz"--layout_2, layout_3 :: (Layout -> Layout -> Layout) -> Layout-layout_2 (#) =-  (file_ "foo" # file_ "bar") # file_ "baz"-layout_3 (#) =-  file_ "foo" # (file_ "bar" # file_ "baz")--layout_4, layout_5 :: () -> Layout-layout_4 =-  (const (file_ "foo") >=> const (file_ "bar")) >=> const (file_ "baz")-layout_5 =-  const (file_ "foo") >=> (const (file_ "bar") >=> const (file_ "baz"))
− tests/Main.hs
@@ -1,144 +0,0 @@-{-# 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
@@ -1,12 +0,0 @@-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"]