packages feed

mangrove-cli-0.5.0.0: src/Mangrove/Render.hs

{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs             #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies      #-}

{-|
Module      : Mangrove.Render
Copyright   : (c) Quytelda Kahja, 2026
License     : BSD-3-Clause

Facilities for textual representation of data structures.
-}
module Mangrove.Render
  ( -- * Text Rendering
    Render(..)
  , renderLazyText
  , renderText
  , putBuilder
  , hPutBuilder

    -- * Helpers & Combinators
  , munless
  , between
  , brackets
  , braces
  , quotes
  , renderDelimitedIf
  , wrapIf
  , bracesIf
  , bracketsIf

    -- * Usage
  , Usage(..)

    -- * Re-exports
  , Builder
  ) where

import           Data.Kind
import           Data.Text              (Text)
import qualified Data.Text              as T
import qualified Data.Text.Lazy         as TL
import           Data.Text.Lazy.Builder (Builder)
import qualified Data.Text.Lazy.Builder as TLB
import qualified Data.Text.Lazy.IO      as TLIO
import           System.IO

-- | A class for things that can be rendered to a text 'Builder'.
class Render a where
  render :: a -> Builder

instance Render Builder where
  render = id

instance Render T.Text where
  render = TLB.fromText

instance Render Char where
  render = TLB.singleton

instance Render String where
  render = TLB.fromString

-- | Convert renderable data directly to lazy 'TL.Text'.
renderLazyText :: Render a => a -> TL.Text
renderLazyText = TLB.toLazyText . render

-- | Convert renderable data directly to strict 'T.Text'.
renderText :: Render a => a -> Text
renderText = TL.toStrict . TLB.toLazyText . render

-- | Write the contents of a 'Builder' to standard output.
putBuilder :: Builder -> IO ()
putBuilder = TLIO.putStr . TLB.toLazyText

-- | Write the contents of a 'Builder' to some IO handle.
hPutBuilder :: Handle -> Builder -> IO ()
hPutBuilder handle = TLIO.hPutStr handle . TLB.toLazyText

--------------------------------------------------------------------------------
-- Combinators

-- | Similar to 'Control.Monad.unless', but for monoids.
munless :: Monoid m => Bool -> m -> m
munless condition m = if condition then mempty else m

-- | @between open close s@ surrounds @s@ with @open@ and @close@
-- (i.e. @open <> s <> close@).
between :: Monoid m => m -> m -> m -> m
between open close s = open <> s <> close

-- | Surround a string with square brackets.
brackets :: Builder -> Builder
brackets = between "[" "]"

-- | Surround a string with curly braces.
braces :: Builder -> Builder
braces = between "{" "}"

-- | Surround a string with double quotes.
quotes :: Builder -> Builder
quotes = between "\"" "\""

-- | Combinator to modify an expression using some wrapping function
-- if a condition is 'True'.
wrapIf :: (Builder -> Builder) -> Bool -> Builder -> Builder
wrapIf wrap True  = wrap
wrapIf _    False = id

-- | Optionally wrap an expression with braces
bracesIf :: Bool -> Builder -> Builder
bracesIf = wrapIf braces

-- | Optionally wrap an expression with brackets
bracketsIf :: Bool -> Builder -> Builder
bracketsIf = wrapIf brackets

-- | @renderDelimitedIf wrap f x@ will render @x@ as a 'Builder'. If
-- the condition @f x@ is @True@, the result will be modified using
-- the function @wrap@, otherwise the result will be returned
-- unmodified.
renderDelimitedIf :: Render a => (Builder -> Builder) -> (a -> Bool) -> a -> Builder
renderDelimitedIf wrap f x = (if f x then wrap else id) (render x)

--------------------------------------------------------------------------------
-- Usage

-- | A class for parsers that support generation of usage information.
class Usage (p :: Type -> Type) where

  -- | Generate nicely formatted usage information for a parser. This
  -- similar to 'showsPrec'.
  usagePrec :: Int -> p r -> Builder
  usagePrec _ = usage

  -- | Generate nicely formatted usage information for a parser.
  --
  -- Default implementation is equivalent to @usagePrec 0@.
  usage :: p r -> Builder
  usage = usagePrec 0