content: > {-# LANGUAGE ScopedTypeVariables #-}
  >
  > module FileNameUtils          ( extension
  >                               , expandPath
  >                               , chaseFile
  >                               , readTextFile
  >                               , openOutputFile
  >                               , modifySearchPath
  >                               , deep, env
  >                               , absPath
  >                               , module System.FilePath
  >                               ) where
  >
  > import Prelude
  > import System.IO              (  openFile, IOMode(..), hPutStrLn, stderr,
  >                                  hSetEncoding, hGetContents, utf8, Handle() )
  > import System.IO.Error        (  isDoesNotExistError, isPermissionError )
  > import System.Directory
  > import System.Environment
  > import Data.List
  > import Control.Monad (filterM)
  > import Control.Exception as E
  >                               (  try, catch, IOException )
  > import System.FilePath
  > import System.Info
  >
  > import Auxiliaries
  
  A searchpath can be added to the front or to the back of the current path
  by pre- or postfixing it with a path separator. Otherwise the new search
  path replaces the current one.
  
  > modifySearchPath              :: [FilePath] -> String -> [FilePath]
  > modifySearchPath p np
  >   | isSearchPathSeparator (head np)                = p ++ split
  >   | isSearchPathSeparator (last np)                = split ++ p
  >   | otherwise                                      = split
  >   where split = splitOn isSearchPathSeparator np
  
  > -- relPath =  joinpath
  
  > -- absPath ps  =  directorySeparator : relPath ps
  
  > isWindows                     :: Bool
  > isWindows = "win" `isPrefixOf` os || "Win" `isPrefixOf` os || "mingw" `isPrefixOf` os
  
  > absPath                       :: FilePath -> FilePath
  > absPath                       =  if isWindows then
  >                                    (("C:" ++ [pathSeparator]) ++)
  >                                  else
  >                                    (pathSeparator :)
  
  > deep                          :: FilePath -> FilePath
  > deep                          =  (++(replicate 2 pathSeparator))
  
  > env                           :: String -> FilePath
  > env x                         =  "{" ++ x ++ "}"
  
  > extension                     :: FilePath -> Maybe String
  > extension fn                  =  case takeExtension fn of
  >                                    ""       ->  Nothing
  >                                    (_:ext)  ->  Just ext
  
  > -- dirname = takeDirectory
  > -- filename = takeFilePath
  > -- basename = takeBaseName
  
  |expandPath| does two things: it replaces curly braced strings with
  environment entries, if present; furthermore, if the path ends with
  more than one directory separator, all subpaths are added ...
  
  > expandPath                    :: [String] -> IO [String]
  > expandPath s                  =  do let s' = concatMap splitSearchPath s
  >                                     s''  <- mapM expandEnvironment s'
  >                                     s''' <- mapM findSubPaths (concat s'')
  >                                     return (nub $ concat s''')
  
  > findSubPaths                  :: String -> IO [String]
  > findSubPaths ""               =  return []
  > findSubPaths s                =  let rs = reverse s
  >                                      (sep,rs') = span isPathSeparator rs
  >                                      s'   = reverse rs'
  >                                      sep' = reverse sep
  >                                  in  if   null s'
  >                                      then return [[head sep']] {- we don't descend from root -}
  >                                      else if   length sep < 2
  >                                           then return [s]
  >                                           else descendFrom s'
  
  > descendFrom                   :: String -> IO [String]
  > descendFrom s                 =  E.catch (do  d <- getDirectoryContents s
  >                                               {- no hidden files, no parents -}
  >                                               let d' = map (\x -> s </> x)
  >                                                      . filter ((/='.') . head) . filter (not . null) $ d
  >                                               d'' <- filterM doesDirectoryExist d'
  >                                               d''' <- mapM descendFrom d''
  >                                               return (s : concat d''')
  >                                          )
  >                                          (\ (_ :: IOException) -> return [s])
  
  > expandEnvironment             :: String -> IO [String]
  > expandEnvironment s           =  case break (=='{') s of
  >                                    (_s',"")   -> return [s]
  >                                    (s','{':r) -> case break (=='}') r of
  >                                                    (_e,"") -> return [s]
  >                                                    (e,'}':r') -> findEnvironment e s' r'
  >                                                    _ -> impossible "expandEnvironment"
  >                                    _          -> impossible "expandEnvironment"
  >   where findEnvironment       :: String -> String -> String -> IO [String]
  >         findEnvironment e a o =  do er <- try (getEnv e)
  >                                     return $ either (\ (_ :: IOException) -> [])
  >                                                     (map (\x -> a ++ x ++ o) . splitOn isSearchPathSeparator)
  >                                                     er
  
  > readTextFile                  :: FilePath -> IO String
  > readTextFile f                =  do h <- openFile f ReadMode
  >                                     hSetEncoding h utf8
  >                                     hGetContents h
  
  > openOutputFile                :: FilePath -> IO Handle
  > openOutputFile f              =  do h <- openFile f WriteMode
  >                                     hSetEncoding h utf8
  >                                     return h
  
  > chaseFile                     :: [String]    {- search path -}
  >                               -> FilePath -> IO (String,FilePath)
  > chaseFile p fn | isAbsolute fn=  E.catch (t fn) (handle fn (err "."))
  >                | p == []      =  chaseFile ["."] fn
  >                | otherwise    =  s $ map (\ d -> md d ++ fn) p
  >   where
  >   md cs | isPathSeparator (last cs)
  >                               =  cs
  >         | otherwise           =  addTrailingPathSeparator cs
  >   t f                         =  readTextFile f >>= \x -> return (x,f)
  >   s []                        =  err $ " in search path:\n" ++ showpath
  >   s (x:xs)                    =  E.catch (t x) (handle x (s xs))
  >   err extra                   =  ioError
  >                               $  userError $ "File `" ++ fn ++ "' not found or not readable" ++ extra
  >   handle :: FilePath -> IO (String,FilePath) -> IOException -> IO (String,FilePath)
  >   handle x k e                =
  >                                    if isDoesNotExistError e then k
  >                                    else if isPermissionError e then do
  >                                      hPutStrLn stderr $ "Warning: could not access " ++ x ++ " due to permission error."
  >                                      k
  >                                    else ioError e
  >   showpath                    =  concatMap (\x -> "   " ++ x ++ "\n") p
  