{-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TupleSections #-} {-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE CPP #-} module System.Util ( isRW , isExe , exeExists , getShellExe , getXdgConfigFolder , getDotConfigFolder , getHomeFolder , getCurrFolder , getCustomFolder , checkOrCreate , tidleExp , normFilePath , getDefaultShell ) where import Prelude import Data.Semigroup import System.Lifted import System.Directory.Lifted import System.Environment.Lifted import System.Posix.User.Lifted import System.FilePath import System.IO.Error import GHC.IO.Exception import Control.Monad.Trans.Either import Control.Applicative ------------------------------------------------------------------------------ -- Text Utils ---------------------------------------------------------------- ------------------------------------------------------------------------------ type EitherIOException = EitherT IOException deriveSystemLiftedErrors "DisallowIOE [HardwareFault]" ''EitherIOException deriveSystemDirectory ''EitherIOException deriveSystemEnvironment ''EitherIOException deriveSystemPosixUser ''EitherIOException isRW :: FilePath -> EitherT IOException IO FilePath isRW path = do perm <- getPermissions path if readable perm && writable perm then right path else left $ mkIOError PermissionDenied "Missing read/write permissions" Nothing (Just path) isExe :: FilePath -> EitherT IOException IO FilePath isExe path = do perm <- getPermissions path if executable perm then right path else left $ mkIOError PermissionDenied "Missing executable permissions" Nothing (Just path) doesNotExist :: String -> IOError doesNotExist str = mkIOError doesNotExistErrorType str Nothing Nothing -- this could receive a path relative to the current directory or -- the executable name (in the path). exeExists :: FilePath -> EitherT IOException IO FilePath exeExists path = joinMET (doesNotExist "Executable not found") $ bimapEitherT id Just (isExe path) <> findExecutable path -- | Check if a executable pointed by the given environement -- variable exists, and is executable. getShellExe :: FilePath -> EitherT IOException IO FilePath getShellExe var = do exe <- joinMET ( doesNotExist ( "Variable " <> var <> " not found in current environment") ) $ lookupEnv var exeExists exe -- | Get configuration folder -- http://stackoverflow.com/a/1024339/516184 getXdgConfigFolder :: EitherT IOException IO FilePath getXdgConfigFolder = isRW =<< getEnv "XDG_CONFIG_HOME" getDotConfigFolder :: EitherT IOException IO FilePath getDotConfigFolder = do home <- isRW =<< getHomeDirectory isRW $ home ".config" getHomeFolder :: EitherT IOException IO FilePath getHomeFolder = isRW =<< getHomeDirectory getCurrFolder :: EitherT IOException IO FilePath getCurrFolder = isRW "." getCustomFolder :: Maybe FilePath -> EitherT IOException IO FilePath getCustomFolder fp = case fp of Nothing -> left . userError $ "No path provided" Just fp' -> do dirExists <- doesDirectoryExist fp' if dirExists then isRW fp' else left $ mkIOError NoSuchThing "Invalid path provided" Nothing (Just fp') -- | Check if a given directory exists, and if it features Read/Write permissions. -- If it does not exist, create it. checkOrCreate :: FilePath -> EitherT IOException IO FilePath checkOrCreate path = createDirectoryIfMissing True path >> isRW path -- | Perform tidle expansion so that @~ = $HOME@. -- It does not handle tidle as $HOME in other places besides first caracter -- in FilePath tidleExp :: FilePath -> EitherT IOException IO FilePath tidleExp fp = do home <- getHomeFolder right $ case fp of "" -> "" (x:xs) -> case x of '~' -> case xs of [] -> home (y:ys) -> if isPathSeparator y then home ys else x:xs _ -> x:xs normFilePath :: FilePath -> EitherT IOException IO FilePath normFilePath fp = makeValid . normalise <$> (canonicalizePath =<< tidleExp fp) -- | Determine system shell: best effort. Tries @$SHELL@ variable first, -- then POSIX shell user entry, than in a last effort @/bin/sh@. getDefaultShell :: EitherT IOException IO FilePath getDefaultShell = envShell <> nixShell <> wtfShell where envShell = getEnv "SHELL" nixShell = fmap userShell $ getUserEntryForID =<< getRealUserID wtfShell = joinMET (mkIOError NoSuchThing "No shell found!" Nothing Nothing) $ findExecutable "/bin/sh"