Commit 0f1af0ad authored by catch's avatar catch

Issue #1848266 by alexpott: Convert Diff into a proper, PSR-0-compatible PHP component.

parent 0ad74d11
......@@ -114,6 +114,9 @@ services:
cron:
class: Drupal\Core\Cron
arguments: ['@module_handler', '@lock', '@queue', '@state', '@current_user', '@session_manager']
diff.formatter:
class: Drupal\Core\Diff\DiffFormatter
arguments: ['@config.factory']
database:
class: Drupal\Core\Database\Connection
factory_class: Drupal\Core\Database\Database
......
<?php
namespace Drupal\Component\Diff;
use Drupal\Component\Diff\Engine\DiffEngine;
/**
* Class representing a 'diff' between two sequences of strings.
* @todo document
* @subpackage DifferenceEngine
*
* Copied from https://drupal.org/project/diff which was based PHP diff engine
* for phpwiki. (Taken from phpwiki-1.3.3) The original code in phpwiki was
* copyright (C) 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org> and
* licensed under GPL.
*/
class Diff {
/**
* The list of differences as an array of diff operations.
*
* @var \Drupal\Component\Diff\Engine\DiffOp[]
*/
protected $edits;
/**
* Constructor.
* Computes diff between sequences of strings.
*
* @param $from_lines array An array of strings.
* (Typically these are lines from a file.)
* @param $to_lines array An array of strings.
*/
public function __construct($from_lines, $to_lines) {
$eng = new DiffEngine();
$this->edits = $eng->diff($from_lines, $to_lines);
//$this->_check($from_lines, $to_lines);
}
/**
* Compute reversed Diff.
*
* SYNOPSIS:
*
* $diff = new Diff($lines1, $lines2);
* $rev = $diff->reverse();
* @return object A Diff object representing the inverse of the
* original diff.
*/
public function reverse() {
$rev = $this;
$rev->edits = array();
foreach ($this->edits as $edit) {
$rev->edits[] = $edit->reverse();
}
return $rev;
}
/**
* Check for empty diff.
*
* @return bool True iff two sequences were identical.
*/
public function isEmpty() {
foreach ($this->edits as $edit) {
if ($edit->type != 'copy') {
return FALSE;
}
}
return TRUE;
}
/**
* Compute the length of the Longest Common Subsequence (LCS).
*
* This is mostly for diagnostic purposed.
*
* @return int The length of the LCS.
*/
public function lcs() {
$lcs = 0;
foreach ($this->edits as $edit) {
if ($edit->type == 'copy') {
$lcs += sizeof($edit->orig);
}
}
return $lcs;
}
/**
* Get the original set of lines.
*
* This reconstructs the $from_lines parameter passed to the
* constructor.
*
* @return array The original sequence of strings.
*/
public function orig() {
$lines = array();
foreach ($this->edits as $edit) {
if ($edit->orig) {
array_splice($lines, sizeof($lines), 0, $edit->orig);
}
}
return $lines;
}
/**
* Get the closing set of lines.
*
* This reconstructs the $to_lines parameter passed to the
* constructor.
*
* @return array The sequence of strings.
*/
public function closing() {
$lines = array();
foreach ($this->edits as $edit) {
if ($edit->closing) {
array_splice($lines, sizeof($lines), 0, $edit->closing);
}
}
return $lines;
}
/**
* Check a Diff for validity.
*
* This is here only for debugging purposes.
*/
public function check($from_lines, $to_lines) {
if (serialize($from_lines) != serialize($this->orig())) {
trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
}
if (serialize($to_lines) != serialize($this->closing())) {
trigger_error("Reconstructed closing doesn't match", E_USER_ERROR);
}
$rev = $this->reverse();
if (serialize($to_lines) != serialize($rev->orig())) {
trigger_error("Reversed original doesn't match", E_USER_ERROR);
}
if (serialize($from_lines) != serialize($rev->closing())) {
trigger_error("Reversed closing doesn't match", E_USER_ERROR);
}
$prevtype = 'none';
foreach ($this->edits as $edit) {
if ( $prevtype == $edit->type ) {
trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
}
$prevtype = $edit->type;
}
$lcs = $this->lcs();
trigger_error('Diff okay: LCS = ' . $lcs, E_USER_NOTICE);
}
/**
* Gets the list of differences as an array of diff operations.
*
* @return \Drupal\Component\Diff\Engine\DiffOp[]
* The list of differences as an array of diff operations.
*/
public function getEdits() {
return $this->edits;
}
}
<?php
namespace Drupal\Component\Diff;
use Drupal\Component\Diff\Engine\DiffOpCopy;
/**
* A class to format Diffs
*
* This class formats the diff in classic diff format.
* It is intended that this class be customized via inheritance,
* to obtain fancier outputs.
* @todo document
* @private
* @subpackage DifferenceEngine
*/
class DiffFormatter {
/**
* Should a block header be shown?
*/
var $show_header = TRUE;
/**
* Number of leading context "lines" to preserve.
*
* This should be left at zero for this class, but subclasses
* may want to set this to other values.
*/
var $leading_context_lines = 0;
/**
* Number of trailing context "lines" to preserve.
*
* This should be left at zero for this class, but subclasses
* may want to set this to other values.
*/
var $trailing_context_lines = 0;
/**
* Format a diff.
*
* @param \Drupal\Component\Diff\Diff $diff
* A Diff object.
*
* @return string
* The formatted output.
*/
public function format(Diff $diff) {
$xi = $yi = 1;
$block = FALSE;
$context = array();
$nlead = $this->leading_context_lines;
$ntrail = $this->trailing_context_lines;
$this->_start_diff();
foreach ($diff->getEdits() as $edit) {
if ($edit->type == 'copy') {
if (is_array($block)) {
if (sizeof($edit->orig) <= $nlead + $ntrail) {
$block[] = $edit;
}
else {
if ($ntrail) {
$context = array_slice($edit->orig, 0, $ntrail);
$block[] = new DiffOpCopy($context);
}
$this->_block($x0, $ntrail + $xi - $x0, $y0, $ntrail + $yi - $y0, $block);
$block = FALSE;
}
}
$context = $edit->orig;
}
else {
if (! is_array($block)) {
$context = array_slice($context, sizeof($context) - $nlead);
$x0 = $xi - sizeof($context);
$y0 = $yi - sizeof($context);
$block = array();
if ($context) {
$block[] = new DiffOpCopy($context);
}
}
$block[] = $edit;
}
if ($edit->orig) {
$xi += sizeof($edit->orig);
}
if ($edit->closing) {
$yi += sizeof($edit->closing);
}
}
if (is_array($block)) {
$this->_block($x0, $xi - $x0, $y0, $yi - $y0, $block);
}
$end = $this->_end_diff();
if (!empty($xi)) {
$this->line_stats['counter']['x'] += $xi;
}
if (!empty($yi)) {
$this->line_stats['counter']['y'] += $yi;
}
return $end;
}
protected function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) {
$this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen));
foreach ($edits as $edit) {
if ($edit->type == 'copy') {
$this->_context($edit->orig);
}
elseif ($edit->type == 'add') {
$this->_added($edit->closing);
}
elseif ($edit->type == 'delete') {
$this->_deleted($edit->orig);
}
elseif ($edit->type == 'change') {
$this->_changed($edit->orig, $edit->closing);
}
else {
trigger_error('Unknown edit type', E_USER_ERROR);
}
}
$this->_end_block();
}
protected function _start_diff() {
ob_start();
}
protected function _end_diff() {
$val = ob_get_contents();
ob_end_clean();
return $val;
}
protected function _block_header($xbeg, $xlen, $ybeg, $ylen) {
if ($xlen > 1) {
$xbeg .= "," . ($xbeg + $xlen - 1);
}
if ($ylen > 1) {
$ybeg .= "," . ($ybeg + $ylen - 1);
}
return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg;
}
protected function _start_block($header) {
if ($this->show_header) {
echo $header . "\n";
}
}
protected function _end_block() {
}
protected function _lines($lines, $prefix = ' ') {
foreach ($lines as $line) {
echo "$prefix $line\n";
}
}
protected function _context($lines) {
$this->_lines($lines);
}
protected function _added($lines) {
$this->_lines($lines, '>');
}
protected function _deleted($lines) {
$this->_lines($lines, '<');
}
protected function _changed($orig, $closing) {
$this->_deleted($orig);
echo "---\n";
$this->_added($closing);
}
}
<?php
/**
* @file
* A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3)
*
* Copyright (C) 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
* You may copy this code freely under the conditions of the GPL.
*/
namespace Drupal\Component\Diff\Engine;
use Drupal\Component\Utility\String;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Site\Settings;
define('USE_ASSERTS', FALSE);
/**
* @todo document
* @private
* @subpackage DifferenceEngine
*/
class _DiffOp {
var $type;
var $orig;
var $closing;
function reverse() {
trigger_error('pure virtual', E_USER_ERROR);
}
function norig() {
return $this->orig ? sizeof($this->orig) : 0;
}
function nclosing() {
return $this->closing ? sizeof($this->closing) : 0;
}
}
/**
* @todo document
* @private
* @subpackage DifferenceEngine
*/
class _DiffOp_Copy extends _DiffOp {
var $type = 'copy';
function _DiffOp_Copy($orig, $closing = FALSE) {
if (!is_array($closing)) {
$closing = $orig;
}
$this->orig = $orig;
$this->closing = $closing;
}
function reverse() {
return new _DiffOp_Copy($this->closing, $this->orig);
}
}
/**
* @todo document
* @private
* @subpackage DifferenceEngine
*/
class _DiffOp_Delete extends _DiffOp {
var $type = 'delete';
function _DiffOp_Delete($lines) {
$this->orig = $lines;
$this->closing = FALSE;
}
function reverse() {
return new _DiffOp_Add($this->orig);
}
}
/**
* @todo document
* @private
* @subpackage DifferenceEngine
*/
class _DiffOp_Add extends _DiffOp {
var $type = 'add';
function _DiffOp_Add($lines) {
$this->closing = $lines;
$this->orig = FALSE;
}
function reverse() {
return new _DiffOp_Delete($this->closing);
}
}
/**
* @todo document
* @private
* @subpackage DifferenceEngine
*/
class _DiffOp_Change extends _DiffOp {
var $type = 'change';
function _DiffOp_Change($orig, $closing) {
$this->orig = $orig;
$this->closing = $closing;
}
function reverse() {
return new _DiffOp_Change($this->closing, $this->orig);
}
}
/**
* Class used internally by Diff to actually compute the diffs.
......@@ -136,12 +27,13 @@ function reverse() {
* @private
* @subpackage DifferenceEngine
*/
class _DiffEngine {
function MAX_XREF_LENGTH() {
return 10000;
}
class DiffEngine {
const USE_ASSERTS = FALSE;
function diff($from_lines, $to_lines) {
const MAX_XREF_LENGTH = 10000;
public function diff($from_lines, $to_lines) {
$n_from = sizeof($from_lines);
$n_to = sizeof($to_lines);
......@@ -204,8 +96,8 @@ function diff($from_lines, $to_lines) {
$edits = array();
$xi = $yi = 0;
while ($xi < $n_from || $yi < $n_to) {
USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
$this::USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
$this::USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
// Skip matching "snake".
$copy = array();
......@@ -214,7 +106,7 @@ function diff($from_lines, $to_lines) {
++$yi;
}
if ($copy) {
$edits[] = new _DiffOp_Copy($copy);
$edits[] = new DiffOpCopy($copy);
}
// Find deletes & adds.
$delete = array();
......@@ -226,13 +118,13 @@ function diff($from_lines, $to_lines) {
$add[] = $to_lines[$yi++];
}
if ($delete && $add) {
$edits[] = new _DiffOp_Change($delete, $add);
$edits[] = new DiffOpChange($delete, $add);
}
elseif ($delete) {
$edits[] = new _DiffOp_Delete($delete);
$edits[] = new DiffOpDelete($delete);
}
elseif ($add) {
$edits[] = new _DiffOp_Add($add);
$edits[] = new DiffOpAdd($add);
}
}
return $edits;
......@@ -241,8 +133,8 @@ function diff($from_lines, $to_lines) {
/**
* Returns the whole line if it's small enough, or the MD5 hash otherwise.
*/
function _line_hash($line) {
if (Unicode::strlen($line) > $this->MAX_XREF_LENGTH()) {
protected function _line_hash($line) {
if (Unicode::strlen($line) > $this::MAX_XREF_LENGTH) {
return md5($line);
}
else {
......@@ -268,7 +160,7 @@ function _line_hash($line) {
* match. The caller must trim matching lines from the beginning and end
* of the portions it is going to specify.
*/
function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) {
protected function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) {
$flip = FALSE;
if ($xlim - $xoff > $ylim - $yoff) {
......@@ -313,14 +205,14 @@ function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) {
while (list ($junk, $y) = each($matches)) {
if (empty($this->in_seq[$y])) {
$k = $this->_lcs_pos($y);
USE_ASSERTS && assert($k > 0);
$this::USE_ASSERTS && assert($k > 0);
$ymids[$k] = $ymids[$k-1];
break;
}
}
while (list ($junk, $y) = each($matches)) {
if ($y > $this->seq[$k-1]) {
USE_ASSERTS && assert($y < $this->seq[$k]);
$this::USE_ASSERTS && assert($y < $this->seq[$k]);
// Optimization: this is a common case:
// next match is just replacing previous match.
$this->in_seq[$this->seq[$k]] = FALSE;
......@@ -329,7 +221,7 @@ function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) {
}
elseif (empty($this->in_seq[$y])) {
$k = $this->_lcs_pos($y);
USE_ASSERTS && assert($k > 0);
$this::USE_ASSERTS && assert($k > 0);
$ymids[$k] = $ymids[$k-1];
}
}
......@@ -348,7 +240,7 @@ function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) {
return array($this->lcs, $seps);
}
function _lcs_pos($ypos) {
protected function _lcs_pos($ypos) {
$end = $this->lcs;
if ($end == 0 || $ypos > $this->seq[$end]) {
......@@ -368,7 +260,7 @@ function _lcs_pos($ypos) {
}
}
USE_ASSERTS && assert($ypos != $this->seq[$end]);
$this::USE_ASSERTS && assert($ypos != $this->seq[$end]);
$this->in_seq[$this->seq[$end]] = FALSE;
$this->seq[$end] = $ypos;
......@@ -388,7 +280,7 @@ function _lcs_pos($ypos) {
* Note that XLIM, YLIM are exclusive bounds.
* All line numbers are origin-0 and discarded lines are not counted.
*/
function _compareseq($xoff, $xlim, $yoff, $ylim) {
protected function _compareseq($xoff, $xlim, $yoff, $ylim) {
// Slide down the bottom initial diagonal.
while ($xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff]) {
......@@ -448,11 +340,11 @@ function _compareseq($xoff, $xlim, $yoff, $ylim) {
*
* This is extracted verbatim from analyze.c (GNU diffutils-2.7).
*/
function _shift_boundaries($lines, &$changed, $other_changed) {
protected function _shift_boundaries($lines, &$changed, $other_changed) {
$i = 0;
$j = 0;
USE_ASSERTS && assert('sizeof($lines) == sizeof($changed)');
$this::USE_ASSERTS && assert('sizeof($lines) == sizeof($changed)');
$len = sizeof($lines);
$other_len = sizeof($other_changed);
......@@ -472,7 +364,7 @@ function _shift_boundaries($lines, &$changed, $other_changed) {
$j++;
}
while ($i < $len && ! $changed[$i]) {
USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
$this::USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
$i++;
$j++;
while ($j < $other_len && $other_changed[$j]) {
......@@ -508,11 +400,11 @@ function _shift_boundaries($lines, &$changed, $other_changed) {
while ($start > 0 && $changed[$start - 1]) {
$start--;
}
USE_ASSERTS && assert('$j > 0');
$this::USE_ASSERTS && assert('$j > 0');
while ($other_changed[--$j]) {
continue;
}
USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
$this::USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
}
/*
......@@ -535,7 +427,7 @@ function _shift_boundaries($lines, &$changed, $other_changed) {
while ($i < $len && $changed[$i]) {
$i++;
}
USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
$this::USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
$j++;
if ($j < $other_len && $other_changed[$j]) {
$corresponding = $i;
......@@ -553,674 +445,12 @@ function _shift_boundaries($lines, &$changed, $other_changed) {
while ($corresponding < $i) {
$changed[--$start] = 1;
$changed[--$i] = 0;
USE_ASSERTS && assert('$j > 0');
$this::USE_ASSERTS && assert('$j > 0');
while ($other_changed[--$j]) {
continue;
}
USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
}
}
}
}
/**
* Class representing a 'diff' between two sequences of strings.
* @todo document
* @private
* @subpackage DifferenceEngine
*/
class Diff {
var $edits;
/**
* Constructor.
* Computes diff between sequences of strings.
*
* @param $from_lines array An array of strings.
* (Typically these are lines from a file.)
* @param $to_lines array An array of strings.
*/
function Diff($from_lines, $to_lines) {
$eng = new _DiffEngine;
$this->edits = $eng->diff($from_lines, $to_lines);
//$this->_check($from_lines, $to_lines);
}
/**
* Compute reversed Diff.
*
* SYNOPSIS:
*
* $diff = new Diff($lines1, $lines2);
* $rev = $diff->reverse();
* @return object A Diff object representing the inverse of the
* original diff.
*/
function reverse() {
$rev = $this;
$rev->edits = array();
foreach ($this->edits as $edit) {
$rev->edits[] = $edit->reverse();
}
return $rev;
}
/**
* Check for empty diff.
*
* @return bool True iff two sequences were identical.
*/
function isEmpty() {
foreach ($this->edits as $edit) {
if ($edit->type != 'copy') {