{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} -- | -- Printing nice and simple diffs of two values. -- -- @ -- import qualified Pretty.Diff as Diff -- import Data.Default (def) -- -- Diff.pretty def "1234" "_23" -- @ -- -- Will create a string that looks like this: -- -- @ -- ▼ ▼ -- "1234" -- ╷ -- │ -- ╵ -- "_23" -- ▲ -- @ module Pretty.Diff ( -- * Configuration Config (Config, separatorText, wrapping), Wrapping (Wrap, NoWrap), -- * pretty printing pretty, above, below, ) where import qualified Data.Algorithm.Diff as Diff import Data.Default (Default, def) import Data.Function ((&)) import Data.List (transpose) import Data.Maybe (fromMaybe, mapMaybe) import Data.String (IsString) import qualified Data.Text as Text import Data.Text (Text) import Prelude -- | Configuration for `Pretty.Diff.pretty`. data Config = Config { -- | Text that gets displayed inbetween the diffed values -- -- @ -- Diff.pretty def { Diff.separatorText = "differing" } "1234" "_23" -- @ -- -- Will create a string that looks like this: -- -- @ -- ▼ ▼ -- "1234" -- ╷ -- │ differing -- ╵ -- "_23" -- ▲ -- @ separatorText :: Maybe Text, -- | Wrapping text to multiple lines if they are longer than the provided length. -- This is useful in combination with [terminal-size](https://hackage.haskell.org/package/terminal-size). -- -- @ -- Diff.pretty def { Diff.wrapping = Diff.Wrap 6 } "0900000000" "9000000000" -- @ -- -- Will create a string that looks like this: -- -- @ -- ▼ -- "09000 -- 00000" -- ╷ -- │ -- ╵ -- "90000 -- 00000" -- ▲ -- @ wrapping :: Wrapping } instance Default Config where def = Config {separatorText = Nothing, wrapping = NoWrap} -- | Define whether or not to wrap the diffing lines. data Wrapping = Wrap Int | NoWrap -- | Printing a full diff of both values separated by some pipes. pretty :: Show a => Config -> a -> a -> Text pretty Config {separatorText, wrapping} x y = [ above wrapping x y, separator separatorText, below wrapping x y ] & mconcat -- | Printing The first value and the diff indicator above. -- -- @ -- Diff.above Diff.NoWrap "1234" "_23" -- @ -- -- @ -- ▼ ▼ -- "1234" -- @ above :: Show a => Wrapping -> a -> a -> Text above wrapping x y = wrap wrapping [diffLine First down x y, Text.pack (show x)] & filterEmptyLines & Text.unlines -- | Printing The second value and the diff indicator below. -- -- @ -- Diff.below Diff.NoWrap "1234" "_23" -- @ -- -- @ -- "_23" -- ▲ -- @ below :: Show a => Wrapping -> a -> a -> Text below wrapping x y = wrap wrapping [Text.pack (show y), diffLine Second up x y] & filterEmptyLines & Text.unlines wrap :: Wrapping -> [Text] -> [Text] wrap wrapping text = case wrapping of Wrap n -> text & fmap (Text.chunksOf n) & interleaveLists NoWrap -> text down :: Char down = '▼' up :: Char up = '▲' data Position = First | Second diffLine :: Show a => Position -> Char -> a -> a -> Text.Text diffLine pos differ a b = Diff.getDiff (show a) (show b) & mapMaybe (toDiffLine pos differ) & Text.pack & Text.stripEnd toDiffLine :: Position -> Char -> Diff.Diff a -> Maybe Char toDiffLine pos c d = case d of Diff.First _ -> case pos of First -> Just c Second -> Nothing Diff.Second _ -> case pos of First -> Nothing Second -> Just c Diff.Both _ _ -> Just ' ' separator :: Maybe Text -> Text separator maybeComparison = [ "╷", "│" <> (fromMaybe "" $ ((<>) " ") <$> maybeComparison), "╵" ] & Text.unlines interleaveLists :: [[a]] -> [a] interleaveLists = mconcat . transpose filterEmptyLines :: [Text] -> [Text] filterEmptyLines = filter (not . Text.null . Text.strip)