PageRenderTime 40ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/CLI.php

http://github.com/iangreenleaf/Scisr
PHP | 351 lines | 249 code | 37 blank | 65 comment | 48 complexity | dcf996a4412a67973d3e3c95f345482a MD5 | raw file
Possible License(s): GPL-3.0, BSD-3-Clause
  1. <?php
  2. /**
  3. * Handles command line interaction for Scisr
  4. *
  5. * @todo We could extend PHP_CodeSniffer_CLI and make use of some of the parsing
  6. * stuff instead of rolling our own. We would gain some flexibility in where
  7. * flags can be placed in the list, and it would fit reasonably well with the
  8. * need for "scisr command [variable arity args]".
  9. *
  10. * Downsides are that it won't quite work out of the box ($argv overrides our
  11. * testing stuff), and it's not flexible about --flag=val vs. --flag val.
  12. */
  13. class Scisr_CLI implements Scisr_Output
  14. {
  15. const OPT_NONE = 0;
  16. const OPT_REQUIRED = 1;
  17. /**
  18. * If true, quit without running and show usage instead
  19. * @var boolean
  20. */
  21. private $showHelp = false;
  22. /**
  23. * When renaming a method, true if we should also rename children methods
  24. * @var boolean
  25. */
  26. private $withInheritance = true;
  27. public function __construct($output=null)
  28. {
  29. if ($output === null) {
  30. $output = new Scisr_Output_CLI();
  31. }
  32. $this->output = $output;
  33. $this->scisr = ScisrRunner::createRunner();
  34. $this->scisr->setOutput($this->output);
  35. }
  36. /**
  37. * Parse command line options
  38. * @param array $args array of options passed to the program
  39. */
  40. protected function parseOpts($args)
  41. {
  42. // Parse all other options
  43. $shortOptions = 'athi:e:';
  44. $longOptions = array('aggressive', 'timid', 'no-inheritance', 'help', 'ignore=', 'extensions=');
  45. $options = $this->getopt($args, $shortOptions, $longOptions);
  46. $unparsedOptions = $options[1];
  47. $this->parseOtherOpts($options[0]);
  48. if ($this->showHelp) {
  49. return;
  50. }
  51. $this->parseActionOpts($unparsedOptions);
  52. if (count($unparsedOptions) == 0) {
  53. throw new Exception('No paths provided to examine');
  54. }
  55. $this->scisr->addFiles($unparsedOptions);
  56. }
  57. private function parseActionOpts(&$params)
  58. {
  59. // Get the action name
  60. $action = $this->getArg($params);
  61. $actionOpts = array();
  62. switch ($action) {
  63. case 'rename-class':
  64. $oldName = $this->getArg($params);
  65. $newName = $this->getArg($params);
  66. $this->scisr->setRenameClass($oldName, $newName);
  67. break;
  68. case 'rename-method':
  69. $class = $this->getArg($params);
  70. $oldName = $this->getArg($params);
  71. $newName = $this->getArg($params);
  72. $this->scisr->setRenameMethod($class, $oldName, $newName, $this->withInheritance);
  73. break;
  74. case 'rename-file':
  75. $oldName = $this->getArg($params);
  76. $newName = $this->getArg($params);
  77. $this->scisr->setRenameFile($oldName, $newName);
  78. break;
  79. case 'rename-class-file':
  80. $oldName = $this->getArg($params);
  81. $newName = $this->getArg($params);
  82. $this->scisr->setRenameClassFile($oldName, $newName);
  83. break;
  84. case 'split-class-files':
  85. $outputPath = $this->getArg($params);
  86. $this->scisr->setSplitClassFiles($outputPath);
  87. break;
  88. default:
  89. throw new Exception("Command \"$action\" not recognized");
  90. }
  91. }
  92. private function getArg(&$params)
  93. {
  94. $arg = array_shift($params);
  95. if ($arg === null) {
  96. throw new Exception("Not enough arguments");
  97. }
  98. return $arg;
  99. }
  100. private function parseOtherOpts($params)
  101. {
  102. foreach ($params as $key => $value) {
  103. switch ($key) {
  104. case "a":
  105. case "aggressive":
  106. $this->scisr->setEditMode(ScisrRunner::MODE_AGGRESSIVE);
  107. break;
  108. case "no-inheritance":
  109. $this->withInheritance = false;
  110. break;
  111. case "t":
  112. case "timid":
  113. $this->scisr->setEditMode(ScisrRunner::MODE_TIMID);
  114. break;
  115. case "i":
  116. case "ignore":
  117. // We doctor and pass this value in to let phpcs run a pattern on it
  118. $fakekey = 'ignore=' . $value;
  119. $cli = new PHP_CodeSniffer_CLI();
  120. $result = $cli->processLongArgument($fakekey, null, array());
  121. $this->scisr->setIgnorePatterns($result['ignored']);
  122. break;
  123. case "e":
  124. case "extensions":
  125. // We doctor and pass this value in to let phpcs run a pattern on it
  126. $fakekey = 'extensions=' . $value;
  127. $cli = new PHP_CodeSniffer_CLI();
  128. $result = $cli->processLongArgument($fakekey, null, array());
  129. $this->scisr->setAllowedFileExtensions($result['extensions']);
  130. break;
  131. case "help":
  132. $this->showHelp = true;
  133. break;
  134. }
  135. }
  136. }
  137. /**
  138. * For testing use only. Dependency injection.
  139. * @ignore
  140. * @param ScisrRunner
  141. */
  142. public function setRunner($scisr)
  143. {
  144. $this->scisr = $scisr;
  145. }
  146. /**
  147. * Process the CLI arguments and run Scisr
  148. * @param array $args the command arguments - for use in testing only
  149. */
  150. public function process($args=null)
  151. {
  152. // Get options from the command line
  153. if ($args === null) {
  154. global $argv;
  155. $args = $argv;
  156. }
  157. // Remove our own filename
  158. array_shift($args);
  159. // Send to the options handler
  160. try {
  161. $this->parseOpts($args);
  162. if ($this->showHelp) {
  163. $this->printUsage();
  164. return 0;
  165. }
  166. } catch (Exception $e) {
  167. $this->outputString('Error: ' . $e->getMessage());
  168. $this->outputString("\n");
  169. $this->printUsage();
  170. return 2;
  171. }
  172. // Run Scisr
  173. $this->scisr->run();
  174. return 0;
  175. }
  176. // We proxy output through here for simplicity
  177. public function outputString($message)
  178. {
  179. $this->output->outputString($message);
  180. }
  181. /**
  182. * Parse command line options
  183. *
  184. * Believe me, I'm not happy to be reinventing the wheel here. It's just
  185. * that all the other wheels PHP and third parties have to offer in this
  186. * department are inferior. This wheel is inferior too, but at least in ways
  187. * that work for me.
  188. *
  189. * @param array $args the array of arguments from the command line
  190. * @param string $shortOpts short options as proscribed by PHP's getopt()
  191. * @param string $longOpts long options as proscribed by PHP's getopt()
  192. */
  193. protected function getopt($args, $shortOpts, $longOpts)
  194. {
  195. $longOpts = $this->parseLongOpts($longOpts);
  196. $shortOpts = $this->parseShortOpts($shortOpts);
  197. $len = count($args);
  198. $i = 0;
  199. $parsedOptions = array();
  200. $nonOptions = array();
  201. while ($i < $len) {
  202. $curr = $args[$i];
  203. if (substr($curr, 0, 2) == '--') {
  204. $opt = substr($curr, 2);
  205. // If there is a "=", split this argument into opt and value
  206. if (($pos = strpos($opt, '=')) !== false) {
  207. $value = substr($opt, $pos + 1);
  208. $opt = substr($opt, 0, $pos);
  209. if ($longOpts[$opt] != self::OPT_REQUIRED) {
  210. throw new Exception("Value given for option \"$opt\", which does not accept a value");
  211. }
  212. } else if ($longOpts[$opt] == self::OPT_REQUIRED) {
  213. $value = $args[++$i];
  214. } else {
  215. $value = null;
  216. }
  217. if (!array_key_exists($opt, $longOpts)) {
  218. throw new Exception("Option \"$opt\" not recognized");
  219. }
  220. $parsedOptions[$opt] = $value;
  221. } else if (substr($curr, 0, 1) == '-') {
  222. $opt = substr($curr, 1, 1);
  223. if (strlen($curr) > 2) {
  224. $value = substr($curr, 2);
  225. if ($shortOpts[$opt] != self::OPT_REQUIRED) {
  226. throw new Exception("Value given for option \"$opt\", which does not accept a value");
  227. }
  228. } else if ($shortOpts[$opt] == self::OPT_REQUIRED) {
  229. $value = $args[++$i];
  230. } else {
  231. $value = null;
  232. }
  233. if (!array_key_exists($opt, $shortOpts)) {
  234. throw new Exception("Option \"$opt\" not recognized");
  235. }
  236. $parsedOptions[$opt] = $value;
  237. } else {
  238. $nonOptions[] = $curr;
  239. }
  240. $i++;
  241. }
  242. return array($parsedOptions, $nonOptions);
  243. }
  244. /**
  245. * Helper function to {@link $this->getopt()}
  246. */
  247. private function parseShortOpts($opts)
  248. {
  249. $result = array();
  250. $opts = preg_split('/(\w:?:?)/', $opts, null, PREG_SPLIT_DELIM_CAPTURE);
  251. foreach ($opts as $opt) {
  252. if ($opt == '') continue;
  253. if (substr($opt, -2) == '::') {
  254. // We aren't handling this case for now
  255. } else if (substr($opt, -1) == ':') {
  256. $name = substr($opt, 0, -1);
  257. $req = self::OPT_REQUIRED;
  258. } else {
  259. $name = $opt;
  260. $req = self::OPT_NONE;
  261. }
  262. $result[$name] = $req;
  263. }
  264. return $result;
  265. }
  266. /**
  267. * Helper function to {@link $this->getopt()}
  268. */
  269. private function parseLongOpts($opts)
  270. {
  271. $result = array();
  272. foreach ($opts as $opt) {
  273. if (substr($opt, -2) == '==') {
  274. // We aren't handling this case for now
  275. } else if (substr($opt, -1) == '=') {
  276. $name = substr($opt, 0, -1);
  277. $req = self::OPT_REQUIRED;
  278. } else {
  279. $name = $opt;
  280. $req = self::OPT_NONE;
  281. }
  282. $result[$name] = $req;
  283. }
  284. return $result;
  285. }
  286. /**
  287. * Print usage information to our output handler
  288. */
  289. public function printUsage()
  290. {
  291. $usage = <<<EOL
  292. Usage:
  293. scisr rename-class OldName NewName [options] [files]
  294. scisr rename-method OwningClassName oldMethodName newMethodName [options] [files]
  295. scisr rename-file old/file_name new/dir/new_file_name [options] [files]
  296. scisr rename-class-file OldName NewName [options] [files]
  297. scisr split-class-files OutputDir [options] [files]
  298. [files] is any number of files and/or directories to be searched and modified.
  299. Options:
  300. --no-inheritance Only with rename-method. Do not rename method in descendants of
  301. the given class.
  302. -t, --timid Do not make changes to the files, just list filenames
  303. with line numbers.
  304. -a, --aggressive Make changes even when we're not sure they're correct.
  305. -e<extensions>, --extensions=<extensions>
  306. Specify a comma-separated list of allowed file extensions.
  307. -i<patterns>, --ignore=<patterns>
  308. Specify a comma-separated list of patterns used to
  309. ignore directories and files.
  310. -h, --help Print usage instructions.
  311. EOL;
  312. $this->outputString($usage);
  313. }
  314. }