packages feed

cabal-gild-0.3.0.1: source/library/CabalGild/Type/Block.hs

-- | This module defines the 'Block' data type.
module CabalGild.Type.Block where

import qualified CabalGild.Type.Line as Line
import qualified Data.ByteString as ByteString
import qualified Distribution.Compat.Lens as Lens
import Prelude hiding (lines)

-- | A block of text, which is made up of multiple lines and can have blank
-- lines before and/or after it.
data Block = Block
  { -- | Does this block have a blank line before it?
    lineBefore :: Bool,
    lines :: [Line.Line],
    -- | Does this block have a blank line after it?
    lineAfter :: Bool
  }
  deriving (Eq, Show)

-- | Joins two blocks together by adding a blank line between them if
-- necessary. (A blank line is necessary if /either/ block needs a space.) If
-- either block is empty, the other block is returned.
instance Semigroup Block where
  x <> y =
    let s = [Line.empty | lineAfter x || lineBefore y]
        z =
          Block
            { lineBefore = lineBefore x,
              lines = lines x <> s <> lines y,
              lineAfter = lineAfter y
            }
     in if isEmpty x then y else if isEmpty y then x else z

-- | The empty block has no lines and also no blank lines before or after.
instance Monoid Block where
  mempty =
    Block
      { lineBefore = False,
        lines = [],
        lineAfter = False
      }

-- | Converts a single line into a block without blank lines before or after.
fromLine :: Line.Line -> Block
fromLine l = mempty {lines = [l]}

-- | Returns 'True' if the block has no lines.
isEmpty :: Block -> Bool
isEmpty = null . lines

-- | A lens for the 'lineAfter' field.
lineAfterLens :: Lens.Lens' Block Bool
lineAfterLens f s = fmap (\a -> s {lineAfter = a}) . f $ lineAfter s

-- | A lens for the 'lineBefore' field.
lineBeforeLens :: Lens.Lens' Block Bool
lineBeforeLens f s = fmap (\a -> s {lineBefore = a}) . f $ lineBefore s

-- | Converts a block into a 'ByteString.ByteString' by joining the lines
-- together with newline characters. Note that unline 'unlines', there will be
-- no trailing newline unless 'lineAfter' is 'True'.
toByteString :: Block -> ByteString.ByteString
toByteString b =
  if isEmpty b
    then ByteString.empty
    else
      ByteString.intercalate (ByteString.singleton 0x0a)
        . fmap Line.toByteString
        $ concat
          [ [Line.empty | lineBefore b],
            lines b,
            [Line.empty | lineAfter b]
          ]