{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE MultiWayIf #-}
module Darcs.Patch.Prim.V1.Apply () where

import Darcs.Prelude

import Control.Exception ( throw )

import Darcs.Patch.Apply ( Apply(..) )
import Darcs.Patch.Repair ( RepairToFL(..) )

import Darcs.Patch.Prim.Class ( PrimApply(..) )
import Darcs.Patch.Prim.V1.Core
    ( Prim(..),
      DirPatchType(..), FilePatchType(..) )
import Darcs.Patch.Prim.V1.Show ( showHunk )

import Darcs.Util.Path ( AnchoredPath, anchorPath )
import Darcs.Patch.Format ( FileNameFormat(FileNameFormatDisplay) )
import Darcs.Patch.TokenReplace ( tryTokReplace )

import Darcs.Patch.ApplyMonad ( ApplyMonadTree(..) )
import Darcs.Util.Tree( Tree )

import Darcs.Patch.Witnesses.Ordered ( FL(..), mapFL_FL, spanFL, (:>)(..) )
import Darcs.Patch.Witnesses.Unsafe ( unsafeCoercePStart )

import Darcs.Util.ByteString ( unlinesPS )
import Darcs.Util.Printer( renderString )

import qualified Data.ByteString as B
    ( ByteString
    , drop
    , empty
    , null
    , concat
    , isPrefixOf
    , length
    , splitAt
    )
import qualified Data.ByteString.Char8 as BC (pack, unpack, unlines, elemIndices)

type FileContents = B.ByteString

ap2fp :: AnchoredPath -> FilePath
ap2fp :: AnchoredPath -> String
ap2fp = String -> AnchoredPath -> String
anchorPath String
""

instance Apply Prim where
    type ApplyState Prim = Tree
    apply :: forall (m :: * -> *) wX wY.
ApplyMonad (ApplyState Prim) m =>
Prim wX wY -> m ()
apply (FP AnchoredPath
f FilePatchType wX wY
RmFile) = forall (m :: * -> *). ApplyMonadTree m => AnchoredPath -> m ()
mRemoveFile AnchoredPath
f
    apply (FP AnchoredPath
f FilePatchType wX wY
AddFile) = forall (m :: * -> *). ApplyMonadTree m => AnchoredPath -> m ()
mCreateFile AnchoredPath
f
    apply (FP AnchoredPath
f (Hunk Int
l [FileContents]
o [FileContents]
n)) = forall (m :: * -> *).
ApplyMonadTree m =>
AnchoredPath -> (FileContents -> m FileContents) -> m ()
mModifyFilePS AnchoredPath
f forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
Monad m =>
AnchoredPath
-> (Int, [FileContents], [FileContents])
-> FileContents
-> m FileContents
applyHunk AnchoredPath
f (Int
l, [FileContents]
o, [FileContents]
n)
    apply (FP AnchoredPath
f (TokReplace String
t String
o String
n)) = forall (m :: * -> *).
ApplyMonadTree m =>
AnchoredPath -> (FileContents -> m FileContents) -> m ()
mModifyFilePS AnchoredPath
f forall {m :: * -> *}. Monad m => FileContents -> m FileContents
doreplace
        where doreplace :: FileContents -> m FileContents
doreplace FileContents
fc =
                  case String
-> FileContents
-> FileContents
-> FileContents
-> Maybe FileContents
tryTokReplace String
t (String -> FileContents
BC.pack String
o) (String -> FileContents
BC.pack String
n) FileContents
fc of
                  Maybe FileContents
Nothing -> forall a e. Exception e => e -> a
throw forall a b. (a -> b) -> a -> b
$ String -> IOError
userError forall a b. (a -> b) -> a -> b
$ String
"replace patch to " forall a. [a] -> [a] -> [a]
++ AnchoredPath -> String
ap2fp AnchoredPath
f
                             forall a. [a] -> [a] -> [a]
++ String
" couldn't apply."
                  Just FileContents
fc' -> forall (m :: * -> *) a. Monad m => a -> m a
return FileContents
fc'
    apply (FP AnchoredPath
f (Binary FileContents
o FileContents
n)) = forall (m :: * -> *).
ApplyMonadTree m =>
AnchoredPath -> (FileContents -> m FileContents) -> m ()
mModifyFilePS AnchoredPath
f forall {m :: * -> *}. Monad m => FileContents -> m FileContents
doapply
        where doapply :: FileContents -> m FileContents
doapply FileContents
oldf = if FileContents
o forall a. Eq a => a -> a -> Bool
== FileContents
oldf
                             then forall (m :: * -> *) a. Monad m => a -> m a
return FileContents
n
                             else forall a e. Exception e => e -> a
throw forall a b. (a -> b) -> a -> b
$ String -> IOError
userError
                                  forall a b. (a -> b) -> a -> b
$ String
"binary patch to " forall a. [a] -> [a] -> [a]
++ AnchoredPath -> String
ap2fp AnchoredPath
f
                                  forall a. [a] -> [a] -> [a]
++ String
" couldn't apply."
    apply (DP AnchoredPath
d DirPatchType wX wY
AddDir) = forall (m :: * -> *). ApplyMonadTree m => AnchoredPath -> m ()
mCreateDirectory AnchoredPath
d
    apply (DP AnchoredPath
d DirPatchType wX wY
RmDir) = forall (m :: * -> *). ApplyMonadTree m => AnchoredPath -> m ()
mRemoveDirectory AnchoredPath
d
    apply (Move AnchoredPath
f AnchoredPath
f') = forall (m :: * -> *).
ApplyMonadTree m =>
AnchoredPath -> AnchoredPath -> m ()
mRename AnchoredPath
f AnchoredPath
f'
    apply (ChangePref String
p String
f String
t) = forall (m :: * -> *).
ApplyMonadTree m =>
String -> String -> String -> m ()
mChangePref String
p String
f String
t

instance RepairToFL Prim where
    applyAndTryToFixFL :: forall (m :: * -> *) wX wY.
ApplyMonad (ApplyState Prim) m =>
Prim wX wY -> m (Maybe (String, FL Prim wX wY))
applyAndTryToFixFL (FP AnchoredPath
f FilePatchType wX wY
RmFile) =
        do FileContents
x <- forall (m :: * -> *).
ApplyMonadTree m =>
AnchoredPath -> m FileContents
mReadFilePS AnchoredPath
f
           forall (m :: * -> *). ApplyMonadTree m => AnchoredPath -> m ()
mRemoveFile AnchoredPath
f
           forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ if FileContents -> Bool
B.null FileContents
x
                        then forall a. Maybe a
Nothing
                        else forall a. a -> Maybe a
Just (String
"WARNING: Fixing removal of non-empty file "forall a. [a] -> [a] -> [a]
++AnchoredPath -> String
ap2fp AnchoredPath
f,
                                   -- No need to coerce because the content
                                   -- removal patch has freely decided contexts
                                   forall wX wY. AnchoredPath -> FilePatchType wX wY -> Prim wX wY
FP AnchoredPath
f (forall wX wY. FileContents -> FileContents -> FilePatchType wX wY
Binary FileContents
x FileContents
B.empty) forall (a :: * -> * -> *) wX wY wZ.
a wX wY -> FL a wY wZ -> FL a wX wZ
:>: forall wX wY. AnchoredPath -> FilePatchType wX wY -> Prim wX wY
FP AnchoredPath
f forall wX wY. FilePatchType wX wY
RmFile forall (a :: * -> * -> *) wX wY wZ.
a wX wY -> FL a wY wZ -> FL a wX wZ
:>: forall (a :: * -> * -> *) wX. FL a wX wX
NilFL )
    applyAndTryToFixFL (FP AnchoredPath
f FilePatchType wX wY
AddFile) =
        do Bool
exists <- forall (m :: * -> *). ApplyMonadTree m => AnchoredPath -> m Bool
mDoesFileExist AnchoredPath
f
           if Bool
exists
             then forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$
                     forall a. a -> Maybe a
Just (String
"WARNING: Dropping add of existing file "forall a. [a] -> [a] -> [a]
++AnchoredPath -> String
ap2fp AnchoredPath
f,
                           -- the old context was wrong, so we have to coerce
                           forall (a :: * -> * -> *) wX1 wY wX2. a wX1 wY -> a wX2 wY
unsafeCoercePStart forall (a :: * -> * -> *) wX. FL a wX wX
NilFL
                          )
             else do forall (m :: * -> *). ApplyMonadTree m => AnchoredPath -> m ()
mCreateFile AnchoredPath
f
                     forall (m :: * -> *) a. Monad m => a -> m a
return forall a. Maybe a
Nothing
    applyAndTryToFixFL (DP AnchoredPath
f DirPatchType wX wY
AddDir) =
        do Bool
exists <- forall (m :: * -> *). ApplyMonadTree m => AnchoredPath -> m Bool
mDoesDirectoryExist AnchoredPath
f
           if Bool
exists
             then forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$
                     forall a. a -> Maybe a
Just (String
"WARNING: Dropping add of existing directory "forall a. [a] -> [a] -> [a]
++AnchoredPath -> String
ap2fp AnchoredPath
f,
                           -- the old context was wrong, so we have to coerce
                           forall (a :: * -> * -> *) wX1 wY wX2. a wX1 wY -> a wX2 wY
unsafeCoercePStart forall (a :: * -> * -> *) wX. FL a wX wX
NilFL
                          )
             else do forall (m :: * -> *). ApplyMonadTree m => AnchoredPath -> m ()
mCreateDirectory AnchoredPath
f
                     forall (m :: * -> *) a. Monad m => a -> m a
return forall a. Maybe a
Nothing
    applyAndTryToFixFL (FP AnchoredPath
f (Binary FileContents
old FileContents
new)) =
        do FileContents
x <- forall (m :: * -> *).
ApplyMonadTree m =>
AnchoredPath -> m FileContents
mReadFilePS AnchoredPath
f
           forall (m :: * -> *).
ApplyMonadTree m =>
AnchoredPath -> (FileContents -> m FileContents) -> m ()
mModifyFilePS AnchoredPath
f (\FileContents
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return FileContents
new)
           if FileContents
x forall a. Eq a => a -> a -> Bool
/= FileContents
old
             then forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$
                     forall a. a -> Maybe a
Just (String
"WARNING: Fixing binary patch to "forall a. [a] -> [a] -> [a]
++AnchoredPath -> String
ap2fp AnchoredPath
f,
                           forall wX wY. AnchoredPath -> FilePatchType wX wY -> Prim wX wY
FP AnchoredPath
f (forall wX wY. FileContents -> FileContents -> FilePatchType wX wY
Binary FileContents
x FileContents
new) forall (a :: * -> * -> *) wX wY wZ.
a wX wY -> FL a wY wZ -> FL a wX wZ
:>: forall (a :: * -> * -> *) wX. FL a wX wX
NilFL
                          )
             else forall (m :: * -> *) a. Monad m => a -> m a
return forall a. Maybe a
Nothing
    applyAndTryToFixFL p :: Prim wX wY
p@(Move AnchoredPath
old AnchoredPath
new) =
        do Bool
old_is_file <- forall (m :: * -> *). ApplyMonadTree m => AnchoredPath -> m Bool
mDoesFileExist AnchoredPath
old
           Bool
old_is_dir <- forall (m :: * -> *). ApplyMonadTree m => AnchoredPath -> m Bool
mDoesDirectoryExist AnchoredPath
old
           Bool
new_is_file <- forall (m :: * -> *). ApplyMonadTree m => AnchoredPath -> m Bool
mDoesFileExist AnchoredPath
new
           Bool
new_is_dir <- forall (m :: * -> *). ApplyMonadTree m => AnchoredPath -> m Bool
mDoesDirectoryExist AnchoredPath
new
           if | Bool -> Bool
not (Bool
old_is_file Bool -> Bool -> Bool
|| Bool
old_is_dir) ->
                  forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$
                     forall a. a -> Maybe a
Just (String
"WARNING: Dropping move patch with non-existing source "forall a. [a] -> [a] -> [a]
++AnchoredPath -> String
ap2fp AnchoredPath
old,
                           forall (a :: * -> * -> *) wX1 wY wX2. a wX1 wY -> a wX2 wY
unsafeCoercePStart forall (a :: * -> * -> *) wX. FL a wX wX
NilFL
                          )
              | Bool
new_is_file Bool -> Bool -> Bool
|| Bool
new_is_dir ->
                  forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$
                     forall a. a -> Maybe a
Just (String
"WARNING: Dropping move patch with existing target "forall a. [a] -> [a] -> [a]
++AnchoredPath -> String
ap2fp AnchoredPath
old,
                           forall (a :: * -> * -> *) wX1 wY wX2. a wX1 wY -> a wX2 wY
unsafeCoercePStart forall (a :: * -> * -> *) wX. FL a wX wX
NilFL
                          )
              | Bool
otherwise -> forall (p :: * -> * -> *) (m :: * -> *) wX wY.
(Apply p, ApplyMonad (ApplyState p) m) =>
p wX wY -> m ()
apply Prim wX wY
p forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (m :: * -> *) a. Monad m => a -> m a
return forall a. Maybe a
Nothing
    applyAndTryToFixFL Prim wX wY
p = forall (p :: * -> * -> *) (m :: * -> *) wX wY.
(Apply p, ApplyMonad (ApplyState p) m) =>
p wX wY -> m ()
apply Prim wX wY
p forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (m :: * -> *) a. Monad m => a -> m a
return forall a. Maybe a
Nothing

instance PrimApply Prim where
    applyPrimFL :: forall (m :: * -> *) wX wY.
ApplyMonad (ApplyState Prim) m =>
FL Prim wX wY -> m ()
applyPrimFL FL Prim wX wY
NilFL = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    applyPrimFL (FP AnchoredPath
f h :: FilePatchType wX wY
h@(Hunk{}):>:FL Prim wY wY
the_ps)
     = case forall (a :: * -> * -> *) wX wZ.
(forall wW wY. a wW wY -> Bool)
-> FL a wX wZ -> (:>) (FL a) (FL a) wX wZ
spanFL forall {wX} {wY}. Prim wX wY -> Bool
f_hunk FL Prim wY wY
the_ps of
           (FL Prim wY wZ
xs :> FL Prim wZ wY
ps') ->
               do let foo :: FL FilePatchType wX wZ
foo = FilePatchType wX wY
h forall (a :: * -> * -> *) wX wY wZ.
a wX wY -> FL a wY wZ -> FL a wX wZ
:>: forall (a :: * -> * -> *) (b :: * -> * -> *) wX wZ.
(forall wW wY. a wW wY -> b wW wY) -> FL a wX wZ -> FL b wX wZ
mapFL_FL (\(FP AnchoredPath
_ FilePatchType wW wY
h') -> FilePatchType wW wY
h') FL Prim wY wZ
xs
                  forall (m :: * -> *).
ApplyMonadTree m =>
AnchoredPath -> (FileContents -> m FileContents) -> m ()
mModifyFilePS AnchoredPath
f forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) wX wY.
Monad m =>
FL FilePatchType wX wY -> FileContents -> m FileContents
hunkmod FL FilePatchType wX wZ
foo
                  forall (prim :: * -> * -> *) (m :: * -> *) wX wY.
(PrimApply prim, ApplyMonad (ApplyState prim) m) =>
FL prim wX wY -> m ()
applyPrimFL FL Prim wZ wY
ps'
        where f_hunk :: Prim wX wY -> Bool
f_hunk (FP AnchoredPath
f' (Hunk{})) = AnchoredPath
f forall a. Eq a => a -> a -> Bool
== AnchoredPath
f'
              f_hunk Prim wX wY
_ = Bool
False
              -- TODO there should be a HOF that abstracts
              -- over this recursion scheme
              hunkmod :: Monad m => FL FilePatchType wX wY
                      -> B.ByteString -> m B.ByteString
              hunkmod :: forall (m :: * -> *) wX wY.
Monad m =>
FL FilePatchType wX wY -> FileContents -> m FileContents
hunkmod FL FilePatchType wX wY
NilFL FileContents
content = forall (m :: * -> *) a. Monad m => a -> m a
return FileContents
content
              hunkmod (Hunk Int
line [FileContents]
old [FileContents]
new:>:FL FilePatchType wY wY
hs) FileContents
content =
                  forall (m :: * -> *).
Monad m =>
AnchoredPath
-> (Int, [FileContents], [FileContents])
-> FileContents
-> m FileContents
applyHunk AnchoredPath
f (Int
line, [FileContents]
old, [FileContents]
new) FileContents
content forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= forall (m :: * -> *) wX wY.
Monad m =>
FL FilePatchType wX wY -> FileContents -> m FileContents
hunkmod FL FilePatchType wY wY
hs
              hunkmod FL FilePatchType wX wY
_ FileContents
_ = forall a. HasCallStack => String -> a
error String
"impossible case"
    applyPrimFL (Prim wX wY
p:>:FL Prim wY wY
ps) = forall (p :: * -> * -> *) (m :: * -> *) wX wY.
(Apply p, ApplyMonad (ApplyState p) m) =>
p wX wY -> m ()
apply Prim wX wY
p forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (prim :: * -> * -> *) (m :: * -> *) wX wY.
(PrimApply prim, ApplyMonad (ApplyState prim) m) =>
FL prim wX wY -> m ()
applyPrimFL FL Prim wY wY
ps

applyHunk :: Monad m
          => AnchoredPath
          -> (Int, [B.ByteString], [B.ByteString])
          -> FileContents
          -> m FileContents
applyHunk :: forall (m :: * -> *).
Monad m =>
AnchoredPath
-> (Int, [FileContents], [FileContents])
-> FileContents
-> m FileContents
applyHunk AnchoredPath
f (Int, [FileContents], [FileContents])
h FileContents
fc =
  case (Int, [FileContents], [FileContents])
-> FileContents -> Either String FileContents
applyHunkLines (Int, [FileContents], [FileContents])
h FileContents
fc of
    Right FileContents
fc' -> forall (m :: * -> *) a. Monad m => a -> m a
return FileContents
fc'
    Left String
msg ->
      forall a e. Exception e => e -> a
throw forall a b. (a -> b) -> a -> b
$ String -> IOError
userError forall a b. (a -> b) -> a -> b
$
      String
"### Error applying:\n" forall a. [a] -> [a] -> [a]
++ (Int, [FileContents], [FileContents]) -> String
renderHunk (Int, [FileContents], [FileContents])
h forall a. [a] -> [a] -> [a]
++
      String
"\n### to file " forall a. [a] -> [a] -> [a]
++ AnchoredPath -> String
ap2fp AnchoredPath
f forall a. [a] -> [a] -> [a]
++ String
":\n" forall a. [a] -> [a] -> [a]
++ FileContents -> String
BC.unpack FileContents
fc forall a. [a] -> [a] -> [a]
++
      String
"### Reason: " forall a. [a] -> [a] -> [a]
++ String
msg
  where
    renderHunk :: (Int, [FileContents], [FileContents]) -> String
renderHunk (Int
l, [FileContents]
o, [FileContents]
n) = Doc -> String
renderString (FileNameFormat
-> AnchoredPath -> Int -> [FileContents] -> [FileContents] -> Doc
showHunk FileNameFormat
FileNameFormatDisplay AnchoredPath
f Int
l [FileContents]
o [FileContents]
n)

{- The way darcs handles newlines is not easy to understand.

Everything seems pretty logical and conventional as long as files end in a
newline. In this case, the lines in a hunk can be regarded as newline
terminated, too. However, this view breaks down if we consider files that
are not newline terminated.

Here is a different view that covers the general case and explains,
conceptually, the algorithm below.

* Ever line (in a hunk or file) is regarded as being /preceded/ by a newline
  character.

* Every file starts out containing a single newline character, that is, a
  single empty line. A first empty line at the start of a file (if present)
  is /invisible/.

* When lines are appended to a file by a hunk, they are inserted /before/ a
  final empty line, if there is one. This results in a file that remains
  being terminated by a newline.

* In particular, when we start with an empty file and add a line, we push
  the invisible newline back, making it visible, and the newline that
  initiates our new content becomes invisible instead. This results in a
  newline terminated file, as above.

* However, if there is a newline at the end of a file (remember that this
  includes the case of an empty file), a hunk can /remove/ it by removing an
  empty line before adding anything. This results in a file that is /not/
  newline terminated.

The invisible newline character at the front is, of course, not present
anywhere in the representation of files, it is just a conceptual tool.

The algorithm below is highly optimized to minimize allocation of
intermediate ByteStrings. -}

applyHunkLines :: (Int, [B.ByteString], [B.ByteString])
               -> FileContents
               -> Either String FileContents
applyHunkLines :: (Int, [FileContents], [FileContents])
-> FileContents -> Either String FileContents
applyHunkLines (Int
line, [FileContents]
old, [FileContents]
new) FileContents
content
  | Int
line forall a. Eq a => a -> a -> Bool
== Int
1 =
      {- This case is subtle because here we have to deal with any invisible
      newline at the front of a file without it actually being present. We
      first try to drop everything up to the (length old)'th newline. 

      If this fails, we know that the content was not newline terminated. So
      we replace everything with the new content, interspersing but not
      terminating the lines with newline characters.

      If it succeeds, we insert the new content, interspersing /and/
      terminating the lines with newline characters before appending the
      rest of the content. -}
      case Int -> FileContents -> Maybe (FileContents, FileContents)
breakAfterNthNewline (forall (t :: * -> *) a. Foldable t => t a -> Int
length [FileContents]
old) FileContents
content of
        Maybe (FileContents, FileContents)
Nothing
          -- old content is not newline terminated
          | FileContents
content forall a. Eq a => a -> a -> Bool
== [FileContents] -> FileContents
unlinesPS [FileContents]
old -> forall a b. b -> Either a b
Right forall a b. (a -> b) -> a -> b
$ [FileContents] -> FileContents
unlinesPS [FileContents]
new
          | Bool
otherwise -> forall a b. a -> Either a b
Left String
"Hunk wants to remove content that isn't there"
        Just (FileContents
should_be_old, FileContents
suffix)
          -- old content is newline terminated
          | FileContents
should_be_old forall a. Eq a => a -> a -> Bool
== [FileContents] -> FileContents
BC.unlines [FileContents]
old ->
              forall a b. b -> Either a b
Right forall a b. (a -> b) -> a -> b
$ [FileContents] -> FileContents
unlinesPS forall a b. (a -> b) -> a -> b
$ [FileContents]
new forall a. [a] -> [a] -> [a]
++ [FileContents
suffix]
          | Bool
otherwise ->
              forall a b. a -> Either a b
Left String
"Hunk wants to remove content that isn't there"
  | Int
line forall a. Ord a => a -> a -> Bool
>= Int
2 = do
      {- This is the simpler case. We can be sure that we have at least one
      newline character at the point where we modify the file. This means we
      can apply the conceptual view literally, i.e. replace old content with
      new content /before/ this newline, where the lines in the old and new
      content are /preceded/ by newline characters. -}
      (FileContents
pre, FileContents
start) <- Int -> FileContents -> Either String (FileContents, FileContents)
breakBeforeNthNewline (Int
lineforall a. Num a => a -> a -> a
-Int
2) FileContents
content
      let hunkContent :: [FileContents] -> FileContents
hunkContent [FileContents]
ls = [FileContents] -> FileContents
unlinesPS (FileContents
B.emptyforall a. a -> [a] -> [a]
:[FileContents]
ls)
      FileContents
post <- FileContents -> FileContents -> Either String FileContents
dropPrefix ([FileContents] -> FileContents
hunkContent [FileContents]
old) FileContents
start
      forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ [FileContents] -> FileContents
B.concat [FileContents
pre, [FileContents] -> FileContents
hunkContent [FileContents]
new, FileContents
post]
  | Bool
otherwise = forall a b. a -> Either a b
Left String
"Hunk has zero or negative line number"
  where
    dropPrefix :: FileContents -> FileContents -> Either String FileContents
dropPrefix FileContents
x FileContents
y
      | FileContents
x FileContents -> FileContents -> Bool
`B.isPrefixOf` FileContents
y = forall a b. b -> Either a b
Right forall a b. (a -> b) -> a -> b
$ Int -> FileContents -> FileContents
B.drop (FileContents -> Int
B.length FileContents
x) FileContents
y
      | Bool
otherwise =
        forall a b. a -> Either a b
Left forall a b. (a -> b) -> a -> b
$ String
"Hunk wants to remove content that isn't there"

breakAfterNthNewline :: Int -> B.ByteString -> Maybe (B.ByteString, B.ByteString)
breakAfterNthNewline :: Int -> FileContents -> Maybe (FileContents, FileContents)
breakAfterNthNewline Int
0 FileContents
the_ps = forall a. a -> Maybe a
Just (FileContents
B.empty, FileContents
the_ps)
breakAfterNthNewline Int
n FileContents
_ | Int
n forall a. Ord a => a -> a -> Bool
< Int
0 = forall a. HasCallStack => String -> a
error String
"precondition of breakAfterNthNewline"
breakAfterNthNewline Int
n FileContents
the_ps = forall {t}.
(Eq t, Num t) =>
t -> [Int] -> Maybe (FileContents, FileContents)
go Int
n (Char -> FileContents -> [Int]
BC.elemIndices Char
'\n' FileContents
the_ps)
  where
    go :: t -> [Int] -> Maybe (FileContents, FileContents)
go t
_ [] = forall a. Maybe a
Nothing -- we have fewer than n newlines
    go t
1 (Int
i:[Int]
_) = forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Int -> FileContents -> (FileContents, FileContents)
B.splitAt (Int
i forall a. Num a => a -> a -> a
+ Int
1) FileContents
the_ps
    go !t
m (Int
_:[Int]
is) = t -> [Int] -> Maybe (FileContents, FileContents)
go (t
m forall a. Num a => a -> a -> a
- t
1) [Int]
is

breakBeforeNthNewline :: Int -> B.ByteString -> Either String (B.ByteString, B.ByteString)
breakBeforeNthNewline :: Int -> FileContents -> Either String (FileContents, FileContents)
breakBeforeNthNewline Int
n FileContents
_ | Int
n forall a. Ord a => a -> a -> Bool
< Int
0 = forall a. HasCallStack => String -> a
error String
"precondition of breakBeforeNthNewline"
breakBeforeNthNewline Int
n FileContents
the_ps = forall {t}.
(Eq t, Num t) =>
t -> [Int] -> Either String (FileContents, FileContents)
go Int
n (Char -> FileContents -> [Int]
BC.elemIndices Char
'\n' FileContents
the_ps)
  where
    go :: t -> [Int] -> Either String (FileContents, FileContents)
go t
0 [] = forall a b. b -> Either a b
Right (FileContents
the_ps, FileContents
B.empty)
    go t
0 (Int
i:[Int]
_) = forall a b. b -> Either a b
Right forall a b. (a -> b) -> a -> b
$ Int -> FileContents -> (FileContents, FileContents)
B.splitAt Int
i FileContents
the_ps
    go !t
m (Int
_:[Int]
is) = t -> [Int] -> Either String (FileContents, FileContents)
go (t
m forall a. Num a => a -> a -> a
- t
1) [Int]
is
    go t
_ [] = forall a b. a -> Either a b
Left String
"Line number does not exist"