packages feed

hbro-1.2.0.0: library/Hbro/Prelude.hs

{-# LANGUAGE ConstraintKinds #-}
-- | Replacement for the traditional @Prelude@ module. This module doesn't import any other Hbro.* module, so it can be safely imported from anywhere in the project.
module Hbro.Prelude
    ( module X
-- * Typeclass
    , ToList(..)
    , ToSet(..)
    , ToNonEmpty(..)
    , Describable(..)
    , BaseIO
    , ControlIO
-- * Generic aliases/shortcuts
    , leftM
    , io
    , (>/>)
    , abort
    , doNothing
-- * Lens util
    , withM
    , withM_
    , fwd
    , askL
-- * Concurrent util
    , writeTMVar
    , withAsyncList
    , withAsyncList_
-- * Gtk util
    , gSync
    , gAsync
-- * Process control
    , spawn
-- * Pango util
    , allItalic
    , allBold
    , black
    , gray
    , red
    , green
    , blue
    , yellow
) where

-- {{{ Imports
import ClassyPrelude as X hiding(Builder(..), log, toList)

import Control.Applicative as X (Alternative(..), WrappedMonad)
import Control.Arrow as X (Kleisli(..), left, right)
import Control.Concurrent.Async.Lifted
import Control.Conditional as X (ToBool(..), (<|), (|>), (<<|), (|>>))
import Control.Lens
import Control.Monad.Base as X (MonadBase(..))
import Control.Monad.Trans.Control as X
import Control.Monad.Reader

import Data.Default as X
import Data.Foldable as X (asum)
import Data.Functor as X
import Data.List as X (tail)
import Data.List.NonEmpty (NonEmpty(..))
import Data.Maybe as X (fromJust)

import Graphics.Rendering.Pango.Enums
import Graphics.UI.Gtk.General.General

import Safe as X (initSafe, tailSafe)

import System.Log.Logger
-- import System.Posix.Process
-- import System.Posix.Types
import System.Process
-- }}}

-- | We often want to use a single object where a list is expected.
class ToList m t | t -> m where toList :: t -> [m]

instance ToList m [m] where toList = id

-- | We often want to use a single object where a set is expected.
class ToSet m t | t -> m where toSet :: t -> Set m

instance ToSet m (Set m) where toSet = id

-- | We often want to use a single object where a non-empty list is expected.
class ToNonEmpty s t | t -> s where toNonEmpty :: t -> NonEmpty s

instance ToNonEmpty s (NonEmpty s) where toNonEmpty = id

-- | Like 'Show', for 'Text'
class Describable a where describe :: a -> Text

-- | Mix of 'MonadBase IO' and 'MonadIO'
type BaseIO m = (MonadBase IO m, MonadIO m)

-- | Mix of 'MonadBaseControl IO' and 'MonadIO'
type ControlIO m = (MonadBaseControl IO m, MonadIO m)

-- {{{ Generic aliases/shortcuts
-- 'left' for 'Kleisli' arrows
leftM :: Monad m => (a -> m b) -> Either a c -> m (Either b c)
leftM f = runKleisli (left $ Kleisli f)

-- | Alias for 'liftIO'
io :: MonadIO m => IO a -> m a
io = liftIO

-- | Like '(\</\>)' with first argument in @IO@ to build platform-dependent paths.
(>/>) :: (BaseIO m) => IO FilePath -> FilePath -> m FilePath
(>/>) a b = io $ (</> b) <$> a

-- | Alias for 'mzero'
abort :: MonadPlus m => m a
abort = mzero

-- | Alias for @return ()@
doNothing :: Monad m => m ()
doNothing = return ()
-- }}}

-- {{{ Lens util
-- | Alias for 'mapMOf'
withM :: Profunctor p => Over p (WrappedMonad m) s t a b -> p a (m b) -> s -> m t
withM = mapMOf

withM_ :: Monad m => Over (->) (WrappedMonad m) s t a a -> (a -> m ()) -> s -> m t
withM_ l f = mapMOf l (fwd f)

fwd :: (Monad m) => (a -> m ()) -> a -> m a
fwd f x = f x >> return x

-- | Call 'asks' with the given lens getter.
askL ::(MonadReader t m) => Lens' t a -> m a
askL l = asks $ view l
-- }}}

-- {{{ Concurrent util
-- | This is a combination of 'tryTakeTMVar' and 'putTMVar';
-- ie. it empties the 'TMVar' if needed, and puts the new value instead.
writeTMVar :: TMVar a -> a -> STM ()
writeTMVar var val = do
    tryTakeTMVar var
    putTMVar var val

-- | Recursive 'withAsync'
withAsyncList :: (ControlIO m) => [m a] -> ([Async (StM m a)] -> m b) -> m b
withAsyncList a f = withAsyncList' a [] f

-- | Same as 'withAsyncList', but discards the result
withAsyncList_ :: (ControlIO m) => [m a] -> ([Async (StM m a)] -> m b) -> m ()
withAsyncList_ a f = void $ withAsyncList a f

withAsyncList' :: (ControlIO m) => [m a] -> [Async (StM m a)] -> ([Async (StM m a)] -> m b) -> m b
withAsyncList' [] x f  = f x
withAsyncList' (a:b) x f = withAsync a $ \x' -> withAsyncList' b (x':x) f
-- }}}

