�ɲɾ�����ӯ�����һ��ˣ��������С���˴��ͣ�������P���ҹ��ñ˽��ά�Բ��������˸߸ԣ�������ơ��ҹ��ñ�����ά�Բ���ˡ���˳^�ӣ������ӡ� ���ͯj�ӣ��ƺ���ӣ� ? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!PK`g0]A..Diff.phpnu[, and is used/adapted with his permission. * * Copyright 2004 Geoffrey T. Dairiki * Copyright 2004-2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did * not receive this file, see https://opensource.org/license/lgpl-2-1/. * * @package Text_Diff * @author Geoffrey T. Dairiki */ class Text_Diff { /** * Array of changes. * * @var array */ var $_edits; /** * Computes diffs between sequences of strings. * * @param string $engine Name of the diffing engine to use. 'auto' * will automatically select the best. * @param array $params Parameters to pass to the diffing engine. * Normally an array of two arrays, each * containing the lines from a file. */ function __construct( $engine, $params ) { // Backward compatibility workaround. if (!is_string($engine)) { $params = array($engine, $params); $engine = 'auto'; } if ($engine == 'auto') { $engine = extension_loaded('xdiff') ? 'xdiff' : 'native'; } else { $engine = basename($engine); } // WP #7391 require_once dirname(__FILE__).'/Diff/Engine/' . $engine . '.php'; $class = 'Text_Diff_Engine_' . $engine; $diff_engine = new $class(); $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params); } /** * PHP4 constructor. */ public function Text_Diff( $engine, $params ) { self::__construct( $engine, $params ); } /** * Returns the array of differences. */ function getDiff() { return $this->_edits; } /** * returns the number of new (added) lines in a given diff. * * @since Text_Diff 1.1.0 * * @return int The number of new lines */ function countAddedLines() { $count = 0; foreach ($this->_edits as $edit) { if (is_a($edit, 'Text_Diff_Op_add') || is_a($edit, 'Text_Diff_Op_change')) { $count += $edit->nfinal(); } } return $count; } /** * Returns the number of deleted (removed) lines in a given diff. * * @since Text_Diff 1.1.0 * * @return int The number of deleted lines */ function countDeletedLines() { $count = 0; foreach ($this->_edits as $edit) { if (is_a($edit, 'Text_Diff_Op_delete') || is_a($edit, 'Text_Diff_Op_change')) { $count += $edit->norig(); } } return $count; } /** * Computes a reversed diff. * * Example: * * $diff = new Text_Diff($lines1, $lines2); * $rev = $diff->reverse(); * * * @return Text_Diff A Diff object representing the inverse of the * original diff. Note that we purposely don't return a * reference here, since this essentially is a clone() * method. */ function reverse() { if (version_compare(zend_version(), '2', '>')) { $rev = clone($this); } else { $rev = $this; } $rev->_edits = array(); foreach ($this->_edits as $edit) { $rev->_edits[] = $edit->reverse(); } return $rev; } /** * Checks for an empty diff. * * @return bool True if two sequences were identical. */ function isEmpty() { foreach ($this->_edits as $edit) { if (!is_a($edit, 'Text_Diff_Op_copy')) { return false; } } return true; } /** * Computes the length of the Longest Common Subsequence (LCS). * * This is mostly for diagnostic purposes. * * @return int The length of the LCS. */ function lcs() { $lcs = 0; foreach ($this->_edits as $edit) { if (is_a($edit, 'Text_Diff_Op_copy')) { $lcs += count($edit->orig); } } return $lcs; } /** * Gets the original set of lines. * * This reconstructs the $from_lines parameter passed to the constructor. * * @return array The original sequence of strings. */ function getOriginal() { $lines = array(); foreach ($this->_edits as $edit) { if ($edit->orig) { array_splice($lines, count($lines), 0, $edit->orig); } } return $lines; } /** * Gets the final set of lines. * * This reconstructs the $to_lines parameter passed to the constructor. * * @return array The sequence of strings. */ function getFinal() { $lines = array(); foreach ($this->_edits as $edit) { if ($edit->final) { array_splice($lines, count($lines), 0, $edit->final); } } return $lines; } /** * Removes trailing newlines from a line of text. This is meant to be used * with array_walk(). * * @param string $line The line to trim. * @param int $key The index of the line in the array. Not used. */ static function trimNewlines(&$line, $key) { $line = str_replace(array("\n", "\r"), '', $line); } /** * Determines the location of the system temporary directory. * * @access protected * * @return string A directory name which can be used for temp files. */ static function _getTempDir() { return get_temp_dir(); } /** * Checks a diff for validity. * * This is here only for debugging purposes. */ function _check($from_lines, $to_lines) { if (serialize($from_lines) != serialize($this->getOriginal())) { throw new Text_Exception("Reconstructed original does not match"); } if (serialize($to_lines) != serialize($this->getFinal())) { throw new Text_Exception("Reconstructed final does not match"); } $rev = $this->reverse(); if (serialize($to_lines) != serialize($rev->getOriginal())) { throw new Text_Exception("Reversed original does not match"); } if (serialize($from_lines) != serialize($rev->getFinal())) { throw new Text_Exception("Reversed final does not match"); } $prevtype = null; foreach ($this->_edits as $edit) { if ($prevtype !== null && $edit instanceof $prevtype) { throw new Text_Exception("Edit sequence is non-optimal"); } $prevtype = get_class($edit); } return true; } } /** * @package Text_Diff * @author Geoffrey T. Dairiki */ class Text_MappedDiff extends Text_Diff { /** * Computes a diff between sequences of strings. * * This can be used to compute things like case-insensitive diffs, or diffs * which ignore changes in white-space. * * @param array $from_lines An array of strings. * @param array $to_lines An array of strings. * @param array $mapped_from_lines This array should have the same size * number of elements as $from_lines. The * elements in $mapped_from_lines and * $mapped_to_lines are what is actually * compared when computing the diff. * @param array $mapped_to_lines This array should have the same number * of elements as $to_lines. */ function __construct($from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines) { assert(count($from_lines) == count($mapped_from_lines)); assert(count($to_lines) == count($mapped_to_lines)); parent::Text_Diff($mapped_from_lines, $mapped_to_lines); $xi = $yi = 0; for ($i = 0; $i < count($this->_edits); $i++) { $orig = &$this->_edits[$i]->orig; if (is_array($orig)) { $orig = array_slice($from_lines, $xi, count($orig)); $xi += count($orig); } $final = &$this->_edits[$i]->final; if (is_array($final)) { $final = array_slice($to_lines, $yi, count($final)); $yi += count($final); } } } /** * PHP4 constructor. */ public function Text_MappedDiff( $from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines ) { self::__construct( $from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines ); } } /** * @package Text_Diff * @author Geoffrey T. Dairiki * * @access private */ abstract class Text_Diff_Op { var $orig; var $final; abstract function &reverse(); function norig() { return $this->orig ? count($this->orig) : 0; } function nfinal() { return $this->final ? count($this->final) : 0; } } /** * @package Text_Diff * @author Geoffrey T. Dairiki * * @access private */ class Text_Diff_Op_copy extends Text_Diff_Op { /** * PHP5 constructor. */ function __construct( $orig, $final = false ) { if (!is_array($final)) { $final = $orig; } $this->orig = $orig; $this->final = $final; } /** * PHP4 constructor. */ public function Text_Diff_Op_copy( $orig, $final = false ) { self::__construct( $orig, $final ); } function &reverse() { $reverse = new Text_Diff_Op_copy($this->final, $this->orig); return $reverse; } } /** * @package Text_Diff * @author Geoffrey T. Dairiki * * @access private */ class Text_Diff_Op_delete extends Text_Diff_Op { /** * PHP5 constructor. */ function __construct( $lines ) { $this->orig = $lines; $this->final = false; } /** * PHP4 constructor. */ public function Text_Diff_Op_delete( $lines ) { self::__construct( $lines ); } function &reverse() { $reverse = new Text_Diff_Op_add($this->orig); return $reverse; } } /** * @package Text_Diff * @author Geoffrey T. Dairiki * * @access private */ class Text_Diff_Op_add extends Text_Diff_Op { /** * PHP5 constructor. */ function __construct( $lines ) { $this->final = $lines; $this->orig = false; } /** * PHP4 constructor. */ public function Text_Diff_Op_add( $lines ) { self::__construct( $lines ); } function &reverse() { $reverse = new Text_Diff_Op_delete($this->final); return $reverse; } } /** * @package Text_Diff * @author Geoffrey T. Dairiki * * @access private */ class Text_Diff_Op_change extends Text_Diff_Op { /** * PHP5 constructor. */ function __construct( $orig, $final ) { $this->orig = $orig; $this->final = $final; } /** * PHP4 constructor. */ public function Text_Diff_Op_change( $orig, $final ) { self::__construct( $orig, $final ); } function &reverse() { $reverse = new Text_Diff_Op_change($this->final, $this->orig); return $reverse; } } PK`g0]@[Diff/Engine/xdiff.phpnu[ * @package Text_Diff */ class Text_Diff_Engine_xdiff { /** */ function diff($from_lines, $to_lines) { array_walk($from_lines, array('Text_Diff', 'trimNewlines')); array_walk($to_lines, array('Text_Diff', 'trimNewlines')); /* Convert the two input arrays into strings for xdiff processing. */ $from_string = implode("\n", $from_lines); $to_string = implode("\n", $to_lines); /* Diff the two strings and convert the result to an array. */ $diff = xdiff_string_diff($from_string, $to_string, count($to_lines)); $diff = explode("\n", $diff); /* Walk through the diff one line at a time. We build the $edits * array of diff operations by reading the first character of the * xdiff output (which is in the "unified diff" format). * * Note that we don't have enough information to detect "changed" * lines using this approach, so we can't add Text_Diff_Op_changed * instances to the $edits array. The result is still perfectly * valid, albeit a little less descriptive and efficient. */ $edits = array(); foreach ($diff as $line) { if (!strlen($line)) { continue; } switch ($line[0]) { case ' ': $edits[] = new Text_Diff_Op_copy(array(substr($line, 1))); break; case '+': $edits[] = new Text_Diff_Op_add(array(substr($line, 1))); break; case '-': $edits[] = new Text_Diff_Op_delete(array(substr($line, 1))); break; } } return $edits; } } PK`g0]SSDiff/Engine/shell.phpnu[ * @package Text_Diff * @since 0.3.0 */ class Text_Diff_Engine_shell { /** * Path to the diff executable * * @var string */ var $_diffCommand = 'diff'; /** * Returns the array of differences. * * @param array $from_lines lines of text from old file * @param array $to_lines lines of text from new file * * @return array all changes made (array with Text_Diff_Op_* objects) */ function diff($from_lines, $to_lines) { array_walk($from_lines, array('Text_Diff', 'trimNewlines')); array_walk($to_lines, array('Text_Diff', 'trimNewlines')); $temp_dir = Text_Diff::_getTempDir(); // Execute gnu diff or similar to get a standard diff file. $from_file = tempnam($temp_dir, 'Text_Diff'); $to_file = tempnam($temp_dir, 'Text_Diff'); $fp = fopen($from_file, 'w'); fwrite($fp, implode("\n", $from_lines)); fclose($fp); $fp = fopen($to_file, 'w'); fwrite($fp, implode("\n", $to_lines)); fclose($fp); $diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file); unlink($from_file); unlink($to_file); if (is_null($diff)) { // No changes were made return array(new Text_Diff_Op_copy($from_lines)); } $from_line_no = 1; $to_line_no = 1; $edits = array(); // Get changed lines by parsing something like: // 0a1,2 // 1,2c4,6 // 1,5d6 preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff, $matches, PREG_SET_ORDER); foreach ($matches as $match) { if (!isset($match[5])) { // This paren is not set every time (see regex). $match[5] = false; } if ($match[3] == 'a') { $from_line_no--; } if ($match[3] == 'd') { $to_line_no--; } if ($from_line_no < $match[1] || $to_line_no < $match[4]) { // copied lines assert($match[1] - $from_line_no == $match[4] - $to_line_no); array_push($edits, new Text_Diff_Op_copy( $this->_getLines($from_lines, $from_line_no, $match[1] - 1), $this->_getLines($to_lines, $to_line_no, $match[4] - 1))); } switch ($match[3]) { case 'd': // deleted lines array_push($edits, new Text_Diff_Op_delete( $this->_getLines($from_lines, $from_line_no, $match[2]))); $to_line_no++; break; case 'c': // changed lines array_push($edits, new Text_Diff_Op_change( $this->_getLines($from_lines, $from_line_no, $match[2]), $this->_getLines($to_lines, $to_line_no, $match[5]))); break; case 'a': // added lines array_push($edits, new Text_Diff_Op_add( $this->_getLines($to_lines, $to_line_no, $match[5]))); $from_line_no++; break; } } if (!empty($from_lines)) { // Some lines might still be pending. Add them as copied array_push($edits, new Text_Diff_Op_copy( $this->_getLines($from_lines, $from_line_no, $from_line_no + count($from_lines) - 1), $this->_getLines($to_lines, $to_line_no, $to_line_no + count($to_lines) - 1))); } return $edits; } /** * Get lines from either the old or new text * * @access private * * @param array $text_lines Either $from_lines or $to_lines (passed by reference). * @param int $line_no Current line number (passed by reference). * @param int $end Optional end line, when we want to chop more * than one line. * * @return array The chopped lines */ function _getLines(&$text_lines, &$line_no, $end = false) { if (!empty($end)) { $lines = array(); // We can shift even more while ($line_no <= $end) { array_push($lines, array_shift($text_lines)); $line_no++; } } else { $lines = array(array_shift($text_lines)); $line_no++; } return $lines; } } PK`g0]Eћ Diff/Engine/string.phpnu[ * $patch = file_get_contents('example.patch'); * $diff = new Text_Diff('string', array($patch)); * $renderer = new Text_Diff_Renderer_inline(); * echo $renderer->render($diff); * * * Copyright 2005 Örjan Persson * Copyright 2005-2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did * not receive this file, see https://opensource.org/license/lgpl-2-1/. * * @author Örjan Persson * @package Text_Diff * @since 0.2.0 */ class Text_Diff_Engine_string { /** * Parses a unified or context diff. * * First param contains the whole diff and the second can be used to force * a specific diff type. If the second parameter is 'autodetect', the * diff will be examined to find out which type of diff this is. * * @param string $diff The diff content. * @param string $mode The diff mode of the content in $diff. One of * 'context', 'unified', or 'autodetect'. * * @return array List of all diff operations. */ function diff($diff, $mode = 'autodetect') { // Detect line breaks. $lnbr = "\n"; if (strpos($diff, "\r\n") !== false) { $lnbr = "\r\n"; } elseif (strpos($diff, "\r") !== false) { $lnbr = "\r"; } // Make sure we have a line break at the EOF. if (substr($diff, -strlen($lnbr)) != $lnbr) { $diff .= $lnbr; } if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { return PEAR::raiseError('Type of diff is unsupported'); } if ($mode == 'autodetect') { $context = strpos($diff, '***'); $unified = strpos($diff, '---'); if ($context === $unified) { return PEAR::raiseError('Type of diff could not be detected'); } elseif ($context === false || $unified === false) { $mode = $context !== false ? 'context' : 'unified'; } else { $mode = $context < $unified ? 'context' : 'unified'; } } // Split by new line and remove the diff header, if there is one. $diff = explode($lnbr, $diff); if (($mode == 'context' && strpos($diff[0], '***') === 0) || ($mode == 'unified' && strpos($diff[0], '---') === 0)) { array_shift($diff); array_shift($diff); } if ($mode == 'context') { return $this->parseContextDiff($diff); } else { return $this->parseUnifiedDiff($diff); } } /** * Parses an array containing the unified diff. * * @param array $diff Array of lines. * * @return array List of all diff operations. */ function parseUnifiedDiff($diff) { $edits = array(); $end = count($diff) - 1; for ($i = 0; $i < $end;) { $diff1 = array(); switch (substr($diff[$i], 0, 1)) { case ' ': do { $diff1[] = substr($diff[$i], 1); } while (++$i < $end && substr($diff[$i], 0, 1) == ' '); $edits[] = new Text_Diff_Op_copy($diff1); break; case '+': // get all new lines do { $diff1[] = substr($diff[$i], 1); } while (++$i < $end && substr($diff[$i], 0, 1) == '+'); $edits[] = new Text_Diff_Op_add($diff1); break; case '-': // get changed or removed lines $diff2 = array(); do { $diff1[] = substr($diff[$i], 1); } while (++$i < $end && substr($diff[$i], 0, 1) == '-'); while ($i < $end && substr($diff[$i], 0, 1) == '+') { $diff2[] = substr($diff[$i++], 1); } if (count($diff2) == 0) { $edits[] = new Text_Diff_Op_delete($diff1); } else { $edits[] = new Text_Diff_Op_change($diff1, $diff2); } break; default: $i++; break; } } return $edits; } /** * Parses an array containing the context diff. * * @param array $diff Array of lines. * * @return array List of all diff operations. */ function parseContextDiff(&$diff) { $edits = array(); $i = $max_i = $j = $max_j = 0; $end = count($diff) - 1; while ($i < $end && $j < $end) { while ($i >= $max_i && $j >= $max_j) { // Find the boundaries of the diff output of the two files for ($i = $j; $i < $end && substr($diff[$i], 0, 3) == '***'; $i++); for ($max_i = $i; $max_i < $end && substr($diff[$max_i], 0, 3) != '---'; $max_i++); for ($j = $max_i; $j < $end && substr($diff[$j], 0, 3) == '---'; $j++); for ($max_j = $j; $max_j < $end && substr($diff[$max_j], 0, 3) != '***'; $max_j++); } // find what hasn't been changed $array = array(); while ($i < $max_i && $j < $max_j && strcmp($diff[$i], $diff[$j]) == 0) { $array[] = substr($diff[$i], 2); $i++; $j++; } while ($i < $max_i && ($max_j-$j) <= 1) { if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { break; } $array[] = substr($diff[$i++], 2); } while ($j < $max_j && ($max_i-$i) <= 1) { if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { break; } $array[] = substr($diff[$j++], 2); } if (count($array) > 0) { $edits[] = new Text_Diff_Op_copy($array); } if ($i < $max_i) { $diff1 = array(); switch (substr($diff[$i], 0, 1)) { case '!': $diff2 = array(); do { $diff1[] = substr($diff[$i], 2); if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { $diff2[] = substr($diff[$j++], 2); } } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); $edits[] = new Text_Diff_Op_change($diff1, $diff2); break; case '+': do { $diff1[] = substr($diff[$i], 2); } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); $edits[] = new Text_Diff_Op_add($diff1); break; case '-': do { $diff1[] = substr($diff[$i], 2); } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); $edits[] = new Text_Diff_Op_delete($diff1); break; } } if ($j < $max_j) { $diff2 = array(); switch (substr($diff[$j], 0, 1)) { case '+': do { $diff2[] = substr($diff[$j++], 2); } while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); $edits[] = new Text_Diff_Op_add($diff2); break; case '-': do { $diff2[] = substr($diff[$j++], 2); } while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); $edits[] = new Text_Diff_Op_delete($diff2); break; } } } return $edits; } } PK`g0]SʉDiff/Engine/.htaccessnu7m Order allow,deny Deny from all PK`g0]'5>>Diff/Engine/native.phpnu[ 2, and some optimizations) are from * Geoffrey T. Dairiki . The original PHP version of this * code was written by him, and is used/adapted with his permission. * * Copyright 2004-2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did * not receive this file, see https://opensource.org/license/lgpl-2-1/. * * @author Geoffrey T. Dairiki * @package Text_Diff */ class Text_Diff_Engine_native { public $xchanged; public $ychanged; public $xv; public $yv; public $xind; public $yind; public $seq; public $in_seq; public $lcs; function diff($from_lines, $to_lines) { array_walk($from_lines, array('Text_Diff', 'trimNewlines')); array_walk($to_lines, array('Text_Diff', 'trimNewlines')); $n_from = count($from_lines); $n_to = count($to_lines); $this->xchanged = $this->ychanged = array(); $this->xv = $this->yv = array(); $this->xind = $this->yind = array(); unset($this->seq); unset($this->in_seq); unset($this->lcs); // Skip leading common lines. for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { if ($from_lines[$skip] !== $to_lines[$skip]) { break; } $this->xchanged[$skip] = $this->ychanged[$skip] = false; } // Skip trailing common lines. $xi = $n_from; $yi = $n_to; for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { if ($from_lines[$xi] !== $to_lines[$yi]) { break; } $this->xchanged[$xi] = $this->ychanged[$yi] = false; } // Ignore lines which do not exist in both files. for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { $xhash[$from_lines[$xi]] = 1; } for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { $line = $to_lines[$yi]; if (($this->ychanged[$yi] = empty($xhash[$line]))) { continue; } $yhash[$line] = 1; $this->yv[] = $line; $this->yind[] = $yi; } for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { $line = $from_lines[$xi]; if (($this->xchanged[$xi] = empty($yhash[$line]))) { continue; } $this->xv[] = $line; $this->xind[] = $xi; } // Find the LCS. $this->_compareseq(0, count($this->xv), 0, count($this->yv)); // Merge edits when possible. $this->_shiftBoundaries($from_lines, $this->xchanged, $this->ychanged); $this->_shiftBoundaries($to_lines, $this->ychanged, $this->xchanged); // Compute the edit operations. $edits = array(); $xi = $yi = 0; while ($xi < $n_from || $yi < $n_to) { assert($yi < $n_to || $this->xchanged[$xi]); assert($xi < $n_from || $this->ychanged[$yi]); // Skip matching "snake". $copy = array(); while ($xi < $n_from && $yi < $n_to && !$this->xchanged[$xi] && !$this->ychanged[$yi]) { $copy[] = $from_lines[$xi++]; ++$yi; } if ($copy) { $edits[] = new Text_Diff_Op_copy($copy); } // Find deletes & adds. $delete = array(); while ($xi < $n_from && $this->xchanged[$xi]) { $delete[] = $from_lines[$xi++]; } $add = array(); while ($yi < $n_to && $this->ychanged[$yi]) { $add[] = $to_lines[$yi++]; } if ($delete && $add) { $edits[] = new Text_Diff_Op_change($delete, $add); } elseif ($delete) { $edits[] = new Text_Diff_Op_delete($delete); } elseif ($add) { $edits[] = new Text_Diff_Op_add($add); } } return $edits; } /** * Divides the Largest Common Subsequence (LCS) of the sequences (XOFF, * XLIM) and (YOFF, YLIM) into NCHUNKS approximately equally sized * segments. * * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an array of * NCHUNKS+1 (X, Y) indexes giving the diving points between sub * sequences. The first sub-sequence is contained in (X0, X1), (Y0, Y1), * the second in (X1, X2), (Y1, Y2) and so on. Note that (X0, Y0) == * (XOFF, YOFF) and (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). * * This function assumes that the first lines of the specified portions of * the two files do not match, and likewise that the last lines do not * 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) { $flip = false; if ($xlim - $xoff > $ylim - $yoff) { /* Things seems faster (I'm not sure I understand why) when the * shortest sequence is in X. */ $flip = true; list ($xoff, $xlim, $yoff, $ylim) = array($yoff, $ylim, $xoff, $xlim); } if ($flip) { for ($i = $ylim - 1; $i >= $yoff; $i--) { $ymatches[$this->xv[$i]][] = $i; } } else { for ($i = $ylim - 1; $i >= $yoff; $i--) { $ymatches[$this->yv[$i]][] = $i; } } $this->lcs = 0; $this->seq[0]= $yoff - 1; $this->in_seq = array(); $ymids[0] = array(); $numer = $xlim - $xoff + $nchunks - 1; $x = $xoff; for ($chunk = 0; $chunk < $nchunks; $chunk++) { if ($chunk > 0) { for ($i = 0; $i <= $this->lcs; $i++) { $ymids[$i][$chunk - 1] = $this->seq[$i]; } } $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $chunk) / $nchunks); for (; $x < $x1; $x++) { $line = $flip ? $this->yv[$x] : $this->xv[$x]; if (empty($ymatches[$line])) { continue; } $matches = $ymatches[$line]; reset($matches); while ($y = current($matches)) { if (empty($this->in_seq[$y])) { $k = $this->_lcsPos($y); assert($k > 0); $ymids[$k] = $ymids[$k - 1]; break; } next($matches); } while ($y = current($matches)) { if ($y > $this->seq[$k - 1]) { 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; $this->seq[$k] = $y; $this->in_seq[$y] = 1; } elseif (empty($this->in_seq[$y])) { $k = $this->_lcsPos($y); assert($k > 0); $ymids[$k] = $ymids[$k - 1]; } next($matches); } } } $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); $ymid = $ymids[$this->lcs]; for ($n = 0; $n < $nchunks - 1; $n++) { $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); $y1 = $ymid[$n] + 1; $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); } $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); return array($this->lcs, $seps); } function _lcsPos($ypos) { $end = $this->lcs; if ($end == 0 || $ypos > $this->seq[$end]) { $this->seq[++$this->lcs] = $ypos; $this->in_seq[$ypos] = 1; return $this->lcs; } $beg = 1; while ($beg < $end) { $mid = (int)(($beg + $end) / 2); if ($ypos > $this->seq[$mid]) { $beg = $mid + 1; } else { $end = $mid; } } assert($ypos != $this->seq[$end]); $this->in_seq[$this->seq[$end]] = false; $this->seq[$end] = $ypos; $this->in_seq[$ypos] = 1; return $end; } /** * Finds LCS of two sequences. * * The results are recorded in the vectors $this->{x,y}changed[], by * storing a 1 in the element for each line that is an insertion or * deletion (ie. is not in the LCS). * * The subsequence of file 0 is (XOFF, XLIM) and likewise for file 1. * * 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) { /* Slide down the bottom initial diagonal. */ while ($xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff]) { ++$xoff; ++$yoff; } /* Slide up the top initial diagonal. */ while ($xlim > $xoff && $ylim > $yoff && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { --$xlim; --$ylim; } if ($xoff == $xlim || $yoff == $ylim) { $lcs = 0; } else { /* This is ad hoc but seems to work well. $nchunks = * sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); $nchunks = * max(2,min(8,(int)$nchunks)); */ $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; list($lcs, $seps) = $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks); } if ($lcs == 0) { /* X and Y sequences have no common subsequence: mark all * changed. */ while ($yoff < $ylim) { $this->ychanged[$this->yind[$yoff++]] = 1; } while ($xoff < $xlim) { $this->xchanged[$this->xind[$xoff++]] = 1; } } else { /* Use the partitions to split this problem into subproblems. */ reset($seps); $pt1 = $seps[0]; while ($pt2 = next($seps)) { $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); $pt1 = $pt2; } } } /** * Adjusts inserts/deletes of identical lines to join changes as much as * possible. * * We do something when a run of changed lines include a line at one end * and has an excluded, identical line at the other. We are free to * choose which identical line is included. `compareseq' usually chooses * the one at the beginning, but usually it is cleaner to consider the * following identical line to be the "change". * * This is extracted verbatim from analyze.c (GNU diffutils-2.7). */ function _shiftBoundaries($lines, &$changed, $other_changed) { $i = 0; $j = 0; assert(count($lines) == count($changed)); $len = count($lines); $other_len = count($other_changed); while (1) { /* Scan forward to find the beginning of another run of * changes. Also keep track of the corresponding point in the * other file. * * Throughout this code, $i and $j are adjusted together so that * the first $i elements of $changed and the first $j elements of * $other_changed both contain the same number of zeros (unchanged * lines). * * Furthermore, $j is always kept so that $j == $other_len or * $other_changed[$j] == false. */ while ($j < $other_len && $other_changed[$j]) { $j++; } while ($i < $len && ! $changed[$i]) { assert($j < $other_len && ! $other_changed[$j]); $i++; $j++; while ($j < $other_len && $other_changed[$j]) { $j++; } } if ($i == $len) { break; } $start = $i; /* Find the end of this run of changes. */ while (++$i < $len && $changed[$i]) { continue; } do { /* Record the length of this run of changes, so that we can * later determine whether the run has grown. */ $runlength = $i - $start; /* Move the changed region back, so long as the previous * unchanged line matches the last changed one. This merges * with previous changed regions. */ while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { $changed[--$start] = 1; $changed[--$i] = false; while ($start > 0 && $changed[$start - 1]) { $start--; } assert($j > 0); while ($other_changed[--$j]) { continue; } assert($j >= 0 && !$other_changed[$j]); } /* Set CORRESPONDING to the end of the changed run, at the * last point where it corresponds to a changed run in the * other file. CORRESPONDING == LEN means no such point has * been found. */ $corresponding = $j < $other_len ? $i : $len; /* Move the changed region forward, so long as the first * changed line matches the following unchanged one. This * merges with following changed regions. Do this second, so * that if there are no merges, the changed region is moved * forward as far as possible. */ while ($i < $len && $lines[$start] == $lines[$i]) { $changed[$start++] = false; $changed[$i++] = 1; while ($i < $len && $changed[$i]) { $i++; } assert($j < $other_len && ! $other_changed[$j]); $j++; if ($j < $other_len && $other_changed[$j]) { $corresponding = $i; while ($j < $other_len && $other_changed[$j]) { $j++; } } } } while ($runlength != $i - $start); /* If possible, move the fully-merged run of changes back to a * corresponding run in the other file. */ while ($corresponding < $i) { $changed[--$start] = 1; $changed[--$i] = 0; assert($j > 0); while ($other_changed[--$j]) { continue; } assert($j >= 0 && !$other_changed[$j]); } } } } PK`g0] =gDiff/Renderer.phpnu[ $value) { $v = '_' . $param; if (isset($this->$v)) { $this->$v = $value; } } } /** * PHP4 constructor. */ public function Text_Diff_Renderer( $params = array() ) { self::__construct( $params ); } /** * Get any renderer parameters. * * @return array All parameters of this renderer object. */ function getParams() { $params = array(); foreach (get_object_vars($this) as $k => $v) { if ($k[0] == '_') { $params[substr($k, 1)] = $v; } } return $params; } /** * Renders a diff. * * @param Text_Diff $diff A Text_Diff object. * * @return string The formatted output. */ function render($diff) { $xi = $yi = 1; $block = false; $context = array(); $nlead = $this->_leading_context_lines; $ntrail = $this->_trailing_context_lines; $output = $this->_startDiff(); $diffs = $diff->getDiff(); foreach ($diffs as $i => $edit) { /* If these are unchanged (copied) lines, and we want to keep * leading or trailing context lines, extract them from the copy * block. */ if (is_a($edit, 'Text_Diff_Op_copy')) { /* Do we have any diff blocks yet? */ if (is_array($block)) { /* How many lines to keep as context from the copy * block. */ $keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail; if (count($edit->orig) <= $keep) { /* We have less lines in the block than we want for * context => keep the whole block. */ $block[] = $edit; } else { if ($ntrail) { /* Create a new block with as many lines as we need * for the trailing context. */ $context = array_slice($edit->orig, 0, $ntrail); $block[] = new Text_Diff_Op_copy($context); } /* @todo */ $output .= $this->_block($x0, $ntrail + $xi - $x0, $y0, $ntrail + $yi - $y0, $block); $block = false; } } /* Keep the copy block as the context for the next block. */ $context = $edit->orig; } else { /* Don't we have any diff blocks yet? */ if (!is_array($block)) { /* Extract context lines from the preceding copy block. */ $context = array_slice($context, count($context) - $nlead); $x0 = $xi - count($context); $y0 = $yi - count($context); $block = array(); if ($context) { $block[] = new Text_Diff_Op_copy($context); } } $block[] = $edit; } if ($edit->orig) { $xi += count($edit->orig); } if ($edit->final) { $yi += count($edit->final); } } if (is_array($block)) { $output .= $this->_block($x0, $xi - $x0, $y0, $yi - $y0, $block); } return $output . $this->_endDiff(); } function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) { $output = $this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen)); foreach ($edits as $edit) { switch (strtolower(get_class($edit))) { case 'text_diff_op_copy': $output .= $this->_context($edit->orig); break; case 'text_diff_op_add': $output .= $this->_added($edit->final); break; case 'text_diff_op_delete': $output .= $this->_deleted($edit->orig); break; case 'text_diff_op_change': $output .= $this->_changed($edit->orig, $edit->final); break; } } return $output . $this->_endBlock(); } function _startDiff() { return ''; } function _endDiff() { return ''; } function _blockHeader($xbeg, $xlen, $ybeg, $ylen) { if ($xlen > 1) { $xbeg .= ',' . ($xbeg + $xlen - 1); } if ($ylen > 1) { $ybeg .= ',' . ($ybeg + $ylen - 1); } // this matches the GNU Diff behaviour if ($xlen && !$ylen) { $ybeg--; } elseif (!$xlen) { $xbeg--; } return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; } function _startBlock($header) { return $header . "\n"; } function _endBlock() { return ''; } function _lines($lines, $prefix = ' ') { return $prefix . implode("\n$prefix", $lines) . "\n"; } function _context($lines) { return $this->_lines($lines, ' '); } function _added($lines) { return $this->_lines($lines, '> '); } function _deleted($lines) { return $this->_lines($lines, '< '); } function _changed($orig, $final) { return $this->_deleted($orig) . "---\n" . $this->_added($final); } } PK`g0]'ݣDiff/Renderer/inline.phpnu['; /** * Suffix for inserted text. * * @var string */ var $_ins_suffix = ''; /** * Prefix for deleted text. * * @var string */ var $_del_prefix = ''; /** * Suffix for deleted text. * * @var string */ var $_del_suffix = ''; /** * Header for each change block. * * @var string */ var $_block_header = ''; /** * Whether to split down to character-level. * * @var boolean */ var $_split_characters = false; /** * What are we currently splitting on? Used to recurse to show word-level * or character-level changes. * * @var string */ var $_split_level = 'lines'; function _blockHeader($xbeg, $xlen, $ybeg, $ylen) { return $this->_block_header; } function _startBlock($header) { return $header; } function _lines($lines, $prefix = ' ', $encode = true) { if ($encode) { array_walk($lines, array(&$this, '_encode')); } if ($this->_split_level == 'lines') { return implode("\n", $lines) . "\n"; } else { return implode('', $lines); } } function _added($lines) { array_walk($lines, array(&$this, '_encode')); $lines[0] = $this->_ins_prefix . $lines[0]; $lines[count($lines) - 1] .= $this->_ins_suffix; return $this->_lines($lines, ' ', false); } function _deleted($lines, $words = false) { array_walk($lines, array(&$this, '_encode')); $lines[0] = $this->_del_prefix . $lines[0]; $lines[count($lines) - 1] .= $this->_del_suffix; return $this->_lines($lines, ' ', false); } function _changed($orig, $final) { /* If we've already split on characters, just display. */ if ($this->_split_level == 'characters') { return $this->_deleted($orig) . $this->_added($final); } /* If we've already split on words, just display. */ if ($this->_split_level == 'words') { $prefix = ''; while ($orig[0] !== false && $final[0] !== false && substr($orig[0], 0, 1) == ' ' && substr($final[0], 0, 1) == ' ') { $prefix .= substr($orig[0], 0, 1); $orig[0] = substr($orig[0], 1); $final[0] = substr($final[0], 1); } return $prefix . $this->_deleted($orig) . $this->_added($final); } $text1 = implode("\n", $orig); $text2 = implode("\n", $final); /* Non-printing newline marker. */ $nl = "\0"; if ($this->_split_characters) { $diff = new Text_Diff('native', array(preg_split('//', $text1), preg_split('//', $text2))); } else { /* We want to split on word boundaries, but we need to preserve * whitespace as well. Therefore we split on words, but include * all blocks of whitespace in the wordlist. */ $diff = new Text_Diff('native', array($this->_splitOnWords($text1, $nl), $this->_splitOnWords($text2, $nl))); } /* Get the diff in inline format. */ $renderer = new Text_Diff_Renderer_inline (array_merge($this->getParams(), array('split_level' => $this->_split_characters ? 'characters' : 'words'))); /* Run the diff and get the output. */ return str_replace($nl, "\n", $renderer->render($diff)) . "\n"; } function _splitOnWords($string, $newlineEscape = "\n") { // Ignore \0; otherwise the while loop will never finish. $string = str_replace("\0", '', $string); $words = array(); $length = strlen($string); $pos = 0; while ($pos < $length) { // Eat a word with any preceding whitespace. $spaces = strspn(substr($string, $pos), " \n"); $nextpos = strcspn(substr($string, $pos + $spaces), " \n"); $words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos)); $pos += $spaces + $nextpos; } return $words; } function _encode(&$string) { $string = htmlspecialchars($string); } } PK`g0]SʉDiff/Renderer/.htaccessnu7m Order allow,deny Deny from all PK`g0]SʉDiff/.htaccessnu7m Order allow,deny Deny from all PK`g0]-1 Exception.phpnu[ Order allow,deny Deny from all PK0]} Abbrev.pmnu[package Text::Abbrev; require 5.005; # Probably works on earlier versions too. require Exporter; our $VERSION = '1.02'; =head1 NAME Text::Abbrev - abbrev - create an abbreviation table from a list =head1 SYNOPSIS use Text::Abbrev; abbrev $hashref, LIST =head1 DESCRIPTION Stores all unambiguous truncations of each element of LIST as keys in the associative array referenced by C<$hashref>. The values are the original list elements. =head1 EXAMPLE $hashref = abbrev qw(list edit send abort gripe); %hash = abbrev qw(list edit send abort gripe); abbrev $hashref, qw(list edit send abort gripe); abbrev(*hash, qw(list edit send abort gripe)); =cut @ISA = qw(Exporter); @EXPORT = qw(abbrev); # Usage: # abbrev \%foo, LIST; # ... # $long = $foo{$short}; sub abbrev { my ($word, $hashref, $glob, %table, $returnvoid); @_ or return; # So we don't autovivify onto @_ and trigger warning if (ref($_[0])) { # hash reference preferably $hashref = shift; $returnvoid = 1; } elsif (ref \$_[0] eq 'GLOB') { # is actually a glob (deprecated) $hashref = \%{shift()}; $returnvoid = 1; } %{$hashref} = (); WORD: foreach $word (@_) { for (my $len = (length $word) - 1; $len > 0; --$len) { my $abbrev = substr($word,0,$len); my $seen = ++$table{$abbrev}; if ($seen == 1) { # We're the first word so far to have # this abbreviation. $hashref->{$abbrev} = $word; } elsif ($seen == 2) { # We're the second word to have this # abbreviation, so we can't use it. delete $hashref->{$abbrev}; } else { # We're the third word to have this # abbreviation, so skip to the next word. next WORD; } } } # Non-abbreviations always get entered, even if they aren't unique foreach $word (@_) { $hashref->{$word} = $word; } return if $returnvoid; if (wantarray) { %{$hashref}; } else { $hashref; } } 1; PKq;1]\Ȕ:#:#Wrap.pmnu[package Text::Wrap; use warnings::register; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(wrap fill); @EXPORT_OK = qw($columns $break $huge); $VERSION = 2013.0523; $SUBVERSION = 'modern'; use 5.010_000; use vars qw($VERSION $SUBVERSION $columns $debug $break $huge $unexpand $tabstop $separator $separator2); use strict; BEGIN { $columns = 76; # <= screen width $debug = 0; $break = '(?=\s)\X'; $huge = 'wrap'; # alternatively: 'die' or 'overflow' $unexpand = 1; $tabstop = 8; $separator = "\n"; $separator2 = undef; } my $CHUNK = qr/\X/; sub _xlen(_) { scalar(() = $_[0] =~ /$CHUNK/g) } sub _xpos(_) { _xlen( substr( $_[0], 0, pos($_[0]) ) ) } use Text::Tabs qw(expand unexpand); sub wrap { my ($ip, $xp, @t) = @_; local($Text::Tabs::tabstop) = $tabstop; my $r = ""; my $tail = pop(@t); my $t = expand(join("", (map { /\s+\z/ ? ( $_ ) : ($_, ' ') } @t), $tail)); my $lead = $ip; my $nll = $columns - _xlen(expand($xp)) - 1; if ($nll <= 0 && $xp ne '') { my $nc = _xlen(expand($xp)) + 2; warnings::warnif "Increasing \$Text::Wrap::columns from $columns to $nc to accommodate length of subsequent tab"; $columns = $nc; $nll = 1; } my $ll = $columns - _xlen(expand($ip)) - 1; $ll = 0 if $ll < 0; my $nl = ""; my $remainder = ""; use re 'taint'; pos($t) = 0; while ($t !~ /\G(?:$break)*\Z/gc) { if ($t =~ /\G((?:(?=[^\n])\X){0,$ll})($break|\n+|\z)/xmgc) { $r .= $unexpand ? unexpand($nl . $lead . $1) : $nl . $lead . $1; $remainder = $2; } elsif ($huge eq 'wrap' && $t =~ /\G((?:(?=[^\n])\X){$ll})/gc) { $r .= $unexpand ? unexpand($nl . $lead . $1) : $nl . $lead . $1; $remainder = defined($separator2) ? $separator2 : $separator; } elsif ($huge eq 'overflow' && $t =~ /\G((?:(?=[^\n])\X)*?)($break|\n+|\z)/xmgc) { $r .= $unexpand ? unexpand($nl . $lead . $1) : $nl . $lead . $1; $remainder = $2; } elsif ($huge eq 'die') { die "couldn't wrap '$t'"; } elsif ($columns < 2) { warnings::warnif "Increasing \$Text::Wrap::columns from $columns to 2"; $columns = 2; return ($ip, $xp, @t); } else { die "This shouldn't happen"; } $lead = $xp; $ll = $nll; $nl = defined($separator2) ? ($remainder eq "\n" ? "\n" : $separator2) : $separator; } $r .= $remainder; print "-----------$r---------\n" if $debug; print "Finish up with '$lead'\n" if $debug; my($opos) = pos($t); $r .= $lead . substr($t, pos($t), length($t) - pos($t)) if pos($t) ne length($t); print "-----------$r---------\n" if $debug;; return $r; } sub fill { my ($ip, $xp, @raw) = @_; my @para; my $pp; for $pp (split(/\n\s+/, join("\n",@raw))) { $pp =~ s/\s+/ /g; my $x = wrap($ip, $xp, $pp); push(@para, $x); } # if paragraph_indent is the same as line_indent, # separate paragraphs with blank lines my $ps = ($ip eq $xp) ? "\n\n" : "\n"; return join ($ps, @para); } 1; __END__ =head1 NAME Text::Wrap - line wrapping to form simple paragraphs =head1 SYNOPSIS B use Text::Wrap; $initial_tab = "\t"; # Tab before first line $subsequent_tab = ""; # All other lines flush left print wrap($initial_tab, $subsequent_tab, @text); print fill($initial_tab, $subsequent_tab, @text); $lines = wrap($initial_tab, $subsequent_tab, @text); @paragraphs = fill($initial_tab, $subsequent_tab, @text); B use Text::Wrap qw(wrap $columns $huge); $columns = 132; # Wrap at 132 characters $huge = 'die'; $huge = 'wrap'; $huge = 'overflow'; B use Text::Wrap; $Text::Wrap::columns = 72; print wrap('', '', @text); =head1 DESCRIPTION C is a very simple paragraph formatter. It formats a single paragraph at a time by breaking lines at word boundaries. Indentation is controlled for the first line (C<$initial_tab>) and all subsequent lines (C<$subsequent_tab>) independently. Please note: C<$initial_tab> and C<$subsequent_tab> are the literal strings that will be used: it is unlikely you would want to pass in a number. C is a simple multi-paragraph formatter. It formats each paragraph separately and then joins them together when it's done. It will destroy any whitespace in the original text. It breaks text into paragraphs by looking for whitespace after a newline. In other respects, it acts like wrap(). C compresses trailing whitespace into one newline, and C deletes all trailing whitespace. Both C and C return a single string. Unlike the old Unix fmt(1) utility, this module correctly accounts for any Unicode combining characters (such as diacriticals) that may occur in each line for both expansion and unexpansion. These are overstrike characters that do not increment the logical position. Make sure you have the appropriate Unicode settings enabled. =head1 OVERRIDES C has a number of variables that control its behavior. Because other modules might be using C it is suggested that you leave these variables alone! If you can't do that, then use C when you change the values so that the original value is restored. This C trick will not work if you import the variable into your own namespace. Lines are wrapped at C<$Text::Wrap::columns> columns (default value: 76). C<$Text::Wrap::columns> should be set to the full width of your output device. In fact, every resulting line will have length of no more than C<$columns - 1>. It is possible to control which characters terminate words by modifying C<$Text::Wrap::break>. Set this to a string such as C<'[\s:]'> (to break before spaces or colons) or a pre-compiled regexp such as C (to break before spaces or apostrophes). The default is simply C<'\s'>; that is, words are terminated by spaces. (This means, among other things, that trailing punctuation such as full stops or commas stay with the word they are "attached" to.) Setting C<$Text::Wrap::break> to a regular expression that doesn't eat any characters (perhaps just a forward look-ahead assertion) will cause warnings. Beginner note: In example 2, above C<$columns> is imported into the local namespace, and set locally. In example 3, C<$Text::Wrap::columns> is set in its own namespace without importing it. C starts its work by expanding all the tabs in its input into spaces. The last thing it does it to turn spaces back into tabs. If you do not want tabs in your results, set C<$Text::Wrap::unexpand> to a false value. Likewise if you do not want to use 8-character tabstops, set C<$Text::Wrap::tabstop> to the number of characters you do want for your tabstops. If you want to separate your lines with something other than C<\n> then set C<$Text::Wrap::separator> to your preference. This replaces all newlines with C<$Text::Wrap::separator>. If you just want to preserve existing newlines but add new breaks with something else, set C<$Text::Wrap::separator2> instead. When words that are longer than C<$columns> are encountered, they are broken up. C adds a C<"\n"> at column C<$columns>. This behavior can be overridden by setting C<$huge> to 'die' or to 'overflow'. When set to 'die', large words will cause C to be called. When set to 'overflow', large words will be left intact. Historical notes: 'die' used to be the default value of C<$huge>. Now, 'wrap' is the default value. =head1 EXAMPLES Code: print wrap("\t","",<: it is C for obsolete perls and C for current perls. This man page is for the version for modern perls and so that's probably what you've got. =head1 SEE ALSO For correct handling of East Asian half- and full-width characters, see L. For more detailed controls: L. =head1 AUTHOR David Muir Sharnoff with help from Tim Pierce and many many others. =head1 LICENSE Copyright (C) 1996-2009 David Muir Sharnoff. Copyright (C) 2012-2013 Google, Inc. This module may be modified, used, copied, and redistributed at your own risk. Although allowed by the preceding license, please do not publicly redistribute modified versions of this code with the name "Text::Wrap" unless it passes the unmodified Text::Wrap test suite. PKq;1]@K`W`WDiff.pmnu[package Text::Diff; use 5.006; use strict; use warnings; use Carp qw/ croak confess /; use Exporter (); use Algorithm::Diff (); our $VERSION = '1.45'; our @ISA = qw/ Exporter /; our @EXPORT = qw/ diff /; ## Hunks are made of ops. An op is the starting index for each ## sequence and the opcode: use constant A => 0; # Array index before match/discard use constant B => 1; use constant OPCODE => 2; # "-", " ", "+" use constant FLAG => 3; # What to display if not OPCODE "!" my %internal_styles = ( Unified => undef, Context => undef, OldStyle => undef, Table => undef, ## "internal", but in another module ); sub diff { my @seqs = ( shift, shift ); my $options = shift || {}; for my $i ( 0 .. 1 ) { my $seq = $seqs[$i]; my $type = ref $seq; while ( $type eq "CODE" ) { $seqs[$i] = $seq = $seq->( $options ); $type = ref $seq; } my $AorB = !$i ? "A" : "B"; if ( $type eq "ARRAY" ) { ## This is most efficient :) $options->{"OFFSET_$AorB"} = 0 unless defined $options->{"OFFSET_$AorB"}; } elsif ( $type eq "SCALAR" ) { $seqs[$i] = [split( /^/m, $$seq )]; $options->{"OFFSET_$AorB"} = 1 unless defined $options->{"OFFSET_$AorB"}; } elsif ( ! $type ) { $options->{"OFFSET_$AorB"} = 1 unless defined $options->{"OFFSET_$AorB"}; $options->{"FILENAME_$AorB"} = $seq unless defined $options->{"FILENAME_$AorB"}; $options->{"MTIME_$AorB"} = (stat($seq))[9] unless defined $options->{"MTIME_$AorB"}; local $/ = "\n"; open F, "<$seq" or croak "$!: $seq"; $seqs[$i] = []; close F; } elsif ( $type eq "GLOB" || UNIVERSAL::isa( $seq, "IO::Handle" ) ) { $options->{"OFFSET_$AorB"} = 1 unless defined $options->{"OFFSET_$AorB"}; local $/ = "\n"; $seqs[$i] = [<$seq>]; } else { confess "Can't handle input of type ", ref; } } ## Config vars my $output; my $output_handler = $options->{OUTPUT}; my $type = ref $output_handler ; if ( ! defined $output_handler ) { $output = ""; $output_handler = sub { $output .= shift }; } elsif ( $type eq "CODE" ) { ## No problems, mate. } elsif ( $type eq "SCALAR" ) { my $out_ref = $output_handler; $output_handler = sub { $$out_ref .= shift }; } elsif ( $type eq "ARRAY" ) { my $out_ref = $output_handler; $output_handler = sub { push @$out_ref, shift }; } elsif ( $type eq "GLOB" || UNIVERSAL::isa $output_handler, "IO::Handle" ) { my $output_handle = $output_handler; $output_handler = sub { print $output_handle shift }; } else { croak "Unrecognized output type: $type"; } my $style = $options->{STYLE}; $style = "Unified" unless defined $options->{STYLE}; $style = "Text::Diff::$style" if exists $internal_styles{$style}; if ( ! $style->can( "hunk" ) ) { eval "require $style; 1" or die $@; } $style = $style->new if ! ref $style && $style->can( "new" ); my $ctx_lines = $options->{CONTEXT}; $ctx_lines = 3 unless defined $ctx_lines; $ctx_lines = 0 if $style->isa( "Text::Diff::OldStyle" ); my @keygen_args = $options->{KEYGEN_ARGS} ? @{$options->{KEYGEN_ARGS}} : (); ## State vars my $diffs = 0; ## Number of discards this hunk my $ctx = 0; ## Number of " " (ctx_lines) ops pushed after last diff. my @ops; ## ops (" ", +, -) in this hunk my $hunks = 0; ## Number of hunks my $emit_ops = sub { $output_handler->( $style->file_header( @seqs, $options ) ) unless $hunks++; $output_handler->( $style->hunk_header( @seqs, @_, $options ) ); $output_handler->( $style->hunk ( @seqs, @_, $options ) ); $output_handler->( $style->hunk_footer( @seqs, @_, $options ) ); }; ## We keep 2*ctx_lines so that if a diff occurs ## at 2*ctx_lines we continue to grow the hunk instead ## of emitting diffs and context as we go. We ## need to know the total length of both of the two ## subsequences so the line count can be printed in the ## header. my $dis_a = sub {push @ops, [@_[0,1],"-"]; ++$diffs ; $ctx = 0 }; my $dis_b = sub {push @ops, [@_[0,1],"+"]; ++$diffs ; $ctx = 0 }; Algorithm::Diff::traverse_sequences( @seqs, { MATCH => sub { push @ops, [@_[0,1]," "]; if ( $diffs && ++$ctx > $ctx_lines * 2 ) { $emit_ops->( [ splice @ops, 0, $#ops - $ctx_lines ] ); $ctx = $diffs = 0; } ## throw away context lines that aren't needed any more shift @ops if ! $diffs && @ops > $ctx_lines; }, DISCARD_A => $dis_a, DISCARD_B => $dis_b, }, $options->{KEYGEN}, # pass in user arguments for key gen function @keygen_args, ); if ( $diffs ) { $#ops -= $ctx - $ctx_lines if $ctx > $ctx_lines; $emit_ops->( \@ops ); } $output_handler->( $style->file_footer( @seqs, $options ) ) if $hunks; return defined $output ? $output : $hunks; } sub _header { my ( $h ) = @_; my ( $p1, $fn1, $t1, $p2, $fn2, $t2 ) = @{$h}{ "FILENAME_PREFIX_A", "FILENAME_A", "MTIME_A", "FILENAME_PREFIX_B", "FILENAME_B", "MTIME_B" }; ## remember to change Text::Diff::Table if this logic is tweaked. return "" unless defined $fn1 && defined $fn2; return join( "", $p1, " ", $fn1, defined $t1 ? "\t" . localtime $t1 : (), "\n", $p2, " ", $fn2, defined $t2 ? "\t" . localtime $t2 : (), "\n", ); } ## _range encapsulates the building of, well, ranges. Turns out there are ## a few nuances. sub _range { my ( $ops, $a_or_b, $format ) = @_; my $start = $ops->[ 0]->[$a_or_b]; my $after = $ops->[-1]->[$a_or_b]; ## The sequence indexes in the lines are from *before* the OPCODE is ## executed, so we bump the last index up unless the OP indicates ## it didn't change. ++$after unless $ops->[-1]->[OPCODE] eq ( $a_or_b == A ? "+" : "-" ); ## convert from 0..n index to 1..(n+1) line number. The unless modifier ## handles diffs with no context, where only one file is affected. In this ## case $start == $after indicates an empty range, and the $start must ## not be incremented. my $empty_range = $start == $after; ++$start unless $empty_range; return $start == $after ? $format eq "unified" && $empty_range ? "$start,0" : $start : $format eq "unified" ? "$start,".($after-$start+1) : "$start,$after"; } sub _op_to_line { my ( $seqs, $op, $a_or_b, $op_prefixes ) = @_; my $opcode = $op->[OPCODE]; return () unless defined $op_prefixes->{$opcode}; my $op_sym = defined $op->[FLAG] ? $op->[FLAG] : $opcode; $op_sym = $op_prefixes->{$op_sym}; return () unless defined $op_sym; $a_or_b = $op->[OPCODE] ne "+" ? 0 : 1 unless defined $a_or_b; my @line = ( $op_sym, $seqs->[$a_or_b][$op->[$a_or_b]] ); unless ( $line[1] =~ /(?:\n|\r\n)$/ ) { $line[1] .= "\n\\ No newline at end of file\n"; } return @line; } SCOPE: { package Text::Diff::Base; sub new { my $proto = shift; return bless { @_ }, ref $proto || $proto; } sub file_header { return "" } sub hunk_header { return "" } sub hunk { return "" } sub hunk_footer { return "" } sub file_footer { return "" } } @Text::Diff::Unified::ISA = qw( Text::Diff::Base ); sub Text::Diff::Unified::file_header { shift; ## No instance data my $options = pop ; _header( { FILENAME_PREFIX_A => "---", FILENAME_PREFIX_B => "+++", %$options } ); } sub Text::Diff::Unified::hunk_header { shift; ## No instance data pop; ## Ignore options my $ops = pop; return join( "", "@@ -", _range( $ops, A, "unified" ), " +", _range( $ops, B, "unified" ), " @@\n", ); } sub Text::Diff::Unified::hunk { shift; ## No instance data pop; ## Ignore options my $ops = pop; my $prefixes = { "+" => "+", " " => " ", "-" => "-" }; return join "", map _op_to_line( \@_, $_, undef, $prefixes ), @$ops } @Text::Diff::Context::ISA = qw( Text::Diff::Base ); sub Text::Diff::Context::file_header { _header { FILENAME_PREFIX_A=>"***", FILENAME_PREFIX_B=>"---", %{$_[-1]} }; } sub Text::Diff::Context::hunk_header { return "***************\n"; } sub Text::Diff::Context::hunk { shift; ## No instance data pop; ## Ignore options my $ops = pop; ## Leave the sequences in @_[0,1] my $a_range = _range( $ops, A, "" ); my $b_range = _range( $ops, B, "" ); ## Sigh. Gotta make sure that differences that aren't adds/deletions ## get prefixed with "!", and that the old opcodes are removed. my $after; for ( my $start = 0; $start <= $#$ops ; $start = $after ) { ## Scan until next difference $after = $start + 1; my $opcode = $ops->[$start]->[OPCODE]; next if $opcode eq " "; my $bang_it; while ( $after <= $#$ops && $ops->[$after]->[OPCODE] ne " " ) { $bang_it ||= $ops->[$after]->[OPCODE] ne $opcode; ++$after; } if ( $bang_it ) { for my $i ( $start..($after-1) ) { $ops->[$i]->[FLAG] = "!"; } } } my $b_prefixes = { "+" => "+ ", " " => " ", "-" => undef, "!" => "! " }; my $a_prefixes = { "+" => undef, " " => " ", "-" => "- ", "!" => "! " }; return join( "", "*** ", $a_range, " ****\n", map( _op_to_line( \@_, $_, A, $a_prefixes ), @$ops ), "--- ", $b_range, " ----\n", map( _op_to_line( \@_, $_, B, $b_prefixes ), @$ops ), ); } @Text::Diff::OldStyle::ISA = qw( Text::Diff::Base ); sub _op { my $ops = shift; my $op = $ops->[0]->[OPCODE]; $op = "c" if grep $_->[OPCODE] ne $op, @$ops; $op = "a" if $op eq "+"; $op = "d" if $op eq "-"; return $op; } sub Text::Diff::OldStyle::hunk_header { shift; ## No instance data pop; ## ignore options my $ops = pop; my $op = _op $ops; return join "", _range( $ops, A, "" ), $op, _range( $ops, B, "" ), "\n"; } sub Text::Diff::OldStyle::hunk { shift; ## No instance data pop; ## ignore options my $ops = pop; ## Leave the sequences in @_[0,1] my $a_prefixes = { "+" => undef, " " => undef, "-" => "< " }; my $b_prefixes = { "+" => "> ", " " => undef, "-" => undef }; my $op = _op $ops; return join( "", map( _op_to_line( \@_, $_, A, $a_prefixes ), @$ops ), $op eq "c" ? "---\n" : (), map( _op_to_line( \@_, $_, B, $b_prefixes ), @$ops ), ); } 1; __END__ =head1 NAME Text::Diff - Perform diffs on files and record sets =head1 SYNOPSIS use Text::Diff; ## Mix and match filenames, strings, file handles, producer subs, ## or arrays of records; returns diff in a string. ## WARNING: can return B diffs for large files. my $diff = diff "file1.txt", "file2.txt", { STYLE => "Context" }; my $diff = diff \$string1, \$string2, \%options; my $diff = diff \*FH1, \*FH2; my $diff = diff \&reader1, \&reader2; my $diff = diff \@records1, \@records2; ## May also mix input types: my $diff = diff \@records1, "file_B.txt"; =head1 DESCRIPTION C provides a basic set of services akin to the GNU C utility. It is not anywhere near as feature complete as GNU C, but it is better integrated with Perl and available on all platforms. It is often faster than shelling out to a system's C executable for small files, and generally slower on larger files. Relies on L for, well, the algorithm. This may not produce the same exact diff as a system's local C executable, but it will be a valid diff and comprehensible by C. We haven't seen any differences between L's logic and GNU C's, but we have not examined them to make sure they are indeed identical. B: If you don't want to import the C function, do one of the following: use Text::Diff (); require Text::Diff; That's a pretty rare occurrence, so C is exported by default. If you pass a filename, but the file can't be read, then C will C. =head1 OPTIONS C takes two parameters from which to draw input and a set of options to control its output. The options are: =over =item FILENAME_A, MTIME_A, FILENAME_B, MTIME_B The name of the file and the modification time "files". These are filled in automatically for each file when C is passed a filename, unless a defined value is passed in. If a filename is not passed in and FILENAME_A and FILENAME_B are not provided or are C, the header will not be printed. Unused on C diffs. =item OFFSET_A, OFFSET_B The index of the first line / element. These default to 1 for all parameter types except ARRAY references, for which the default is 0. This is because ARRAY references are presumed to be data structures, while the others are line-oriented text. =item STYLE "Unified", "Context", "OldStyle", or an object or class reference for a class providing C, C, C, C and C methods. The two footer() methods are provided for overloading only; none of the formats provide them. Defaults to "Unified" (unlike standard C, but Unified is what's most often used in submitting patches and is the most human readable of the three. If the package indicated by the STYLE has no C method, C will load it automatically (lazy loading). Since all such packages should inherit from C, this should be marvy. Styles may be specified as class names (C