public function getOpCodes() { $op_codes = array(); $file_a = new TempFile(); $file_b = new TempFile(); Filesystem::writeFile($file_a, implode('', $this->a)); Filesystem::writeFile($file_b, implode('', $this->b)); $diff_future = new ExecFuture('diff -U0 %s %s', $file_a, $file_b); list($err, $stdout, $stderr) = $diff_future->resolve(); if (!in_array($err, array(0, 1))) { throw new CommandException(pht('`diff` returned unexpected exit code %d', $err), $diff_future->getCommand(), $err, $stdout, $stderr); } foreach (phutil_split_lines($stdout) as $line) { $matches = null; $regexp = '/^@@ -(\\d+)(?:,(\\d+))? \\+(\\d+)(?:,(\\d+))? @@/'; if (!preg_match($regexp, $line, $matches)) { continue; } // We need to: // - normalize indices; the ones we get from `diff` are 1-indexed. // - account for how `diff` represents empty hunks -- essentially, it // attributes the change to the *previous line* (which may be 0!). This // differs from Python's difflib. // // http://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html: // If a hunk contains just one line, only its start line number // appears. Otherwise its line numbers look like 'start,count'. An // empty hunk is considered to start at the line that follows the // hunk. // // If a hunk and its context contain two or more lines, its line // numbers look like 'start,count'. Otherwise only its end line // number appears. An empty hunk is considered to end at the line // that precedes the hunk. $i = (int) $matches[1] - 1; $j = (int) $matches[3] - 1; if (isset($matches[2]) && $matches[2] !== '') { $li = (int) $matches[2]; } else { $li = 1; } if (isset($matches[4]) && $matches[4] !== '') { $lj = (int) $matches[4]; } else { $lj = 1; } if (!($li || $lj)) { throw new CommandException(pht('Malformed output from `diff`.'), $diff_future->getCommand(), $err, $stdout, $stderr); } if ($li === 0) { $op_codes[] = array('insert', $i + 1, $i + 1, $j, $j + $lj); } else { if ($lj === 0) { $op_codes[] = array('delete', $i, $i + $li, $j, $j); } else { $op_codes[] = array('replace', $i, $i + $li, $j, $j + $lj); } } } return $op_codes; }