{-# 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 :: Text -> Maybe Int
prefixLength Text
line =
  if Text -> Bool
T.null Text
suffix
    then Maybe Int
forall a. Maybe a
Nothing
    else Int -> Maybe Int
forall a. a -> Maybe a
Just (Text -> Int
T.length Text
prefix)
  where
    (Text
prefix, Text
suffix) = HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
T.breakOn Text
"=" Text
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 :: Int -> Text -> Text
adjustLine Int
desiredPrefixLength Text
oldLine = Text
newLine
  where
    (Text
prefix, Text
suffix) = HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
T.breakOn Text
"=" Text
oldLine

    actualPrefixLength :: Int
actualPrefixLength = Text -> Int
T.length Text
prefix

    additionalSpaces :: Int
additionalSpaces = Int
desiredPrefixLength Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
actualPrefixLength

    spaces :: Text
spaces = Int -> Text -> Text
T.replicate Int
additionalSpaces Text
" "

    newLine :: Text
newLine = [Text] -> Text
T.concat [ Text
prefix, Text
spaces, Text
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.
--
-- >>> adjustText "key=value\na=b"
-- "key=value\na  =b"
-- >>> adjustText "x=y\nlong=var"
-- "x   =y\nlong=var"
adjustText
  :: Text
  -- ^ The input text (possibly multi-line)
  -> Text
  -- ^ The aligned text
adjustText :: Text -> Text
adjustText Text
oldText = Text
newText
  where
    oldLines :: [Text]
oldLines = Text -> [Text]
T.lines Text
oldText

    prefixLengths :: [Maybe Int]
prefixLengths = (Text -> Maybe Int) -> [Text] -> [Maybe Int]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Maybe Int
prefixLength [Text]
oldLines

    prefixLengths' :: [Int]
prefixLengths' = [Maybe Int] -> [Int]
forall a. [Maybe a] -> [a]
catMaybes [Maybe Int]
prefixLengths

    newLines :: [Text]
newLines =
      case [Int] -> Maybe Int
forall a. Ord a => [a] -> Maybe a
maximumMay [Int]
prefixLengths' of
        Maybe Int
Nothing ->
          []
        Just Int
desiredPrefixLength ->
          (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (Int -> Text -> Text
adjustLine Int
desiredPrefixLength) [Text]
oldLines

    newText :: Text
newText = [Text] -> Text
T.unlines [Text]
newLines