WhitespaceShell.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <?php
  2. namespace Tools\Shell;
  3. use Cake\Console\Shell;
  4. use Cake\Filesystem\Folder;
  5. use Cake\Utility\Inflector;
  6. use Cake\Core\Plugin;
  7. /**
  8. * Shell to remove superfluous whitespace.
  9. *
  10. * @author Mark Scherer
  11. * @license MIT
  12. */
  13. class WhitespaceShell extends Shell {
  14. /**
  15. * Each report: [0] => found, [1] => corrected
  16. *
  17. * @var array
  18. */
  19. public $report = array(
  20. 'leading' => array(0, 0),
  21. 'trailing' => array(0, 0)
  22. );
  23. /**
  24. * Whitespaces before or after <?php and ?>.
  25. * The latter should be removed from PHP files by the way.
  26. *
  27. * @return void
  28. */
  29. public function clean() {
  30. if (!empty($this->args[0])) {
  31. $folder = realpath($this->args[0]);
  32. } elseif ($this->params['plugin']) {
  33. $folder = Plugin::path(Inflector::classify($this->params['plugin']));
  34. } else {
  35. $folder = APP;
  36. }
  37. $App = new Folder($folder);
  38. $this->out("Checking *.php in " . $folder);
  39. $files = $App->findRecursive('.*\.php');
  40. $this->out('Found ' . count($files) . ' files.');
  41. $action = $this->in('Continue? [y]/[n]', array('y', 'n'), 'n');
  42. if ($action !== 'y') {
  43. return $this->error('Aborted');
  44. }
  45. $folders = array();
  46. foreach ($files as $file) {
  47. $errors = array();
  48. $action = '';
  49. $this->out('Processing ' . $file, 1, Shell::VERBOSE);
  50. $c = file_get_contents($file);
  51. if (preg_match('/^[\n\r|\n|\r|\s]+\<\?php/', $c)) {
  52. $errors[] = 'leading';
  53. }
  54. if (preg_match('/\?\>[\n\r|\n|\r|\s]+$/', $c)) {
  55. $errors[] = 'trailing';
  56. }
  57. if (empty($errors)) {
  58. continue;
  59. }
  60. foreach ($errors as $e) {
  61. $this->report[$e][0]++;
  62. }
  63. $this->out('');
  64. $this->out('contains ' . implode(' and ' , $errors) . ' whitespaces: ' . $this->shortPath($file));
  65. $dirname = dirname($file);
  66. if (in_array($dirname, $folders)) {
  67. $action = 'y';
  68. }
  69. while (empty($action)) {
  70. $action = $this->in('Remove? [y]/[n], [a] for all in this folder, [r] for all below, [*] for all files(!), [q] to quit', array('y', 'n', 'r', 'a', 'q', '*'), 'q');
  71. }
  72. if ($action === '*') {
  73. $action = 'y';
  74. } elseif ($action === 'a') {
  75. $action = 'y';
  76. $folders[] = $dirname;
  77. $this->out('All: ' . $dirname);
  78. }
  79. if ($action === 'q') {
  80. return $this->error('Abort... Done');
  81. }
  82. if ($action === 'y') {
  83. if (in_array('leading', $errors)) {
  84. $c = preg_replace('/^\s+\<\?php/', '<?php', $c);
  85. }
  86. if (in_array('trailing', $errors)) {
  87. $c = preg_replace('/\?\>\s+$/', '?>', $c);
  88. }
  89. file_put_contents($file, $c);
  90. foreach ($errors as $e) {
  91. $this->report[$e][1]++;
  92. }
  93. $this->out('fixed ' . implode(' and ' , $errors) . ' whitespaces: ' . $this->shortPath($file));
  94. }
  95. }
  96. // Report.
  97. $this->out('--------');
  98. $this->out('found ' . $this->report['leading'][0] . ' leading, ' . $this->report['trailing'][0] . ' trailing ws');
  99. $this->out('fixed ' . $this->report['leading'][1] . ' leading, ' . $this->report['trailing'][1] . ' trailing ws');
  100. }
  101. /**
  102. * Whitespaces at the end of the file
  103. *
  104. * @return void
  105. */
  106. public function eof() {
  107. if (!empty($this->args[0])) {
  108. $folder = realpath($this->args[0]);
  109. } else {
  110. $folder = APP;
  111. }
  112. $App = new Folder($folder);
  113. $this->out("Checking *.php in " . $folder);
  114. $files = $App->findRecursive('.*\.php');
  115. $this->out('Found ' . count($files) . ' files.');
  116. $action = $this->in('Continue? [y]/[n]', array('y', 'n'), 'n');
  117. if ($action !== 'y') {
  118. return $this->error('Aborted');
  119. }
  120. foreach ($files as $file) {
  121. $this->out('Processing ' . $file, 1, Shell::VERBOSE);
  122. $content = $store = file_get_contents($file);
  123. $newline = PHP_EOL;
  124. $x = substr_count($content, "\r\n");
  125. if ($x > 0) {
  126. $newline = "\r\n";
  127. } else {
  128. $newline = "\n";
  129. }
  130. // add one new line at the end
  131. $content = trim($content) . $newline;
  132. if ($content !== $store) {
  133. file_put_contents($file, $content);
  134. }
  135. }
  136. $this->out('Done');
  137. }
  138. public function getOptionParser() {
  139. $subcommandParser = array(
  140. 'options' => array(
  141. 'ext' => array(
  142. 'short' => 'e',
  143. 'help' => 'Specify extensions [php|txt|...]',
  144. 'default' => '',
  145. ),
  146. 'dry-run' => array(
  147. 'short' => 'd',
  148. 'help' => 'Dry run the clear command, no files will actually be deleted. Should be combined with verbose!',
  149. 'boolean' => true
  150. ),
  151. 'plugin' => array(
  152. 'short' => 'p',
  153. 'help' => 'Plugin',
  154. 'default' => '',
  155. ),
  156. )
  157. );
  158. return parent::getOptionParser()
  159. ->description('The Whitespace Shell removes uncessary/wrong whitespaces.
  160. Either provide a path as first argument, use -p PluginName or run it as it is for the complete APP dir.')
  161. ->addSubcommand('clean', array(
  162. 'help' => 'Detect and remove any leading/trailing whitespaces',
  163. 'parser' => $subcommandParser
  164. ))
  165. ->addSubcommand('eof', array(
  166. 'help' => 'Fix whitespace issues at the end of PHP files (a single newline as per coding standards)',
  167. 'parser' => $subcommandParser
  168. ));
  169. }
  170. }