-- {{{ Gtk util
-- | Lifted alias for 'postGUISync'
gSync :: (BaseIO m) => IO a -> m a
gSync  = io . postGUISync

-- | Lifted alias for 'postGUIAsync'
gAsync :: (BaseIO m) => IO () -> m ()
gAsync = io . postGUIAsync
-- }}}

-- {{{ Process management
-- | Run external command and don't die when parent process exits.
spawn :: (BaseIO m) => String -> [String] -> m ()
spawn command options = io $ do
    debugM "hbro.prelude" $ "Executing command: " ++ unwords (command:options)
    void $ createProcess (proc command options) { std_in = CreatePipe,  std_out = CreatePipe, std_err = CreatePipe, close_fds = True }

-- Return the list of process IDs corresponding to all running instances of the browser.
-- getAllProcessIDs :: MonadIO m => m [ProcessID]
-- getAllProcessIDs = do
--     (_, pids, _)  <- io $ readProcessWithExitCode "pidof" ["hbro"] []
--     (_, pids', _) <- io $ readProcessWithExitCode "pidof" ["hbro-" ++ Sys.os ++ "-" ++ Sys.arch] []
--     myPid         <- io $ getProcessID

--     return $ delete myPid . map (read :: String -> ProcessID) . nub . words $ pids ++ " " ++ pids'
-- }}}


{-errorHandler :: (MonadIO m, MonadReader r m, HasOptions r) => FilePath -> IOError -> m ()
errorHandler file e = do
  when (isAlreadyInUseError e) $ unlessQuiet . io . putStrLn $ "ERROR: file <" ++ file ++ "> is already opened and cannot be reopened."
  when (isDoesNotExistError e) $ unlessQuiet . io . putStrLn $ "ERROR: file <" ++ file ++ "> doesn't exist."
  when (isPermissionError   e) $ unlessQuiet . io . putStrLn $ "ERROR: user doesn't have permission to open file <" ++ file ++ ">."-}

-- {{{ Pango util
allItalic, allBold :: PangoAttribute
allItalic = AttrStyle  {paStart = 0, paEnd = -1, paStyle  = StyleItalic}
allBold   = AttrWeight {paStart = 0, paEnd = -1, paWeight = WeightBold}

black, gray, red, green, blue, yellow :: Color
black  = Color     0     0     0
gray   = Color 32767 32767 32767
red    = Color 65535     0     0
green  = Color     0 65535     0
blue   = Color     0     0 65535
yellow = Color 65535 65535     0
-- }}}