ai-agent-diff-patch-0.0.1.0: src/AIAgent/DiffPatch/Unified.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Boot layer: public API for the ai-agent-diff-patch library.
--
-- Provides three file-based operations built on the CPA AppContext structure:
--
-- * 'diff' — compare two files and write unified diff to stdout
-- * 'patch' — apply a patch to a file and write the result to stdout
-- * 'patchFile' — apply a patch to a file and write the result back in place
--
-- IOFunc values are assembled here explicitly rather than via 'Data.Default',
-- so the I\/O wiring is visible at the single point of entry.
--
-- === Example
--
-- @
-- import AIAgent.DiffPatch.Unified
--
-- main :: IO ()
-- main = do
-- -- generate a diff and print to stdout
-- diff "old.txt" "new.txt" >>= either putStrLn pure
--
-- -- apply a patch file in place
-- patchText <- readFile "changes.patch"
-- patchFile "target.txt" patchText >>= either putStrLn pure
-- @
--
-- === Limitations
--
-- * Fuzzy search window is ±5 lines; larger line-number drift will fail.
-- * Myers diff degrades toward O(N²) for files with many differences.
-- * Binary files, multi-file patches, and VCS-specific headers are not supported.
-- * The @\\ No newline at end of file@ marker is not parsed or emitted.
--
-- See the README for the full list of known limitations.
module AIAgent.DiffPatch.Unified
( diff
, patch
, patchFile
) where
import AIAgent.DiffPatch.Unified.CoreModel.Type (IOFunc (..))
import qualified AIAgent.DiffPatch.Unified.Interface.FileIO as F
import qualified AIAgent.DiffPatch.Unified.Interface.StdIO as S
import qualified AIAgent.DiffPatch.Unified.ApplicationBase.Control as Control
-- | Compute the unified diff between two files and write the result to stdout.
--
-- Uses the Myers O(ND) algorithm with 3 context lines (GNU @diff -u@ default).
-- Returns @Left errorMessage@ if either file cannot be read.
--
-- === Example
--
-- @
-- result <- diff "old.txt" "new.txt"
-- case result of
-- Left err -> putStrLn $ "diff failed: " ++ err
-- Right () -> pure () -- unified diff has been written to stdout
-- @
diff :: FilePath -> FilePath -> IO (Either String ())
diff oldFile newFile =
let ioFunc = IOFunc { _readIOFunc = F.read, _writeIOFunc = S.write }
in Control.runDiff ioFunc oldFile newFile
-- | Apply a unified diff patch to a file and write the patched result to stdout.
--
-- Fuzzy matching tolerates line-number drift of up to ±5 lines.
-- Returns @Left errorMessage@ on parse failure, hunk mismatch, or file read error.
-- No exceptions are thrown.
--
-- === Example
--
-- @
-- let p = unlines ["@@ -1,2 +1,2 @@", " ctx", "-old", "+new"]
-- result <- patch "target.txt" p
-- case result of
-- Left err -> putStrLn $ "patch failed: " ++ err
-- Right () -> pure ()
-- @
patch :: FilePath -> String -> IO (Either String ())
patch origFile patchText =
let ioFunc = IOFunc { _readIOFunc = F.read, _writeIOFunc = S.write }
in Control.runPatch ioFunc origFile patchText
-- | Apply a unified diff patch to a file and write the result back to the same file.
--
-- Identical to 'patch' except the patched content replaces the original file
-- rather than being written to stdout.
-- Line endings (LF or CRLF) are detected from the original file and restored
-- in the output.
-- Returns @Left errorMessage@ on any failure; the original file is not modified
-- if the patch cannot be applied.
--
-- === Example
--
-- @
-- result <- patchFile "target.txt" patchText
-- case result of
-- Left err -> putStrLn $ "patchFile failed: " ++ err
-- Right () -> pure () -- target.txt has been updated in place
-- @
patchFile :: FilePath -> String -> IO (Either String ())
patchFile origFile patchText =
let ioFunc = IOFunc { _readIOFunc = F.read, _writeIOFunc = F.write }
in Control.runPatchFile ioFunc origFile patchText