packages feed

xrefcheck-0.3.0: src/Xrefcheck/Util/Interpolate.hs

{- SPDX-FileCopyrightText: 2018-2019 Serokell <https://serokell.io>
 -
 - SPDX-License-Identifier: MPL-2.0
 -}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}

module Xrefcheck.Util.Interpolate
  ( -- $notes
    interpolateIndentF
  , interpolateBlockListF
  , interpolateBlockListF'
  , interpolateUnlinesF
  )
  where

import Universum

import Data.Text.Lazy qualified as TL
import Data.Text.Lazy.Builder (fromLazyText, toLazyText)
import Fmt (Buildable, Builder, blockListF, blockListF', indentF, unlinesF)

{- $notes
The `blockListF` and `indentF` frunctions from @fmt@ add a trailing newline, which makes them unsuitable for string interpolation.
Consider this case:
> [int||
> aaa
> #{indentF 2 "bbb"}
> ccc
> |]
One would reasonably expect this to produce:
> aaa
>   bbb
> ccc
But, in reality, it produces:
> aaa
>   bbb
>
> ccc
This module introduces versions of these functions that do not produce a trailing newline
and can therefore be safely used in string interpolation.
-}

{-# HLINT ignore "Avoid functions that generate extra trailing newlines/whitespaces" #-}

-- | Like @Fmt.indentF@, but strips trailing spaces and does not add a trailing newline.
--
-- >>> import Fmt
-- >>> indentF 2 "a\n\nb"
-- "  a\n  \n  b\n"
--
-- >>> interpolateIndentF 2 "a\n\nb"
-- "  a\n\n  b"
interpolateIndentF :: HasCallStack => Int -> Builder -> Builder
interpolateIndentF n b = (case TL.last (toLazyText b) of
  '\n' -> id
  _ ->  stripLastNewline) $ stripTrailingSpaces $ indentF n b
  -- strips newline added by indentF

-- | Like @Fmt.blockListF'@, but strips trailing spaces and does not add a trailing newline.
interpolateBlockListF' :: HasCallStack => Text -> (a -> Builder) -> NonEmpty a -> Builder
interpolateBlockListF' =  stripLastNewline . stripTrailingSpaces ... blockListF'

-- | Like @Fmt.blockListF@, but strips trailing spaces and does not add a trailing newline.
interpolateBlockListF :: HasCallStack => Buildable a => NonEmpty a -> Builder
interpolateBlockListF = stripLastNewline . stripTrailingSpaces . blockListF

-- | Like @Fmt.unlinesF@, but strips trailing spaces and does not add a trailing newline.
interpolateUnlinesF :: HasCallStack => Buildable a => NonEmpty a -> Builder
interpolateUnlinesF = stripLastNewline . stripTrailingSpaces . unlinesF

-- remove trailing whitespace from all lines.
-- Note: output always ends with newline (adds trailing newline if there wasn't one).
stripTrailingSpaces :: Builder -> Builder
stripTrailingSpaces
  = fromLazyText
  . TL.unlines
  . map (TL.stripEnd)
  . TL.lines
  . toLazyText

stripLastNewline :: HasCallStack => Builder -> Builder
stripLastNewline
  = fromLazyText
  . fromMaybe (error "stripLastNewline: expected newline to strip")
  . TL.stripSuffix "\n"
  . toLazyText