gibberish-3.0.0.0: src/Data/Gibberish/Formatting.hs
module Data.Gibberish.Formatting
( FormatOpts (..),
MaxLen (..),
MaxHeight (..),
Separator (..),
ExactNumberWords (..),
formatWords,
formatLine,
) where
import Data.Gibberish.Types
import Data.List (intersperse)
import Data.Text (Text ())
import Data.Text qualified as Text
import Prelude hiding (Word ())
data FormatOpts = FormatOpts
{ optMaxLen :: MaxLen,
optMaxHeight :: MaxHeight,
optSeparator :: Separator,
optExactWords :: Maybe ExactNumberWords
}
deriving stock (Eq, Show)
newtype MaxLen = MaxLen {unMaxLen :: Int}
deriving stock (Eq, Show)
deriving newtype (Enum, Integral, Num, Ord, Real)
newtype MaxHeight = MaxHeight {unMaxHeight :: Int}
deriving stock (Eq, Show)
deriving newtype (Enum, Integral, Num, Ord, Real)
newtype Separator = Separator {unSeparator :: Text}
deriving stock (Eq, Show)
newtype ExactNumberWords = ExactNumberWords {unExactWords :: Int}
deriving stock (Eq, Show)
deriving newtype (Enum, Integral, Num, Ord, Real)
newtype FormatText = FormatText {fmtLines :: [FormatLine]}
deriving stock (Eq, Show)
data FormatLine = FormatLine
{ fmtSeparator :: Separator,
fmtWords :: [Word]
}
deriving stock (Eq, Show)
-- | Format a list of words to a text blob
formatWords :: FormatOpts -> [Word] -> Text
formatWords opts@FormatOpts {..} =
renderFormatText . take' . formatWords' opts
where
take' :: FormatText -> FormatText
take' =
case optExactWords of
Just (ExactNumberWords exact) -> takeWords exact
Nothing -> takeLines (unMaxHeight optMaxHeight)
-- | Turn a list of words into a Format description. Note that we completely
-- ignore maxHeight and exactWords, resulting in a potentially infinite list
formatWords' :: FormatOpts -> [Word] -> FormatText
formatWords' opts words' =
FormatText $
line : fmtLines (formatWords' opts restWords)
where
line = formatLine opts words'
-- TODO[sgillespie]: Benchmark this vs splitting
restWords = drop (length $ fmtWords line) words'
-- | Format a single line of words, up to maxLen characters
formatLine :: FormatOpts -> [Word] -> FormatLine
formatLine FormatOpts {..} =
FormatLine optSeparator
. map Word
. filter (/= unSeparator optSeparator)
. concatLine (unMaxLen optMaxLen)
. intersperse (unSeparator optSeparator)
. map unWord
where
concatLine :: Int -> [Text] -> [Text]
concatLine len (t : ts)
| len - Text.length t > 0 = t : concatLine (len - Text.length t) ts
| otherwise = []
concatLine _ [] = error "Ran out of words"
-- | Render a Format description into a Text blob
renderFormatText :: FormatText -> Text
renderFormatText (FormatText fmt) =
case fmt of
[] -> ""
l : ls -> renderFormatLine l <> "\n" <> renderFormatText' ls
where
renderFormatLine (FormatLine (Separator sep) ws) =
Text.concat . intersperse sep . map unWord $ ws
renderFormatText' ls = renderFormatText (FormatText ls)
takeLines :: Int -> FormatText -> FormatText
takeLines n (FormatText ls) = FormatText $ take n ls
takeWords :: Int -> FormatText -> FormatText
takeWords _ (FormatText []) = error "Ran out of words"
takeWords n (FormatText (l@(FormatLine sep ws) : ls))
| n >= length ws = FormatText $ l : ls'
| otherwise = FormatText [FormatLine sep (take n ws)]
where
(FormatText ls') = takeWords (n - length ws) (FormatText ls)