All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.simiacryptus.diff.ApxPatchUtil.kt Maven / Gradle / Ivy

There is a newer version: 1.2.21
Show newest version
package com.simiacryptus.diff

import org.apache.commons.text.similarity.LevenshteinDistance

object ApxPatchUtil {


    fun patch(source: String, patch: String): String {
        val sourceLines = source.lines()
        val patchLines = patch.lines()

        // This will hold the final result
        val result = mutableListOf()

        // This will keep track of the current line in the source file
        var sourceIndex = 0

        // Process each line in the patch
        for (patchLine in patchLines.map { it.trim() }) {
            when {
                // If the line starts with "---" or "+++", it's a file indicator line, skip it
                patchLine.startsWith("---") || patchLine.startsWith("+++") -> continue

                // If the line starts with "@@", it's a hunk header
                patchLine.startsWith("@@") -> continue

                // If the line starts with "-", it's a deletion, skip the corresponding source line but otherwise treat it as a context line
                patchLine.startsWith("-") -> {
                    sourceIndex = onDelete(patchLine, sourceIndex, sourceLines, result)
                }

                // If the line starts with "+", it's an addition, add it to the result
                patchLine.startsWith("+") -> {
                    result.add(patchLine.substring(1))
                }

                // \d+\: ___ is a line number, strip it
                patchLine.matches(Regex("\\d+:.*")) -> {
                    sourceIndex = onContextLine(patchLine.substringAfter(":"), sourceIndex, sourceLines, result)
                }

                // it's a context line, advance the source cursor
                else -> {
                    sourceIndex = onContextLine(patchLine, sourceIndex, sourceLines, result)
                }
            }
        }

        // Append any remaining lines from the source file
        while (sourceIndex < sourceLines.size) {
            result.add(sourceLines[sourceIndex])
            sourceIndex++
        }

        return result.joinToString("\n")
    }

    private fun onDelete(
        patchLine: String,
        sourceIndex: Int,
        sourceLines: List,
        result: MutableList
    ): Int {
        var sourceIndex1 = sourceIndex
        val delLine = patchLine.substring(1)
        val sourceIndexSearch = lookAheadFor(sourceIndex1, sourceLines, delLine)
        if (sourceIndexSearch > 0 && sourceIndexSearch + 1 < sourceLines.size) {
            val contextChunk = sourceLines.subList(sourceIndex1, sourceIndexSearch)
            result.addAll(contextChunk)
            sourceIndex1 = sourceIndexSearch + 1
        } else {
            println("Deletion line not found in source file: $delLine")
            // Ignore
        }
        return sourceIndex1
    }

    private fun onContextLine(
        patchLine: String,
        sourceIndex: Int,
        sourceLines: List,
        result: MutableList
    ): Int {
        var sourceIndex1 = sourceIndex
        val sourceIndexSearch = lookAheadFor(sourceIndex1, sourceLines, patchLine)
        if (sourceIndexSearch > 0 && sourceIndexSearch + 1 < sourceLines.size) {
            val contextChunk = sourceLines.subList(sourceIndex1, sourceIndexSearch + 1)
            result.addAll(contextChunk)
            sourceIndex1 = sourceIndexSearch + 1
        } else {
            println("Context line not found in source file: $patchLine")
            // Ignore
        }
        return sourceIndex1
    }

    private fun lookAheadFor(
        sourceIndex: Int,
        sourceLines: List,
        patchLine: String
    ): Int {
        var sourceIndexSearch = sourceIndex
        while (sourceIndexSearch < sourceLines.size) {
            if (lineMatches(patchLine, sourceLines[sourceIndexSearch++])) return sourceIndexSearch - 1
        }
        return -1
    }

    private fun lineMatches(
        a: String,
        b: String,
        factor: Double = 0.1,
    ): Boolean {
        val threshold = (Math.max(a.trim().length, b.trim().length) * factor).toInt()
        val levenshteinDistance = LevenshteinDistance(threshold + 1)
        val dist = levenshteinDistance.apply(a.trim(), b.trim())
        return if (dist >= 0) {
            dist <= threshold
        } else {
            false
        }
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy