packages feed

directory-layout (empty) → 0.1.0.0

raw patch · 8 files changed

+481/−0 lines, 8 filesdep +basedep +directorydep +filepathsetup-changed

Dependencies added: base, directory, filepath, parsec, text, transformers

Files

+ LICENSE view
@@ -0,0 +1,19 @@+Copyright (C) 2012 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:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++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.
+ Setup.hs view
@@ -0,0 +1,4 @@+import Distribution.Simple++main :: IO ()+main = defaultMain
+ directory-layout.cabal view
@@ -0,0 +1,31 @@+name:          directory-layout+version:       0.1.0.0+synopsis:      Declare, construct and verify directory layout+description:   Friendly 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.8++library+  exposed-modules: System.Directory.Layout+                   System.Directory.Layout.Check+                   System.Directory.Layout.Make+                   System.Directory.Layout.Parser+                   System.Directory.Layout.Internal+  hs-source-dirs: src+  build-depends: base >= 3 && < 5,+                 directory,+                 filepath,+                 transformers,+                 parsec,+                 text+  ghc-options: -Wall+               -fno-warn-unused-do-bind++source-repository head+  type: git+  location: https://github.com/supki/directory-layout
+ src/System/Directory/Layout.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE UnicodeSyntax #-}+module System.Directory.Layout+  ( -- * Layout declaration+    DL, Layout, file, file_, directory, directory_+    -- * Layout construction+  , DLMakeWarning(..), make+    -- * Layout verification+  , DLCheckFailure(..), check+    -- * Layout parsers+  , layout, layout'+  ) where++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')+++-- | Declare file with specified contents+file ∷ FilePath → Text → Layout+file x c = F x (Just c) (return ())+++-- | Declare empty file+file_ ∷ FilePath → Layout+file_ x = F x Nothing (return ())+++-- | Declare directory with specified listing+directory ∷ FilePath → Layout → Layout+directory x d = D x d (return ())+++-- | Declare empty directory+directory_ ∷ FilePath → Layout+directory_ x = D x (return ()) (return ())
+ src/System/Directory/Layout/Check.hs view
@@ -0,0 +1,126 @@+{-# 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/Internal.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE UnicodeSyntax #-}+{-# OPTIONS_HADDOCK hide #-}+module System.Directory.Layout.Internal+  ( DL(..), Layout+  ) where++import Data.Text (Text)+++-- | 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)+++-- | But type synonym is nicer+type Layout = DL ()+++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)+++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)
+ src/System/Directory/Layout/Make.hs view
@@ -0,0 +1,127 @@+{-# 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 view
@@ -0,0 +1,104 @@+{-# 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'