    {-# LANGUAGE CPP #-}
    {-# LANGUAGE LambdaCase #-}
    {-# LANGUAGE ViewPatterns #-}
    module Test.Hspec.Core.Formatters.Diff (
      Diff (..)
    , diff

    , LineDiff(..)
    , lineDiff

    , splitLines

    #ifdef TEST
    , partition
    , breakList
    #endif
    ) where

    import           Prelude ()
    import           Test.Hspec.Core.Compat

    import           Data.Char
    import qualified Data.Algorithm.Diff as Diff

    data Diff = First String | Second String | Both String
      deriving (Diff -> Diff -> Bool
    (Diff -> Diff -> Bool) -> (Diff -> Diff -> Bool) -> Eq Diff
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: Diff -> Diff -> Bool
    == :: Diff -> Diff -> Bool
    $c/= :: Diff -> Diff -> Bool
    /= :: Diff -> Diff -> Bool
    Eq, Int -> Diff -> ShowS
    [Diff] -> ShowS
    Diff -> [Char]
    (Int -> Diff -> ShowS)
    -> (Diff -> [Char]) -> ([Diff] -> ShowS) -> Show Diff
    forall a.
    (Int -> a -> ShowS) -> (a -> [Char]) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> Diff -> ShowS
    showsPrec :: Int -> Diff -> ShowS
    $cshow :: Diff -> [Char]
    show :: Diff -> [Char]
    $cshowList :: [Diff] -> ShowS
    showList :: [Diff] -> ShowS
    Show)

    data LineDiff =
        LinesFirst [String]
      | LinesSecond [String]
      | LinesBoth [String]
      | SingleLineDiff [Diff]
      | LinesOmitted Int
      deriving (LineDiff -> LineDiff -> Bool
    (LineDiff -> LineDiff -> Bool)
    -> (LineDiff -> LineDiff -> Bool) -> Eq LineDiff
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: LineDiff -> LineDiff -> Bool
    == :: LineDiff -> LineDiff -> Bool
    $c/= :: LineDiff -> LineDiff -> Bool
    /= :: LineDiff -> LineDiff -> Bool
    Eq, Int -> LineDiff -> ShowS
    [LineDiff] -> ShowS
    LineDiff -> [Char]
    (Int -> LineDiff -> ShowS)
    -> (LineDiff -> [Char]) -> ([LineDiff] -> ShowS) -> Show LineDiff
    forall a.
    (Int -> a -> ShowS) -> (a -> [Char]) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> LineDiff -> ShowS
    showsPrec :: Int -> LineDiff -> ShowS
    $cshow :: LineDiff -> [Char]
    show :: LineDiff -> [Char]
    $cshowList :: [LineDiff] -> ShowS
    showList :: [LineDiff] -> ShowS
    Show)

    lineDiff :: Maybe Int -> String -> String -> [LineDiff]
    lineDiff :: Maybe Int -> [Char] -> [Char] -> [LineDiff]
    lineDiff Maybe Int
    context [Char]
    expected [Char]
    actual = ([LineDiff] -> [LineDiff])
    -> (Int -> [LineDiff] -> [LineDiff])
    -> Maybe Int
    -> [LineDiff]
    -> [LineDiff]
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe [LineDiff] -> [LineDiff]
    forall a. a -> a
    id Int -> [LineDiff] -> [LineDiff]
    applyContext Maybe Int
    context ([LineDiff] -> [LineDiff]) -> [LineDiff] -> [LineDiff]
    forall a b. (a -> b) -> a -> b
    $ [LineDiff] -> [LineDiff]
    singleLineDiffs [LineDiff]
    diffs
      where
        diffs :: [LineDiff]
        diffs :: [LineDiff]
    diffs = [[Char]] -> [[Char]] -> [Diff [[Char]]]
    forall t. Eq t => [t] -> [t] -> [Diff [t]]
    Diff.getGroupedDiff [[Char]]
    expectedLines [[Char]]
    actualLines [Diff [[Char]]] -> (Diff [[Char]] -> LineDiff) -> [LineDiff]
    forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
    <&> \ case
          Diff.First [[Char]]
    xs -> [[Char]] -> LineDiff
    LinesFirst [[Char]]
    xs
          Diff.Second [[Char]]
    xs -> [[Char]] -> LineDiff
    LinesSecond [[Char]]
    xs
          Diff.Both [[Char]]
    xs [[Char]]
    _ -> [[Char]] -> LineDiff
    LinesBoth [[Char]]
    xs

        expectedLines :: [String]
        expectedLines :: [[Char]]
    expectedLines = [Char] -> [[Char]]
    splitLines [Char]
    expected

        actualLines :: [String]
        actualLines :: [[Char]]
    actualLines = [Char] -> [[Char]]
    splitLines [Char]
    actual

    singleLineDiffs :: [LineDiff] -> [LineDiff]
    singleLineDiffs :: [LineDiff] -> [LineDiff]
    singleLineDiffs = [LineDiff] -> [LineDiff]
    go
      where
        go :: [LineDiff] -> [LineDiff]
    go = \ case
          [] -> []
          LinesFirst [[Char]
    first_] : LinesSecond [[Char]
    second_] : [LineDiff]
    xs -> [Diff] -> LineDiff
    SingleLineDiff ([Char] -> [Char] -> [Diff]
    diff [Char]
    first_ [Char]
    second_) LineDiff -> [LineDiff] -> [LineDiff]
    forall a. a -> [a] -> [a]
    : [LineDiff] -> [LineDiff]
    go [LineDiff]
    xs
          LineDiff
    x : [LineDiff]
    xs -> LineDiff
    x LineDiff -> [LineDiff] -> [LineDiff]
    forall a. a -> [a] -> [a]
    : [LineDiff] -> [LineDiff]
    go [LineDiff]
    xs

    splitLines :: String -> [String]
    splitLines :: [Char] -> [[Char]]
    splitLines = [Char] -> [[Char]]
    go
      where
        go :: String -> [String]
        go :: [Char] -> [[Char]]
    go [Char]
    xs = case (Char -> Bool) -> [Char] -> ([Char], [Char])
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    break (Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    == Char
    '\n') [Char]
    xs of
          ([Char]
    ys, Char
    '\n' : [Char]
    zs) -> [Char]
    ys [Char] -> [[Char]] -> [[Char]]
    forall a. a -> [a] -> [a]
    : [Char] -> [[Char]]
    go [Char]
    zs
          ([Char], [Char])
    _ -> [[Char]
    xs]

    data TrimMode = FirstChunk | Chunk | LastChunk

    applyContext :: Int -> [LineDiff] -> [LineDiff]
    applyContext :: Int -> [LineDiff] -> [LineDiff]
    applyContext Int
    context = \ [LineDiff]
    diffs -> case [LineDiff]
    diffs of
      [] -> []
      LineDiff
    x : [LineDiff]
    xs -> TrimMode -> LineDiff -> [LineDiff] -> [LineDiff]
    trimChunk TrimMode
    FirstChunk LineDiff
    x ([LineDiff] -> [LineDiff]
    go [LineDiff]
    xs)
      where
        omitThreshold :: Int
        omitThreshold :: Int
    omitThreshold = Int
    3

        go :: [LineDiff] -> [LineDiff]
        go :: [LineDiff] -> [LineDiff]
    go [LineDiff]
    diffs = case [LineDiff]
    diffs of
          [] -> []
          [LineDiff
    x] -> TrimMode -> LineDiff -> [LineDiff] -> [LineDiff]
    trimChunk TrimMode
    LastChunk LineDiff
    x []
          LineDiff
    x : [LineDiff]
    xs -> TrimMode -> LineDiff -> [LineDiff] -> [LineDiff]
    trimChunk TrimMode
    Chunk LineDiff
    x ([LineDiff] -> [LineDiff]
    go [LineDiff]
    xs)

        trimChunk :: TrimMode -> LineDiff -> [LineDiff] -> [LineDiff]
        trimChunk :: TrimMode -> LineDiff -> [LineDiff] -> [LineDiff]
    trimChunk TrimMode
    mode LineDiff
    chunk = case LineDiff
    chunk of
          LinesBoth [[Char]]
    allLines | Bool
    meetsThreshold -> [[Char]] -> [LineDiff] -> [LineDiff]
    keep [[Char]]
    start ([LineDiff] -> [LineDiff])
    -> ([LineDiff] -> [LineDiff]) -> [LineDiff] -> [LineDiff]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Int -> LineDiff
    LinesOmitted Int
    omitted LineDiff -> [LineDiff] -> [LineDiff]
    forall a. a -> [a] -> [a]
    :) ([LineDiff] -> [LineDiff])
    -> ([LineDiff] -> [LineDiff]) -> [LineDiff] -> [LineDiff]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [[Char]] -> [LineDiff] -> [LineDiff]
    keep [[Char]]
    end
            where
              meetsThreshold :: Bool
              meetsThreshold :: Bool
    meetsThreshold = Int
    omitThreshold Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    <= Int
    omitted

              lastLineHasNL :: Bool
    lastLineHasNL = case (TrimMode
    mode, [[Char]] -> [[Char]]
    forall a. [a] -> [a]
    reverse [[Char]]
    allLines) of
                (TrimMode
    LastChunk, [Char]
    "" : [[Char]]
    _) -> Bool
    True
                (TrimMode, [[Char]])
    _ -> Bool
    False

              omitted :: Int
              omitted :: Int
    omitted = Int
    n Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    keepStart Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    keepEnd

              keepStart :: Int
              keepStart :: Int
    keepStart = case TrimMode
    mode of
                TrimMode
    FirstChunk -> Int
    0
                TrimMode
    _ -> Int
    context

              keepEnd :: Int
              keepEnd :: Int
    keepEnd = case TrimMode
    mode of
                TrimMode
    LastChunk -> Int
    0
                TrimMode
    _ -> Int
    context

              n :: Int
              n :: Int
    n = [[Char]] -> Int
    forall a. [a] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length [[Char]]
    allLines Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - case Bool
    lastLineHasNL of
                Bool
    False -> Int
    0
                Bool
    True -> Int
    1

              start :: [String]
              start :: [[Char]]
    start = Int -> [[Char]] -> [[Char]]
    forall a. Int -> [a] -> [a]
    take Int
    keepStart [[Char]]
    allLines

              end :: [String]
              end :: [[Char]]
    end = Int -> [[Char]] -> [[Char]]
    forall a. Int -> [a] -> [a]
    drop (Int
    keepStart Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    omitted) [[Char]]
    allLines

          LineDiff
    _ -> (LineDiff
    chunk LineDiff -> [LineDiff] -> [LineDiff]
    forall a. a -> [a] -> [a]
    :)

    keep :: [String] -> [LineDiff] -> [LineDiff]
    keep :: [[Char]] -> [LineDiff] -> [LineDiff]
    keep [[Char]]
    xs
      | [[Char]] -> Bool
    forall a. [a] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null [[Char]]
    xs = [LineDiff] -> [LineDiff]
    forall a. a -> a
    id
      | Bool
    otherwise = ([[Char]] -> LineDiff
    LinesBoth [[Char]]
    xs LineDiff -> [LineDiff] -> [LineDiff]
    forall a. a -> [a] -> [a]
    :)

    diff :: String -> String -> [Diff]
    diff :: [Char] -> [Char] -> [Diff]
    diff [Char]
    expected [Char]
    actual = [Diff]
    diffs
      where
        diffs :: [Diff]
        diffs :: [Diff]
    diffs = (Diff [[Char]] -> Diff) -> [Diff [[Char]]] -> [Diff]
    forall a b. (a -> b) -> [a] -> [b]
    map (Diff [Char] -> Diff
    toDiff (Diff [Char] -> Diff)
    -> (Diff [[Char]] -> Diff [Char]) -> Diff [[Char]] -> Diff
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ([[Char]] -> [Char]) -> Diff [[Char]] -> Diff [Char]
    forall a b. (a -> b) -> Diff a -> Diff b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap [[Char]] -> [Char]
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
    concat) ([Diff [[Char]]] -> [Diff]) -> [Diff [[Char]]] -> [Diff]
    forall a b. (a -> b) -> a -> b
    $ [[Char]] -> [[Char]] -> [Diff [[Char]]]
    forall t. Eq t => [t] -> [t] -> [Diff [t]]
    Diff.getGroupedDiff [[Char]]
    expectedChunks [[Char]]
    actualChunks

        expectedChunks :: [String]
        expectedChunks :: [[Char]]
    expectedChunks = [Char] -> [[Char]]
    partition [Char]
    expected

        actualChunks :: [String]
        actualChunks :: [[Char]]
    actualChunks = [Char] -> [[Char]]
    partition [Char]
    actual

    toDiff :: Diff.Diff String -> Diff
    toDiff :: Diff [Char] -> Diff
    toDiff Diff [Char]
    d = case Diff [Char]
    d of
      Diff.First [Char]
    xs -> [Char] -> Diff
    First [Char]
    xs
      Diff.Second [Char]
    xs -> [Char] -> Diff
    Second [Char]
    xs
      Diff.Both [Char]
    xs [Char]
    _ -> [Char] -> Diff
    Both [Char]
    xs

    partition :: String -> [String]
    partition :: [Char] -> [[Char]]
    partition = ([Char] -> Bool) -> [[Char]] -> [[Char]]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Bool -> Bool
    not (Bool -> Bool) -> ([Char] -> Bool) -> [Char] -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [Char] -> Bool
    forall a. [a] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null) ([[Char]] -> [[Char]])
    -> ([Char] -> [[Char]]) -> [Char] -> [[Char]]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [[Char]] -> [[Char]]
    mergeBackslashes ([[Char]] -> [[Char]])
    -> ([Char] -> [[Char]]) -> [Char] -> [[Char]]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Char -> Bool) -> [Char] -> [[Char]]
    forall a. (a -> Bool) -> [a] -> [[a]]
    breakList Char -> Bool
    isAlphaNum
      where
        mergeBackslashes :: [String] -> [String]
        mergeBackslashes :: [[Char]] -> [[Char]]
    mergeBackslashes [[Char]]
    xs = case [[Char]]
    xs of
          [Char
    '\\'] : ([Char] -> Maybe ([Char], [Char])
    splitEscape -> Just ([Char]
    escape, [Char]
    ys)) : [[Char]]
    zs -> ([Char]
    "\\" [Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ [Char]
    escape) [Char] -> [[Char]] -> [[Char]]
    forall a. a -> [a] -> [a]
    : [Char]
    ys [Char] -> [[Char]] -> [[Char]]
    forall a. a -> [a] -> [a]
    : [[Char]] -> [[Char]]
    mergeBackslashes [[Char]]
    zs
          [Char]
    z : [[Char]]
    zs -> [Char]
    z [Char] -> [[Char]] -> [[Char]]
    forall a. a -> [a] -> [a]
    : [[Char]] -> [[Char]]
    mergeBackslashes [[Char]]
    zs
          [] -> []

    breakList :: (a -> Bool) -> [a] -> [[a]]
    breakList :: forall a. (a -> Bool) -> [a] -> [[a]]
    breakList a -> Bool
    _ [] = []
    breakList a -> Bool
    p [a]
    xs = case (a -> Bool) -> [a] -> ([a], [a])
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    break a -> Bool
    p [a]
    xs of
      ([a]
    y, [a]
    ys) -> (a -> [a]) -> [a] -> [[a]]
    forall a b. (a -> b) -> [a] -> [b]
    map a -> [a]
    forall a. a -> [a]
    forall (m :: * -> *) a. Monad m => a -> m a
    return [a]
    y [[a]] -> [[a]] -> [[a]]
    forall a. [a] -> [a] -> [a]
    ++ case (a -> Bool) -> [a] -> ([a], [a])
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    span a -> Bool
    p [a]
    ys of
        ([a]
    z, [a]
    zs) -> [a]
    z [a] -> [[a]] -> [[a]]
    forall {t :: * -> *} {a}. Foldable t => t a -> [t a] -> [t a]
    `cons` (a -> Bool) -> [a] -> [[a]]
    forall a. (a -> Bool) -> [a] -> [[a]]
    breakList a -> Bool
    p [a]
    zs
      where
        cons :: t a -> [t a] -> [t a]
    cons t a
    x
          | t a -> Bool
    forall a. t a -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null t a
    x = [t a] -> [t a]
    forall a. a -> a
    id
          | Bool
    otherwise = (t a
    x t a -> [t a] -> [t a]
    forall a. a -> [a] -> [a]
    :)

    splitEscape :: String -> Maybe (String, String)
    splitEscape :: [Char] -> Maybe ([Char], [Char])
    splitEscape [Char]
    xs = [Char] -> Maybe ([Char], [Char])
    splitNumericEscape [Char]
    xs Maybe ([Char], [Char])
    -> Maybe ([Char], [Char]) -> Maybe ([Char], [Char])
    forall a. Maybe a -> Maybe a -> Maybe a
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> [Maybe ([Char], [Char])] -> Maybe ([Char], [Char])
    forall (t :: * -> *) (m :: * -> *) a.
    (Foldable t, MonadPlus m) =>
    t (m a) -> m a
    msum (([Char] -> Maybe ([Char], [Char]))
    -> [[Char]] -> [Maybe ([Char], [Char])]
    forall a b. (a -> b) -> [a] -> [b]
    map [Char] -> Maybe ([Char], [Char])
    split [[Char]]
    escapes)
      where
        split :: String -> Maybe (String, String)
        split :: [Char] -> Maybe ([Char], [Char])
    split [Char]
    escape = (,) [Char]
    escape ([Char] -> ([Char], [Char]))
    -> Maybe [Char] -> Maybe ([Char], [Char])
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> [Char] -> [Char] -> Maybe [Char]
    forall a. Eq a => [a] -> [a] -> Maybe [a]
    stripPrefix [Char]
    escape [Char]
    xs

    splitNumericEscape :: String -> Maybe (String, String)
    splitNumericEscape :: [Char] -> Maybe ([Char], [Char])
    splitNumericEscape [Char]
    xs = case (Char -> Bool) -> [Char] -> ([Char], [Char])
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    span Char -> Bool
    isDigit [Char]
    xs of
      ([Char]
    "", [Char]
    _) -> Maybe ([Char], [Char])
    forall a. Maybe a
    Nothing
      ([Char], [Char])
    r -> ([Char], [Char]) -> Maybe ([Char], [Char])
    forall a. a -> Maybe a
    Just ([Char], [Char])
    r

    escapes :: [String]
    escapes :: [[Char]]
    escapes = [
        [Char]
    "ACK"
      , [Char]
    "CAN"
      , [Char]
    "DC1"
      , [Char]
    "DC2"
      , [Char]
    "DC3"
      , [Char]
    "DC4"
      , [Char]
    "DEL"
      , [Char]
    "DLE"
      , [Char]
    "ENQ"
      , [Char]
    "EOT"
      , [Char]
    "ESC"
      , [Char]
    "ETB"
      , [Char]
    "ETX"
      , [Char]
    "NAK"
      , [Char]
    "NUL"
      , [Char]
    "SOH"
      , [Char]
    "STX"
      , [Char]
    "SUB"
      , [Char]
    "SYN"
      , [Char]
    "EM"
      , [Char]
    "FS"
      , [Char]
    "GS"
      , [Char]
    "RS"
      , [Char]
    "SI"
      , [Char]
    "SO"
      , [Char]
    "US"
      , [Char]
    "a"
      , [Char]
    "b"
      , [Char]
    "f"
      , [Char]
    "n"
      , [Char]
    "r"
      , [Char]
    "t"
      , [Char]
    "v"
      , [Char]
    "&"
      , [Char]
    "'"
      , [Char]
    "\""
      , [Char]
    "\\"
      ]