/lib/sources/patch.class.php

https://github.com/saePixel/jcore · PHP · 242 lines · 205 code · 23 blank · 14 comment · 46 complexity · 453df89e488925ac29e57f43d58913e0 MD5 · raw file

  1. <?php
  2. ## PhpPatcher class
  3. # @author legolas558
  4. # @version 0.1.1
  5. # Licensed under GNU General Public License (GPL)
  6. #
  7. # Facility to merge unified diff files
  8. # First use Merge() and then ApplyPatch() to commit changes
  9. # files will be created, updated and/or deleted
  10. define('_PHPP_INVALID_INPUT', 'Invalid input');
  11. define('_PHPP_UNEXPECTED_EOF', 'Unexpected end of file');
  12. define('_PHPP_UNEXPECTED_ADD_LINE', 'Unexpected add line at line %d');
  13. define('_PHPP_UNEXPECTED_REMOVE_LINE', 'Unexpected remove line at line %d');
  14. define('_PHPP_INVALID_DIFF', 'Invalid unified diff block');
  15. define('_PHPP_FAILED_VERIFY', 'Failed source verification of file %s at line %d');
  16. class _patch {
  17. var $root;
  18. var $msg;
  19. var $sources = array();
  20. var $destinations = array();
  21. var $removals = array();
  22. var $newline = "\n";
  23. function __construct($root_path) {
  24. // if you specify a root path all paths will be intended as relative to it (and not written, too)
  25. $this->root = $root_path;
  26. }
  27. function &_get_source($src) {
  28. if (isset($this->sources[$src]))
  29. return $this->sources[$src];
  30. if (!is_readable($src)) {
  31. $n = null;
  32. return $n;
  33. }
  34. $this->sources[$src] = $this->_linesplit(file_get_contents($src));
  35. return $this->sources[$src];
  36. }
  37. function &_get_destin($dst, $src) {
  38. if (isset($this->destinations[$dst]))
  39. return $this->destinations[$dst];
  40. $this->destinations[$dst] = $this->_get_source($src);
  41. return $this->destinations[$dst];
  42. }
  43. // separate CR or CRLF lines
  44. function &_linesplit(&$data) {
  45. $lines = preg_split('/(\r\n)|(\r)|(\n)/', $data);
  46. return $lines;
  47. }
  48. function merge($udiff) {
  49. // (1) Separate the input into lines
  50. $lines = $this->_linesplit($udiff);
  51. if (!isset($lines)) {
  52. $this->msg = _PHPP_INVALID_INPUT;
  53. return false;
  54. }
  55. unset($udiff);
  56. $line = current($lines);
  57. do {
  58. if (strlen($line)<5)
  59. continue;
  60. // start recognition when a new diff block is found
  61. if (substr($line, 0, 4)!='--- ')
  62. continue;
  63. $p = strpos($line, "\t", 4);
  64. if ($p===false) $p = strlen($line);
  65. $src = $this->root.substr($line, 4, $p-4);
  66. $line = next($lines);
  67. if (!isset($line)) {
  68. $this->msg = _PHPP_UNEXPECTED_EOF;
  69. return false;
  70. }
  71. if (substr($line, 0, 4)!='+++ ') {
  72. $this->msg = _PHPP_INVALID_DIFF;
  73. return false;
  74. }
  75. $p = strpos($line, "\t", 4);
  76. if ($p===false) $p = strlen($line);
  77. $dst = $this->root.substr($line, 4, $p-4);
  78. $line = next($lines);
  79. if (!isset($line)) {
  80. $this->msg = _PHPP_UNEXPECTED_EOF;
  81. return false;
  82. }
  83. $done=0;
  84. while (preg_match('/@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@($)/A', $line, $m)) {
  85. if ($m[3]==='')
  86. $src_size = 1;
  87. else $src_size = (int)$m[3];
  88. if ($m[6]==='')
  89. $dst_size = 1;
  90. else $dst_size = (int)$m[6];
  91. if (!$this->_apply_diff($lines, $src, $dst,
  92. (int)$m[1], $src_size, (int)$m[4],
  93. $dst_size))
  94. return false;
  95. $done++;
  96. $line = next($lines);
  97. if ($line === FALSE)
  98. break 2;
  99. }
  100. if ($done==0) {
  101. $this->msg = _PHPP_INVALID_DIFF;
  102. return false;
  103. }
  104. } while (FALSE !== ($line = next($lines)));
  105. //NOTE: previously opened files are still cached
  106. return true;
  107. }
  108. function clearCache() {
  109. $this->sources = array();
  110. $this->destinations = array();
  111. $this->removals = array();
  112. }
  113. function applyPatch() {
  114. if (empty($this->destinations))
  115. return 0;
  116. $done = 0;
  117. $files = array_keys($this->destinations);
  118. foreach($files as $file) {
  119. $f = @fopen($file, 'w');
  120. if ($f===null)
  121. continue;
  122. fwrite($f, implode($this->newline, $this->destinations[$file]));
  123. fclose($f);
  124. $done++;
  125. }
  126. foreach($this->removals as $file) {
  127. if (@unlink($file))
  128. $done++;
  129. if (isset($this->sources[$file]))
  130. unset($this->sources[$file]);
  131. }
  132. $this->destinations = array(); // clear the destinations cache
  133. $this->removals = array();
  134. return $done;
  135. }
  136. function _apply_diff(&$lines, $src, $dst, $src_line, $src_size, $dst_line, $dst_size) {
  137. $src_line--;
  138. $dst_line--;
  139. $line = next($lines);
  140. if ($line === false) {
  141. $this->msg = _PHPP_UNEXPECTED_EOF;
  142. return false;
  143. }
  144. $source = array(); // source lines (old file)
  145. $destin = array(); // new lines (new file)
  146. $src_left = $src_size;
  147. $dst_left = $dst_size;
  148. do {
  149. if (!isset($line{0})) {
  150. $source[] = '';
  151. $destin[] = '';
  152. $src_left--;
  153. $dst_left--;
  154. continue;
  155. }
  156. if ($line{0}=='-') {
  157. if ($src_left==0) {
  158. $this->msg = sprintf(_PHPP_UNEXPECTED_REMOVE_LINE, key($lines));
  159. return false;
  160. }
  161. $source[] = substr($line, 1);
  162. $src_left--;
  163. } else if ($line{0}=='+') {
  164. if ($dst_left==0) {
  165. $this->msg = sprintf(_PHPP_UNEXPECTED_ADD_LINE, key($lines));
  166. return false;
  167. }
  168. $destin[] = substr($line, 1);
  169. $dst_left--;
  170. } else {
  171. if (!isset($line{1}))
  172. $line = '';
  173. else if ($line{0}=='\\') {
  174. if ($line=='\\ No newline at end of file')
  175. continue;
  176. } else
  177. $line = substr($line, 1);
  178. $source[] = $line;
  179. $destin[] = $line;
  180. $src_left--;
  181. $dst_left--;
  182. }
  183. if (($src_left==0) && ($dst_left==0)) {
  184. // now apply the patch, finally!
  185. if ($src_size>0) {
  186. $src_lines =& $this->_get_source($src);
  187. if (!isset($src_lines))
  188. return false;
  189. }
  190. if ($dst_size>0) {
  191. if ($src_size>0) {
  192. $dst_lines =& $this->_get_destin($dst, $src);
  193. if (!isset($dst_lines))
  194. return false;
  195. $src_bottom=$src_line+count($source);
  196. $dst_bottom=$dst_line+count($destin);
  197. for ($l=$src_line;$l<$src_bottom;$l++) {
  198. if ($src_lines[$l]!=$source[$l-$src_line]) {
  199. $this->msg = sprintf(_PHPP_FAILED_VERIFY, $src, $l);
  200. return false;
  201. }
  202. }
  203. array_splice($dst_lines, $dst_line, count($source), $destin);
  204. } else
  205. $this->destinations[$dst] = $destin;
  206. } else
  207. $this->removals[] = $src;
  208. return true;
  209. }
  210. } while (FALSE !== ($line = next($lines)));
  211. $this->msg = _PHPP_UNEXPECTED_EOF;
  212. return false;
  213. }
  214. function diff(&$latest, &$udiff) {
  215. $this->msg = 'Not available';
  216. return false;
  217. }
  218. }
  219. ?>