packages feed

ghc-events-analyze-0.2.8: src/GHC/RTS/Events/Analyze/Utils.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module GHC.RTS.Events.Analyze.Utils (
    throwLeft
  , throwLeftStr
  , insertWith
  , explode
  , mapEithers
  , unsparse
  , Alignment(..)
  , renderTable
  , showThreadId
  ) where

import Control.Lens
import Control.Exception
import Data.List (transpose)
import Data.Either (partitionEithers)
import Data.Text (Text)
import qualified Data.Text as T
import GHC.RTS.Events (ThreadId)

throwLeft :: Exception e => IO (Either e a) -> IO a
throwLeft act = act >>= \ea -> case ea of Left  e -> throwIO e
                                          Right a -> return a

throwLeftStr :: IO (Either String a) -> IO a
throwLeftStr = throwLeft . fmap (either (Left . userError) Right)

-- | Like `Map.insertWith`, but for associative lists
--
-- > updateAssocs f key val [.. (key, val') ..] == [.. (key, val' `f` val) ..]
-- > updateAssocs f key val assocs == assocs ++ [(key, val)]
insertWith :: Eq a => (b -> b -> b) -> a -> b -> [(a, b)] -> [(a, b)]
insertWith f key val = go
  where
    go [] = [(key, val)]
    go ((key', val') : assocs)
      | key == key' = (key, val' `f` val) : assocs
      | otherwise   = (key', val')        : go assocs

-- | Like PHP's explode function
--
-- > explode ',' "abc,def,ghi" == ["abc","def","ghi"]
explode :: Eq a => a -> [a] -> [[a]]
explode needle = go
  where
    go xs = case break (== needle) xs of
              (before, [])        -> [before]
              (before, _ : after) -> before : go after

mapEithers :: forall a b c d.
              ([a] -> [c])
           -> ([b] -> [d])
           -> [Either a b]
           -> [Either c d]
mapEithers f g eithers = rebuild eithers (f lefts) (g rights)
  where
    (lefts, rights) = partitionEithers eithers

    rebuild :: [Either a b] -> [c] -> [d] -> [Either c d]
    rebuild []             []       []       = []
    rebuild (Left  _ : es) (x : xs)      ys  = Left  x : rebuild es xs ys
    rebuild (Right _ : es)      xs  (y : ys) = Right y : rebuild es xs ys
    rebuild _ _ _ = error "mapEithers: lengths changed"

-- | Turn a sparse representation of a list into a regular list, using
-- a default value for the blanks
{-# INLINE unsparse #-}
unsparse :: FoldableWithIndex Int f => t -> f t -> [t]
unsparse blank = go 0 . itoList
  where
    --go :: Int -> [(Int, a)] -> [a]
    go _ []            = []
    go n ((m, a) : as) = replicate (m - n) blank ++ a : go (m + 1) as

-- | Alignment options for `renderTable`
data Alignment = AlignLeft | AlignRight

-- | "Typeset" a table
renderTable :: [Alignment] -> [[Text]] -> [[Text]]
renderTable aligns rows = transpose paddedColumns
  where
    columns :: [[Text]]
    columns = transpose rows

    columnWidths :: [Int]
    columnWidths = map (maximum . map T.length) columns

    paddedColumns :: [[Text]]
    paddedColumns = map padColumn (zip3 aligns columnWidths columns)

    padColumn :: (Alignment, Int, [Text]) -> [Text]
    padColumn (align, width, column) = map (padCell align width) column

    padCell :: Alignment -> Int -> Text -> Text
    padCell align width cell =
      let padding = T.replicate (width - T.length cell) " "
      in case align of
           AlignLeft  -> cell <> padding
           AlignRight -> padding <> cell

showThreadId :: ThreadId -> Text
showThreadId = T.pack . show