packages feed

sectile-0.1.0.0: src/Data/Sectile/System/Linux/Internal.hs

-- |
-- Module        : Data.Sectile.System.Linux.Internal
-- Copyright     : Gautier DI FOLCO
-- License       : ISC
--
-- Maintainer    : Gautier DI FOLCO <foss@difolco.dev>
-- Stability     : Stable
-- Portability   : Portable
module Data.Sectile.System.Linux.Internal
  ( errMsg,
    mkFormatted,
    tryReadFile,
    tryWriteFile,
    formatKiB,
    showFFloat1,
    readInt,
    readDouble,
  )
where

import qualified Control.Exception as Exception
import Control.Monad.State (State)
import qualified Data.ByteString.Builder as B
import Data.Maybe (fromMaybe)
import qualified Data.Sectile.Tmux as Colour
import Data.Sectile.Types
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TLE
import qualified Data.Text.Read as T
import Numeric (showFFloat)

-- Internal helpers

-- | Build an error message from a segment name.
errMsg :: B.Builder -> T.Text
errMsg name = "Error on " <> TL.toStrict (TLE.decodeUtf8 (B.toLazyByteString name))

-- | Build a 'Formatted' value with standard explain structure.
mkFormatted :: B.Builder -> T.Text -> T.Text -> [(T.Text, T.Text)] -> State Env Formatted
mkFormatted name typeName txt extraFields = do
  currentSt <- currentStyle
  bnds <- currentBindings
  let (finalStyle, rendered) = Colour.parseAnsiChunks currentSt txt
      explain renderStyle renderChunks =
        DetailList $
          [ DetailPlain $ "Name: " <> name,
            DetailPlain $ "Type: " <> T.encodeUtf8Builder typeName,
            DetailPlain $ "Value: " <> T.encodeUtf8Builder txt,
            DetailPlain $ "Style: " <> fromMaybe "<none>" (renderStyle currentSt) <> " -> " <> fromMaybe "<none>" (renderStyle finalStyle),
            DetailPlain $ "Rendered: " <> renderChunks rendered
          ]
            <> map (\(k, v) -> DetailPlain $ T.encodeUtf8Builder k <> ": " <> T.encodeUtf8Builder v) extraFields
            <> bindingsDetail bnds
  _ <- updateStyle (const finalStyle)
  pure Formatted {..}

-- | Try to read a file, catching any IOException.
tryReadFile :: FilePath -> IO (Either IOError T.Text)
tryReadFile path =
  (Right . T.pack <$> readFile path)
    `Exception.catch` (\(e :: IOError) -> pure $ Left e)

-- | Try to write a file, catching any IOException.
tryWriteFile :: FilePath -> T.Text -> IO (Either IOError ())
tryWriteFile path content =
  (Right <$> writeFile path (T.unpack content))
    `Exception.catch` (\(e :: IOError) -> pure $ Left e)

-- | Format kibibytes to human-readable.
formatKiB :: Int -> T.Text
formatKiB kb
  | kb >= 1024 ^ (4 :: Int) = T.pack (showFFloat1 (fromIntegral kb / (1024.0 ** 4) :: Double)) <> "EiB"
  | kb >= 1024 ^ (3 :: Int) = T.pack (showFFloat1 (fromIntegral kb / (1024.0 ** 3) :: Double)) <> "TiB"
  | kb >= 1024 ^ (2 :: Int) = T.pack (showFFloat1 (fromIntegral kb / (1024.0 ** 2) :: Double)) <> "GiB"
  | kb >= 1024 = T.pack (showFFloat1 (fromIntegral kb / 1024.0 :: Double)) <> "MiB"
  | otherwise = T.pack (show kb) <> "KiB"

-- | Show a Double with 1 decimal place.
showFFloat1 :: Double -> String
showFFloat1 x = showFFloat (Just n) x ""
  where
    n
      | abs x >= 100 = 0
      | abs x >= 10 = 1
      | otherwise = 2

-- | Read an Int from Text, returning Nothing on failure.
readInt :: T.Text -> Maybe Int
readInt t = case T.decimal t of
  Right (n, _) -> Just n
  Left _ -> Nothing

-- | Read a Double from Text, returning Nothing on failure.
readDouble :: T.Text -> Maybe Double
readDouble t = case T.double t of
  Right (n, _) -> Just n
  Left _ -> Nothing