diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,4 @@
+import Distribution.Simple
+
+main :: IO ()
+main = defaultMain
diff --git a/directory-layout.cabal b/directory-layout.cabal
new file mode 100644
--- /dev/null
+++ b/directory-layout.cabal
@@ -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
diff --git a/src/System/Directory/Layout.hs b/src/System/Directory/Layout.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Directory/Layout.hs
@@ -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 ())
diff --git a/src/System/Directory/Layout/Check.hs b/src/System/Directory/Layout/Check.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Directory/Layout/Check.hs
@@ -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
diff --git a/src/System/Directory/Layout/Internal.hs b/src/System/Directory/Layout/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Directory/Layout/Internal.hs
@@ -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)
diff --git a/src/System/Directory/Layout/Make.hs b/src/System/Directory/Layout/Make.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Directory/Layout/Make.hs
@@ -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
diff --git a/src/System/Directory/Layout/Parser.hs b/src/System/Directory/Layout/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Directory/Layout/Parser.hs
@@ -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'
