packages feed

align-equal-1.0.1.0: src/Data/Text/AlignEqual.hs

{-# LANGUAGE OverloadedStrings #-}

-- | A module providing functions for text alignment and padding.
module Data.Text.AlignEqual where

import           Data.Maybe (catMaybes)
import           Data.Text  (Text)
import qualified Data.Text  as T
import           Safe       (maximumMay)

-- | Calculates the number of characters preceding the first '=' sign in a text line.
-- If no '=' is found, returns `Nothing`. If '=' is found, returns `Just` the length of the prefix before it.
--
-- >>> prefixLength "key=value"
-- Just 3
-- >>> prefixLength "a=b"
-- Just 1
-- >>> prefixLength "noequals"
-- Nothing
prefixLength
  :: Text
  -- ^ The input text line
  -> Maybe Int
  -- ^ The number of characters before the first '=' sign, or `Nothing` if no '=' is found, or `Just` the length otherwise
prefixLength line =
  if T.null suffix
    then Nothing
    else Just (T.length prefix)
  where
    (prefix, suffix) = T.breakOn "=" line

-- | Adjusts the prefix of a text line to a desired length by adding padding spaces.
--
-- >>> adjustLine 5 "key=value"
-- "key  =value"
-- >>> adjustLine 3 "a=b"
-- "a  =b"
adjustLine
  :: Int
  -- ^ The desired prefix length
  -> Text
  -- ^ The text line to pad
  -> Text
  -- ^ The padded text line
adjustLine desiredPrefixLength oldLine = newLine
  where
    (prefix, suffix) = T.breakOn "=" oldLine

    actualPrefixLength = T.length prefix

    additionalSpaces = desiredPrefixLength - actualPrefixLength

    spaces = T.replicate additionalSpaces " "

    newLine = T.concat [ prefix, spaces, suffix ]

-- | Processes multi-line text to align all '=' signs across lines.
-- It adjusts the prefix length of each line to match the maximum prefix length.
-- If a line does not contain '=', it will be left as-is.
-- If the input is empty or contains no lines with '=', it is returned unchanged.
--
-- >>> adjustText "key=value\na=b"
-- "key=value\na  =b"
-- >>> adjustText "x=y\nlong=var"
-- "x   =y\nlong=var"
-- >>> adjustText ""
-- ""
adjustText
  :: Text
  -- ^ The input text (possibly multi-line)
  -> Text
  -- ^ The aligned text, or the original text if no '=' is found
adjustText oldText
  | T.null oldText = oldText
  | null newLines = oldText
  | otherwise = T.intercalate "\n" newLines
  where
    oldLines = T.lines oldText

    prefixLengths = map prefixLength oldLines

    prefixLengths' = catMaybes prefixLengths

    newLines =
      case maximumMay prefixLengths' of
        Nothing ->
          []
        Just desiredPrefixLength ->
          map (adjustLine desiredPrefixLength) oldLines