packages feed

khph-0.1.0: src/Khph/Util.hs

-- This file is part of khph.
--
-- Copyright 2016 Bryan Gardiner <bog@khumba.net>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

-- | Miscellaneous utilities.
module Khph.Util (
  butLast,
  maybeLast,
  splitOn,
  stripTrailingPathSeparators,
  anyM,
  allM,
  warn,
  die,
  rightOrDie,
  splitPath,
  pathIsAncestor,
  droppingIOErrors,
  LinkType (..),
  FileType (..),
  getFileType,
  parserEq,
  ) where

import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.List (findIndex, isPrefixOf)
import System.Exit (exitFailure)
import System.FilePath (isPathSeparator, normalise, splitDirectories)
import System.IO (hPrint, hPutStrLn, stderr)
import System.IO.Error (
  catchIOError,
  doesNotExistErrorType,
  ioeGetErrorType,
  tryIOError,
  )
import System.Posix (
  getFileStatus,
  getSymbolicLinkStatus,
  isDirectory,
  isRegularFile,
  isSymbolicLink,
  )
import Text.Parsec ((<?>), ParsecT, anyToken, try)

butLast :: [a] -> [a]
butLast [] = []
butLast xs = take (length xs - 1) xs

maybeLast :: [a] -> Maybe a
maybeLast [] = Nothing
maybeLast (x:[]) = Just x
maybeLast (_:xs) = maybeLast xs

-- | Splits a list at elements for which a predicate returns true.  The matching
-- elements themselves are dropped.
splitWhen :: (a -> Bool) -> [a] -> [[a]]
splitWhen _ [] = []
splitWhen f xs = case findIndex f xs of
  Just index -> let (term, _:rest) = splitAt index xs
                in term : splitWhen f rest
  Nothing -> [xs]

-- | Splits a string on a specified character.
splitOn :: Char -> String -> [String]
splitOn ch = splitWhen (== ch)

stripTrailingPathSeparators :: FilePath -> FilePath
stripTrailingPathSeparators "" = ""
stripTrailingPathSeparators str =
  if isPathSeparator $ last str
  then reverse $ dropWhile isPathSeparator $ reverse str
  else str

anyM :: Monad m => (a -> m Bool) -> [a] -> m Bool
anyM _ [] = return False
anyM f (x:xs) = do b <- f x
                   if b then return True else anyM f xs

allM :: Monad m => (a -> m Bool) -> [a] -> m Bool
allM _ [] = return True
allM f (x:xs) = do b <- f x
                   if b then allM f xs else return False

warn :: MonadIO m => String -> m ()
warn msg = liftIO $ hPutStrLn stderr $ "khph: Warning: " ++ msg

die :: MonadIO m => String -> m a
die msg = liftIO $ do
  hPutStrLn stderr $ "khph: Error: " ++ msg
  exitFailure

rightOrDie :: MonadIO m => Either String a -> m a
rightOrDie = either (liftIO . die) return

splitPath :: FilePath -> [String]
splitPath = splitDirectories . normalise

pathIsAncestor :: FilePath -> FilePath -> Bool
pathIsAncestor path1 path2 = splitPath path1 `isPrefixOf` splitPath path2

-- | Executes an IO action, calling the handler function if the action succeeds,
-- and printing the error message to stderr if the action throws an exception.
droppingIOErrors :: MonadIO m => IO a -> (a -> m ()) -> m ()
droppingIOErrors action onSuccess = do
  result <- liftIO $ tryIOError action
  case result of
    Left err -> liftIO $ hPrint stderr err
    Right x -> onSuccess x

data LinkType = HardLink | SoftLink
              deriving (Eq, Show)

data FileType =
    Nonexistant
  | File
  | Directory
  | SymlinkTo FileType
  | Other
  deriving (Eq, Show)

getFileType :: FilePath -> IO FileType
getFileType = query getSymbolicLinkStatus
  where query f path = flip catchIOError handler $ do
          stat <- f path
          case undefined of
            _ | isRegularFile stat -> return File
            _ | isDirectory stat -> return Directory
            _ | isSymbolicLink stat -> fmap SymlinkTo $ query getFileStatus path
            _ -> return Other

        handler e =
          if ioeGetErrorType e == doesNotExistErrorType
          then return Nonexistant
          else ioError e

parserEq :: (Eq a, Show a, Monad m) => a -> ParsecT [a] u m ()
parserEq t =
  try (do t' <- anyToken
          if t == t' then return () else fail "") <?>
  show t