    -----------------------------------------------------------------------------
    -- |
    -- Module      :  XMonad.Prompt.DirExec
    -- Copyright   :  (C) 2008 Juraj Hercek
    -- License     :  BSD3
    --
    -- Maintainer  :  juhe_xmonad@hck.sk
    -- Stability   :  unstable
    -- Portability :  unportable
    --
    -- A directory file executables prompt for XMonad. This might be useful if you
    -- don't want to have scripts in your PATH environment variable (same
    -- executable names, different behavior) - otherwise you might want to use
    -- "XMonad.Prompt.Shell" instead - but you want to have easy access to these
    -- executables through the xmonad's prompt.
    --
    -----------------------------------------------------------------------------

    module XMonad.Prompt.DirExec
        ( -- * Usage
          -- $usage
          dirExecPrompt
        , dirExecPromptNamed
        , DirExec
        ) where

    import Control.Exception as E
    import System.Directory
    import Control.Monad
    import Data.List
    import XMonad
    import XMonad.Prompt

    econst :: Monad m => a -> IOException -> m a
    econst = const . return

    -- $usage
    -- 1. In your @~\/.xmonad\/xmonad.hs@:
    --
    -- > import XMonad.Prompt.DirExec
    --
    -- 2. In your keybindings add something like:
    --
    -- >   , ("M-C-x", dirExecPrompt def spawn "/home/joe/.scipts")
    --
    -- or
    --
    -- >   , ("M-C-x", dirExecPromptNamed def spawn
    -- >                                  "/home/joe/.scripts" "My Scripts: ")
    --
    -- or add this after your default bindings:
    --
    -- >   ++
    -- >   [ ("M-x " ++ key, dirExecPrompt def fn "/home/joe/.scripts")
    -- >     | (key, fn) <- [ ("x", spawn), ("M-x", runInTerm "-hold") ]
    -- >   ]
    -- >   ++
    --
    -- The first alternative uses the last element of the directory path for
    -- a name of prompt. The second alternative uses the provided string
    -- for the name of the prompt. The third alternative defines 2 key bindings,
    -- first one spawns the program by shell, second one runs the program in
    -- terminal
    --
    -- For detailed instruction on editing the key binding see
    -- "XMonad.Doc.Extending#Editing_key_bindings".

    data DirExec = DirExec String

    instance XPrompt DirExec where
        showXPrompt (DirExec name) = name

    -- | Function 'dirExecPrompt' starts the prompt with list of all executable
    -- files in directory specified by 'FilePath'. The name of the prompt is taken
    -- from the last element of the path. If you specify root directory - @\/@ - as
    -- the path, name @Root:@ will be used as the name of the prompt instead. The
    -- 'XPConfig' parameter can be used to customize visuals of the prompt.
    -- The runner parameter specifies the function used to run the program - see
    -- usage for more information
    dirExecPrompt :: XPConfig -> (String -> X ()) -> FilePath -> X ()
    dirExecPrompt cfg runner path = do
        let name = (++ ": ") . last
                             . (["Root"] ++) -- handling of "/" path parameter
                             . words
                             . map (\x -> if x == '/' then ' ' else x)
                             $ path
        dirExecPromptNamed cfg runner path name

    -- | Function 'dirExecPromptNamed' does the same as 'dirExecPrompt' except
    -- the name of the prompt is specified by 'String' parameter.
    dirExecPromptNamed :: XPConfig -> (String -> X ()) -> FilePath -> String -> X ()
    dirExecPromptNamed cfg runner path name = do
        let path' = path ++ "/"
        cmds <- io $ getDirectoryExecutables path'
        mkXPrompt (DirExec name) cfg (compList cmds) (runner . (path' ++))
        where
            compList cmds s = return . filter (isInfixOf s) $ cmds

    getDirectoryExecutables :: FilePath -> IO [String]
    getDirectoryExecutables path =
        (getDirectoryContents path >>=
            filterM (\x -> let x' = path ++ x in
                liftM2 (&&)
                    (doesFileExist x')
                    (liftM executable (getPermissions x'))))
        `E.catch` econst []