packages feed

git-mediate-1.0.6: src/Conflict.hs

{-# LANGUAGE FlexibleContexts, NoImplicitPrelude, RecordWildCards #-}

module Conflict
    ( Conflict(..), LineNo
    , bodyStrings, setBodyStrings
    , pretty, prettyLines
    , parse
    , markerPrefix
    ) where

import Control.Monad.State (MonadState, state, evalStateT)
import Control.Monad.Writer (runWriter, tell)
import Data.Functor.Identity (Identity(..))
import Data.List (isPrefixOf)

import Prelude.Compat

type LineNo = Int

data Conflict = Conflict
    { cMarkerA    :: (LineNo, String) -- <<<<<<<....
    , cMarkerBase :: (LineNo, String) -- |||||||....
    , cMarkerB    :: (LineNo, String) -- =======....
    , cMarkerEnd  :: (LineNo, String) -- >>>>>>>....
    , cBodyA      :: [String]
    , cBodyBase   :: [String]
    , cBodyB      :: [String]
    } deriving (Show)

-- traversal
bodyStrings :: Applicative f => (String -> f String) -> Conflict -> f Conflict
bodyStrings f c@Conflict{..} =
    mk <$> traverse f cBodyA <*> traverse f cBodyBase <*> traverse f cBodyB
    where
        mk bodyA bodyBase bodyB =
            c{cBodyA=bodyA, cBodyBase=bodyBase, cBodyB=bodyB}

-- setter:
setBodyStrings :: (String -> String) -> Conflict -> Conflict
setBodyStrings f = runIdentity . bodyStrings (Identity . f)

prettyLines :: Conflict -> [String]
prettyLines Conflict {..} =
    concat
    [ snd cMarkerA    : cBodyA
    , snd cMarkerBase : cBodyBase
    , snd cMarkerB    : cBodyB
    , [snd cMarkerEnd]
    ]

pretty :: Conflict -> String
pretty = unlines . prettyLines

-- '>' -> ">>>>>>>"
markerPrefix :: Char -> String
markerPrefix = replicate 7

breakUpToMarker :: MonadState [(LineNo, String)] m => Char -> m [(LineNo, String)]
breakUpToMarker c = state (break ((markerPrefix c `isPrefixOf`) . snd))

readHead :: MonadState [a] m => m (Maybe a)
readHead = state f
    where
        f [] = (Nothing, [])
        f (l:ls) = (Just l, ls)

tryReadUpToMarker :: MonadState [(LineNo, String)] m => Char -> m ([(LineNo, String)], Maybe (LineNo, String))
tryReadUpToMarker c =
    do
        ls <- breakUpToMarker c
        mHead <- readHead
        return (ls, mHead)

readUpToMarker :: MonadState [(LineNo, String)] m => Char -> m ([(LineNo, String)], (LineNo, String))
readUpToMarker c = do
    res <- tryReadUpToMarker c
    case res of
        (ls, Just h)  -> return (ls, h)
        (ls, Nothing) ->
            error $ concat
            [ "Parse error: failed reading up to marker: "
            , show c, ", got:"
            , concatMap (\(l,s) -> "\n" ++ show l ++ "\t" ++ s) $ take 5 ls
            ]

parseConflict :: MonadState [(LineNo, String)] m => (LineNo, String) -> m Conflict
parseConflict markerA =
    do  (linesA   , markerBase) <- readUpToMarker '|'
        (linesBase, markerB)    <- readUpToMarker '='
        (linesB   , markerEnd)  <- readUpToMarker '>'
        return Conflict
            { cMarkerA    = markerA
            , cMarkerBase = markerBase
            , cMarkerB    = markerB
            , cMarkerEnd  = markerEnd
            , cBodyA      = map snd linesA
            , cBodyB      = map snd linesB
            , cBodyBase   = map snd linesBase
            }

parseFromNumberedLines :: [(LineNo, String)] -> [Either String Conflict]
parseFromNumberedLines =
    snd . runWriter . evalStateT loop
    where
        loop =
            do  (ls, mMarkerA) <- tryReadUpToMarker '<'
                tell $ map (Left . snd) ls
                case mMarkerA of
                    Nothing -> return ()
                    Just markerA ->
                        do  tell . return . Right =<< parseConflict markerA
                            loop

parse :: String -> [Either String Conflict]
parse input =
    parseFromNumberedLines (zip [1..] (lines input))