ConsoleOptionParserTest.php 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 2.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Console;
  17. use Cake\Console\ConsoleInputArgument;
  18. use Cake\Console\ConsoleInputOption;
  19. use Cake\Console\ConsoleInputSubcommand;
  20. use Cake\Console\ConsoleOptionParser;
  21. use Cake\Console\Exception\InvalidOptionException;
  22. use Cake\TestSuite\TestCase;
  23. /**
  24. * ConsoleOptionParserTest
  25. */
  26. class ConsoleOptionParserTest extends TestCase
  27. {
  28. /**
  29. * test setting the console description
  30. *
  31. * @return void
  32. */
  33. public function testDescription()
  34. {
  35. $parser = new ConsoleOptionParser('test', false);
  36. $result = $parser->setDescription('A test');
  37. $this->assertEquals($parser, $result, 'Setting description is not chainable');
  38. $this->assertSame('A test', $parser->getDescription(), 'getting value is wrong.');
  39. $result = $parser->setDescription(['A test', 'something']);
  40. $this->assertEquals("A test\nsomething", $parser->getDescription(), 'getting value is wrong.');
  41. }
  42. /**
  43. * test setting and getting the console epilog
  44. *
  45. * @return void
  46. */
  47. public function testEpilog()
  48. {
  49. $parser = new ConsoleOptionParser('test', false);
  50. $result = $parser->setEpilog('A test');
  51. $this->assertEquals($parser, $result, 'Setting epilog is not chainable');
  52. $this->assertSame('A test', $parser->getEpilog(), 'getting value is wrong.');
  53. $result = $parser->setEpilog(['A test', 'something']);
  54. $this->assertEquals("A test\nsomething", $parser->getEpilog(), 'getting value is wrong.');
  55. }
  56. /**
  57. * test adding an option returns self.
  58. *
  59. * @return void
  60. */
  61. public function testAddOptionReturnSelf()
  62. {
  63. $parser = new ConsoleOptionParser('test', false);
  64. $result = $parser->addOption('test');
  65. $this->assertEquals($parser, $result, 'Did not return $this from addOption');
  66. }
  67. /**
  68. * test removing an option
  69. *
  70. * @return void
  71. */
  72. public function testRemoveOption()
  73. {
  74. $parser = new ConsoleOptionParser('test', false);
  75. $result = $parser->addOption('test')
  76. ->removeOption('test')
  77. ->removeOption('help');
  78. $this->assertSame($parser, $result, 'Did not return $this from removeOption');
  79. $this->assertEquals([], $result->options());
  80. }
  81. /**
  82. * test adding an option and using the long value for parsing.
  83. *
  84. * @return void
  85. */
  86. public function testAddOptionLong()
  87. {
  88. $parser = new ConsoleOptionParser('test', false);
  89. $parser->addOption('test', [
  90. 'short' => 't',
  91. ]);
  92. $result = $parser->parse(['--test', 'value']);
  93. $this->assertEquals(['test' => 'value', 'help' => false], $result[0], 'Long parameter did not parse out');
  94. }
  95. /**
  96. * test adding an option with a zero value
  97. *
  98. * @return void
  99. */
  100. public function testAddOptionZero()
  101. {
  102. $parser = new ConsoleOptionParser('test', false);
  103. $parser->addOption('count', []);
  104. $result = $parser->parse(['--count', '0']);
  105. $this->assertEquals(['count' => '0', 'help' => false], $result[0], 'Zero parameter did not parse out');
  106. }
  107. /**
  108. * test addOption with an object.
  109. *
  110. * @return void
  111. */
  112. public function testAddOptionObject()
  113. {
  114. $parser = new ConsoleOptionParser('test', false);
  115. $parser->addOption(new ConsoleInputOption('test', 't'));
  116. $result = $parser->parse(['--test=value']);
  117. $this->assertEquals(['test' => 'value', 'help' => false], $result[0], 'Long parameter did not parse out');
  118. }
  119. /**
  120. * test adding an option and using the long value for parsing.
  121. *
  122. * @return void
  123. */
  124. public function testAddOptionLongEquals()
  125. {
  126. $parser = new ConsoleOptionParser('test', false);
  127. $parser->addOption('test', [
  128. 'short' => 't',
  129. ]);
  130. $result = $parser->parse(['--test=value']);
  131. $this->assertEquals(['test' => 'value', 'help' => false], $result[0], 'Long parameter did not parse out');
  132. }
  133. /**
  134. * test adding an option and using the default.
  135. *
  136. * @return void
  137. */
  138. public function testAddOptionDefault()
  139. {
  140. $parser = new ConsoleOptionParser('test', false);
  141. $parser->addOption('test', [
  142. 'default' => 'default value',
  143. ]);
  144. $result = $parser->parse(['--test']);
  145. $this->assertEquals(['test' => 'default value', 'help' => false], $result[0], 'Default value did not parse out');
  146. $parser = new ConsoleOptionParser('test', false);
  147. $parser->addOption('test', [
  148. 'default' => 'default value',
  149. ]);
  150. $result = $parser->parse([]);
  151. $this->assertEquals(['test' => 'default value', 'help' => false], $result[0], 'Default value did not parse out');
  152. }
  153. /**
  154. * test adding an option and using the short value for parsing.
  155. *
  156. * @return void
  157. */
  158. public function testAddOptionShort()
  159. {
  160. $parser = new ConsoleOptionParser('test', false);
  161. $parser->addOption('test', [
  162. 'short' => 't',
  163. ]);
  164. $result = $parser->parse(['-t', 'value']);
  165. $this->assertEquals(['test' => 'value', 'help' => false], $result[0], 'Short parameter did not parse out');
  166. }
  167. /**
  168. * test adding an option and using the short value for parsing.
  169. *
  170. * @return void
  171. */
  172. public function testAddOptionWithMultipleShort()
  173. {
  174. $parser = new ConsoleOptionParser('test', false);
  175. $parser->addOption('source', [
  176. 'multiple' => true,
  177. 'short' => 's',
  178. ]);
  179. $result = $parser->parse(['-s', 'mysql', '-s', 'postgres']);
  180. $this->assertEquals(
  181. [
  182. 'source' => ['mysql', 'postgres'],
  183. 'help' => false,
  184. ],
  185. $result[0],
  186. 'Short parameter did not parse out'
  187. );
  188. }
  189. /**
  190. * Test that adding an option using a two letter short value causes an exception.
  191. * As they will not parse correctly.
  192. *
  193. * @return void
  194. */
  195. public function testAddOptionShortOneLetter()
  196. {
  197. $this->expectException(\Cake\Console\Exception\ConsoleException::class);
  198. $parser = new ConsoleOptionParser('test', false);
  199. $parser->addOption('test', ['short' => 'te']);
  200. }
  201. /**
  202. * test adding and using boolean options.
  203. *
  204. * @return void
  205. */
  206. public function testAddOptionBoolean()
  207. {
  208. $parser = new ConsoleOptionParser('test', false);
  209. $parser->addOption('test', [
  210. 'boolean' => true,
  211. ]);
  212. $result = $parser->parse(['--test', 'value']);
  213. $expected = [['test' => true, 'help' => false], ['value']];
  214. $this->assertEquals($expected, $result);
  215. $result = $parser->parse(['value']);
  216. $expected = [['test' => false, 'help' => false], ['value']];
  217. $this->assertEquals($expected, $result);
  218. }
  219. /**
  220. * test adding an multiple shorts.
  221. *
  222. * @return void
  223. */
  224. public function testAddOptionMultipleShort()
  225. {
  226. $parser = new ConsoleOptionParser('test', false);
  227. $parser->addOption('test', ['short' => 't', 'boolean' => true])
  228. ->addOption('file', ['short' => 'f', 'boolean' => true])
  229. ->addOption('output', ['short' => 'o', 'boolean' => true]);
  230. $result = $parser->parse(['-o', '-t', '-f']);
  231. $expected = ['file' => true, 'test' => true, 'output' => true, 'help' => false];
  232. $this->assertEquals($expected, $result[0], 'Short parameter did not parse out');
  233. $result = $parser->parse(['-otf']);
  234. $this->assertEquals($expected, $result[0], 'Short parameter did not parse out');
  235. }
  236. /**
  237. * test multiple options at once.
  238. *
  239. * @return void
  240. */
  241. public function testMultipleOptions()
  242. {
  243. $parser = new ConsoleOptionParser('test', false);
  244. $parser->addOption('test')
  245. ->addOption('connection')
  246. ->addOption('table', ['short' => 't', 'default' => true]);
  247. $result = $parser->parse(['--test', 'value', '-t', '--connection', 'postgres']);
  248. $expected = ['test' => 'value', 'table' => true, 'connection' => 'postgres', 'help' => false];
  249. $this->assertEquals($expected, $result[0], 'multiple options did not parse');
  250. }
  251. /**
  252. * test adding an option that accepts multiple values.
  253. *
  254. * @return void
  255. */
  256. public function testAddOptionWithMultiple()
  257. {
  258. $parser = new ConsoleOptionParser('test', false);
  259. $parser->addOption('source', ['short' => 's', 'multiple' => true]);
  260. $result = $parser->parse(['--source', 'mysql', '-s', 'postgres']);
  261. $expected = [
  262. 'source' => [
  263. 'mysql',
  264. 'postgres',
  265. ],
  266. 'help' => false,
  267. ];
  268. $this->assertEquals($expected, $result[0], 'options with multiple values did not parse');
  269. }
  270. /**
  271. * test adding multiple options, including one that accepts multiple values.
  272. *
  273. * @return void
  274. */
  275. public function testAddMultipleOptionsWithMultiple()
  276. {
  277. $parser = new ConsoleOptionParser('test', false);
  278. $parser
  279. ->addOption('source', ['multiple' => true])
  280. ->addOption('name')
  281. ->addOption('export', ['boolean' => true]);
  282. $result = $parser->parse(['--export', '--source', 'mysql', '--name', 'annual-report', '--source', 'postgres']);
  283. $expected = [
  284. 'export' => true,
  285. 'source' => [
  286. 'mysql',
  287. 'postgres',
  288. ],
  289. 'name' => 'annual-report',
  290. 'help' => false,
  291. ];
  292. $this->assertEquals($expected, $result[0], 'options with multiple values did not parse');
  293. }
  294. /**
  295. * Test adding multiple options.
  296. *
  297. * @return void
  298. */
  299. public function testAddOptions()
  300. {
  301. $parser = new ConsoleOptionParser('something', false);
  302. $result = $parser->addOptions([
  303. 'name' => ['help' => 'The name'],
  304. 'other' => ['help' => 'The other arg'],
  305. ]);
  306. $this->assertEquals($parser, $result, 'addOptions is not chainable.');
  307. $result = $parser->options();
  308. $this->assertCount(3, $result, 'Not enough options');
  309. }
  310. /**
  311. * test that boolean options work
  312. *
  313. * @return void
  314. */
  315. public function testOptionWithBooleanParam()
  316. {
  317. $parser = new ConsoleOptionParser('test', false);
  318. $parser->addOption('no-commit', ['boolean' => true])
  319. ->addOption('table', ['short' => 't']);
  320. $result = $parser->parse(['--table', 'posts', '--no-commit', 'arg1', 'arg2']);
  321. $expected = [['table' => 'posts', 'no-commit' => true, 'help' => false], ['arg1', 'arg2']];
  322. $this->assertEquals($expected, $result, 'Boolean option did not parse correctly.');
  323. }
  324. /**
  325. * test parsing options that do not exist.
  326. *
  327. * @return void
  328. */
  329. public function testOptionThatDoesNotExist()
  330. {
  331. $parser = new ConsoleOptionParser('test', false);
  332. $parser->addOption('no-commit', ['boolean' => true]);
  333. try {
  334. $parser->parse(['--he', 'other']);
  335. } catch (InvalidOptionException $e) {
  336. $this->assertStringContainsString(
  337. "Unknown option `he`. Did you mean: `help`?\n" .
  338. "\n" .
  339. "Other valid choices:\n" .
  340. "\n" .
  341. "- help",
  342. $e->getFullMessage()
  343. );
  344. }
  345. }
  346. /**
  347. * test parsing short options that do not exist.
  348. *
  349. * @return void
  350. */
  351. public function testShortOptionThatDoesNotExist()
  352. {
  353. $parser = new ConsoleOptionParser('test', false);
  354. $parser->addOption('no-commit', ['boolean' => true, 'short' => 'n']);
  355. $parser->addOption('construct', ['boolean' => true]);
  356. $parser->addOption('clear', ['boolean' => true, 'short' => 'c']);
  357. try {
  358. $parser->parse(['-f']);
  359. } catch (InvalidOptionException $e) {
  360. $this->assertStringContainsString("Unknown short option `f`.", $e->getFullMessage());
  361. }
  362. }
  363. /**
  364. * test that options with choices enforce them.
  365. *
  366. * @return void
  367. */
  368. public function testOptionWithChoices()
  369. {
  370. $this->expectException(\Cake\Console\Exception\ConsoleException::class);
  371. $parser = new ConsoleOptionParser('test', false);
  372. $parser->addOption('name', ['choices' => ['mark', 'jose']]);
  373. $result = $parser->parse(['--name', 'mark']);
  374. $expected = ['name' => 'mark', 'help' => false];
  375. $this->assertEquals($expected, $result[0], 'Got the correct value.');
  376. $result = $parser->parse(['--name', 'jimmy']);
  377. }
  378. /**
  379. * Ensure that option values can start with -
  380. *
  381. * @return void
  382. */
  383. public function testOptionWithValueStartingWithMinus()
  384. {
  385. $parser = new ConsoleOptionParser('test', false);
  386. $parser->addOption('name')
  387. ->addOption('age');
  388. $result = $parser->parse(['--name', '-foo', '--age', 'old']);
  389. $expected = ['name' => '-foo', 'age' => 'old', 'help' => false];
  390. $this->assertEquals($expected, $result[0], 'Option values starting with "-" are broken.');
  391. }
  392. /**
  393. * test positional argument parsing.
  394. *
  395. * @return void
  396. */
  397. public function testPositionalArgument()
  398. {
  399. $parser = new ConsoleOptionParser('test', false);
  400. $result = $parser->addArgument('name', ['help' => 'An argument']);
  401. $this->assertEquals($parser, $result, 'Should return this');
  402. }
  403. /**
  404. * test addOption with an object.
  405. *
  406. * @return void
  407. */
  408. public function testAddArgumentObject()
  409. {
  410. $parser = new ConsoleOptionParser('test', false);
  411. $parser->addArgument(new ConsoleInputArgument('test'));
  412. $result = $parser->arguments();
  413. $this->assertCount(1, $result);
  414. $this->assertSame('test', $result[0]->name());
  415. }
  416. /**
  417. * Test adding arguments out of order.
  418. *
  419. * @return void
  420. */
  421. public function testAddArgumentOutOfOrder()
  422. {
  423. $parser = new ConsoleOptionParser('test', false);
  424. $parser->addArgument('name', ['index' => 1, 'help' => 'first argument'])
  425. ->addArgument('bag', ['index' => 2, 'help' => 'second argument'])
  426. ->addArgument('other', ['index' => 0, 'help' => 'Zeroth argument']);
  427. $result = $parser->arguments();
  428. $this->assertCount(3, $result);
  429. $this->assertSame('other', $result[0]->name());
  430. $this->assertSame('name', $result[1]->name());
  431. $this->assertSame('bag', $result[2]->name());
  432. $this->assertSame([0, 1, 2], array_keys($result));
  433. $this->assertEquals(
  434. ['other', 'name', 'bag'],
  435. $parser->argumentNames()
  436. );
  437. }
  438. /**
  439. * test overwriting positional arguments.
  440. *
  441. * @return void
  442. */
  443. public function testPositionalArgOverwrite()
  444. {
  445. $parser = new ConsoleOptionParser('test', false);
  446. $parser->addArgument('name', ['help' => 'An argument'])
  447. ->addArgument('other', ['index' => 0]);
  448. $result = $parser->arguments();
  449. $this->assertCount(1, $result, 'Overwrite did not occur');
  450. }
  451. /**
  452. * test parsing arguments.
  453. *
  454. * @return void
  455. */
  456. public function testParseArgumentTooMany()
  457. {
  458. $this->expectException(\Cake\Console\Exception\ConsoleException::class);
  459. $parser = new ConsoleOptionParser('test', false);
  460. $parser->addArgument('name', ['help' => 'An argument'])
  461. ->addArgument('other');
  462. $expected = ['one', 'two'];
  463. $result = $parser->parse($expected);
  464. $this->assertEquals($expected, $result[1], 'Arguments are not as expected');
  465. $result = $parser->parse(['one', 'two', 'three']);
  466. }
  467. /**
  468. * test parsing arguments with 0 value.
  469. *
  470. * @return void
  471. */
  472. public function testParseArgumentZero()
  473. {
  474. $parser = new ConsoleOptionParser('test', false);
  475. $expected = ['one', 'two', 0, 'after', 'zero'];
  476. $result = $parser->parse($expected);
  477. $this->assertEquals($expected, $result[1], 'Arguments are not as expected');
  478. }
  479. /**
  480. * test that when there are not enough arguments an exception is raised
  481. *
  482. * @return void
  483. */
  484. public function testPositionalArgNotEnough()
  485. {
  486. $this->expectException(\Cake\Console\Exception\ConsoleException::class);
  487. $parser = new ConsoleOptionParser('test', false);
  488. $parser->addArgument('name', ['required' => true])
  489. ->addArgument('other', ['required' => true]);
  490. $parser->parse(['one']);
  491. }
  492. /**
  493. * test that when there are required arguments after optional ones an exception is raised
  494. *
  495. * @return void
  496. */
  497. public function testPositionalArgRequiredAfterOptional()
  498. {
  499. $this->expectException(\LogicException::class);
  500. $parser = new ConsoleOptionParser('test');
  501. $parser->addArgument('name', ['required' => false])
  502. ->addArgument('other', ['required' => true]);
  503. }
  504. /**
  505. * test that arguments with choices enforce them.
  506. *
  507. * @return void
  508. */
  509. public function testPositionalArgWithChoices()
  510. {
  511. $this->expectException(\Cake\Console\Exception\ConsoleException::class);
  512. $parser = new ConsoleOptionParser('test', false);
  513. $parser->addArgument('name', ['choices' => ['mark', 'jose']])
  514. ->addArgument('alias', ['choices' => ['cowboy', 'samurai']])
  515. ->addArgument('weapon', ['choices' => ['gun', 'sword']]);
  516. $result = $parser->parse(['mark', 'samurai', 'sword']);
  517. $expected = ['mark', 'samurai', 'sword'];
  518. $this->assertEquals($expected, $result[1], 'Got the correct value.');
  519. $result = $parser->parse(['jose', 'coder']);
  520. }
  521. /**
  522. * Test adding multiple arguments.
  523. *
  524. * @return void
  525. */
  526. public function testAddArguments()
  527. {
  528. $parser = new ConsoleOptionParser('test', false);
  529. $result = $parser->addArguments([
  530. 'name' => ['help' => 'The name'],
  531. 'other' => ['help' => 'The other arg'],
  532. ]);
  533. $this->assertEquals($parser, $result, 'addArguments is not chainable.');
  534. $result = $parser->arguments();
  535. $this->assertCount(2, $result, 'Not enough arguments');
  536. }
  537. /**
  538. * test setting a subcommand up.
  539. *
  540. * @return void
  541. */
  542. public function testSubcommand()
  543. {
  544. $parser = new ConsoleOptionParser('test', false);
  545. $result = $parser->addSubcommand('initdb', [
  546. 'help' => 'Initialize the database',
  547. ]);
  548. $this->assertEquals($parser, $result, 'Adding a subcommand is not chainable');
  549. }
  550. /**
  551. * Tests setting a subcommand up for a Shell method `initMyDb`.
  552. *
  553. * @return void
  554. */
  555. public function testSubcommandCamelBacked()
  556. {
  557. $parser = new ConsoleOptionParser('test', false);
  558. $result = $parser->addSubcommand('initMyDb', [
  559. 'help' => 'Initialize the database',
  560. ]);
  561. $subcommands = array_keys($result->subcommands());
  562. $this->assertEquals(['init_my_db'], $subcommands, 'Adding a subcommand does not work with camel backed method names.');
  563. }
  564. /**
  565. * Test addSubcommand inherits options as no
  566. * parser is created.
  567. *
  568. * @return void
  569. */
  570. public function testAddSubcommandInheritOptions()
  571. {
  572. $parser = new ConsoleOptionParser('test', false);
  573. $parser->addSubcommand('build', [
  574. 'help' => 'Build things',
  575. ])->addOption('connection', [
  576. 'short' => 'c',
  577. 'default' => 'default',
  578. ])->addArgument('name', ['required' => false]);
  579. $result = $parser->parse(['build']);
  580. $this->assertSame('default', $result[0]['connection']);
  581. $result = $parser->subcommands();
  582. $this->assertArrayHasKey('build', $result);
  583. $this->assertNull($result['build']->parser(), 'No parser should be created');
  584. }
  585. /**
  586. * test addSubcommand with an object.
  587. *
  588. * @return void
  589. */
  590. public function testAddSubcommandObject()
  591. {
  592. $parser = new ConsoleOptionParser('test', false);
  593. $parser->addSubcommand(new ConsoleInputSubcommand('test'));
  594. $result = $parser->subcommands();
  595. $this->assertCount(1, $result);
  596. $this->assertSame('test', $result['test']->name());
  597. }
  598. /**
  599. * test addSubcommand without sorting applied.
  600. */
  601. public function testAddSubcommandSort()
  602. {
  603. $parser = new ConsoleOptionParser('test', false);
  604. $this->assertTrue($parser->isSubcommandSortEnabled());
  605. $parser->enableSubcommandSort(false);
  606. $this->assertFalse($parser->isSubcommandSortEnabled());
  607. $parser->addSubcommand(new ConsoleInputSubcommand('betaTest'), []);
  608. $parser->addSubcommand(new ConsoleInputSubcommand('alphaTest'), []);
  609. $result = $parser->subcommands();
  610. $this->assertCount(2, $result);
  611. $firstResult = key($result);
  612. $this->assertSame('betaTest', $firstResult);
  613. }
  614. /**
  615. * test removeSubcommand with an object.
  616. *
  617. * @return void
  618. */
  619. public function testRemoveSubcommand()
  620. {
  621. $parser = new ConsoleOptionParser('test', false);
  622. $parser->addSubcommand(new ConsoleInputSubcommand('test'));
  623. $result = $parser->subcommands();
  624. $this->assertCount(1, $result);
  625. $parser->removeSubcommand('test');
  626. $result = $parser->subcommands();
  627. $this->assertCount(0, $result, 'Remove a subcommand does not work');
  628. }
  629. /**
  630. * test adding multiple subcommands
  631. *
  632. * @return void
  633. */
  634. public function testAddSubcommands()
  635. {
  636. $parser = new ConsoleOptionParser('test', false);
  637. $result = $parser->addSubcommands([
  638. 'initdb' => ['help' => 'Initialize the database'],
  639. 'create' => ['help' => 'Create something'],
  640. ]);
  641. $this->assertEquals($parser, $result, 'Adding a subcommands is not chainable');
  642. $result = $parser->subcommands();
  643. $this->assertCount(2, $result, 'Not enough subcommands');
  644. }
  645. /**
  646. * test that no exception is triggered when help is being generated
  647. *
  648. * @return void
  649. */
  650. public function testHelpNoExceptionWhenGettingHelp()
  651. {
  652. $parser = new ConsoleOptionParser('mycommand', false);
  653. $parser->addOption('test', ['help' => 'A test option.'])
  654. ->addArgument('model', ['help' => 'The model to make.', 'required' => true]);
  655. $result = $parser->parse(['--help']);
  656. $this->assertTrue($result[0]['help']);
  657. }
  658. /**
  659. * test that help() with a command param shows the help for a subcommand
  660. *
  661. * @return void
  662. */
  663. public function testHelpSubcommandHelp()
  664. {
  665. $subParser = new ConsoleOptionParser('method', false);
  666. $subParser->addOption('connection', ['help' => 'Db connection.']);
  667. $subParser->addOption('zero', ['short' => '0', 'help' => 'Zero.']);
  668. $parser = new ConsoleOptionParser('mycommand', false);
  669. $parser->addSubcommand('method', [
  670. 'help' => 'This is another command',
  671. 'parser' => $subParser,
  672. ])
  673. ->addOption('test', ['help' => 'A test option.']);
  674. $result = $parser->help('method');
  675. $expected = <<<TEXT
  676. This is another command
  677. <info>Usage:</info>
  678. cake mycommand method [--connection] [-h] [-0]
  679. <info>Options:</info>
  680. --connection Db connection.
  681. --help, -h Display this help.
  682. --zero, -0 Zero.
  683. TEXT;
  684. $this->assertTextEquals($expected, $result, 'Help is not correct.');
  685. }
  686. /**
  687. * Test addSubcommand inherits options as no
  688. * parser is created.
  689. *
  690. * @return void
  691. */
  692. public function testHelpSubcommandInheritOptions()
  693. {
  694. $parser = new ConsoleOptionParser('mycommand', false);
  695. $parser->addSubcommand('build', [
  696. 'help' => 'Build things.',
  697. ])->addSubcommand('destroy', [
  698. 'help' => 'Destroy things.',
  699. ])->addOption('connection', [
  700. 'help' => 'Db connection.',
  701. 'short' => 'c',
  702. ])->addArgument('name', ['required' => false]);
  703. $result = $parser->help('build');
  704. $expected = <<<TEXT
  705. Build things.
  706. <info>Usage:</info>
  707. cake mycommand build [-c] [-h] [-q] [-v] [<name>]
  708. <info>Options:</info>
  709. --connection, -c Db connection.
  710. --help, -h Display this help.
  711. --quiet, -q Enable quiet output.
  712. --verbose, -v Enable verbose output.
  713. <info>Arguments:</info>
  714. name <comment>(optional)</comment>
  715. TEXT;
  716. $this->assertTextEquals($expected, $result, 'Help is not correct.');
  717. }
  718. /**
  719. * test that help() with a command param shows the help for a subcommand
  720. *
  721. * @return void
  722. */
  723. public function testHelpSubcommandHelpArray()
  724. {
  725. $subParser = [
  726. 'options' => [
  727. 'foo' => [
  728. 'short' => 'f',
  729. 'help' => 'Foo.',
  730. 'boolean' => true,
  731. ],
  732. ],
  733. ];
  734. $parser = new ConsoleOptionParser('mycommand', false);
  735. $parser->addSubcommand('method', [
  736. 'help' => 'This is a subcommand',
  737. 'parser' => $subParser,
  738. ])
  739. ->setRootName('tool')
  740. ->addOption('test', ['help' => 'A test option.']);
  741. $result = $parser->help('method');
  742. $expected = <<<TEXT
  743. This is a subcommand
  744. <info>Usage:</info>
  745. tool mycommand method [-f] [-h] [-q] [-v]
  746. <info>Options:</info>
  747. --foo, -f Foo.
  748. --help, -h Display this help.
  749. --quiet, -q Enable quiet output.
  750. --verbose, -v Enable verbose output.
  751. TEXT;
  752. $this->assertTextEquals($expected, $result, 'Help is not correct.');
  753. }
  754. /**
  755. * test that help() with a command param shows the help for a subcommand
  756. *
  757. * @return void
  758. */
  759. public function testHelpSubcommandInheritParser()
  760. {
  761. $subParser = new ConsoleOptionParser('method', false);
  762. $subParser->addOption('connection', ['help' => 'Db connection.']);
  763. $subParser->addOption('zero', ['short' => '0', 'help' => 'Zero.']);
  764. $parser = new ConsoleOptionParser('mycommand', false);
  765. $parser->addSubcommand('method', [
  766. 'help' => 'This is another command',
  767. 'parser' => $subParser,
  768. ])
  769. ->addOption('test', ['help' => 'A test option.']);
  770. $result = $parser->help('method');
  771. $expected = <<<TEXT
  772. This is another command
  773. <info>Usage:</info>
  774. cake mycommand method [--connection] [-h] [-0]
  775. <info>Options:</info>
  776. --connection Db connection.
  777. --help, -h Display this help.
  778. --zero, -0 Zero.
  779. TEXT;
  780. $this->assertTextEquals($expected, $result, 'Help is not correct.');
  781. }
  782. /**
  783. * test that help() with a custom rootName
  784. *
  785. * @return void
  786. */
  787. public function testHelpWithRootName()
  788. {
  789. $parser = new ConsoleOptionParser('sample', false);
  790. $parser->setDescription('A command!')
  791. ->setRootName('tool')
  792. ->addOption('test', ['help' => 'A test option.']);
  793. $result = $parser->help();
  794. $expected = <<<TEXT
  795. A command!
  796. <info>Usage:</info>
  797. tool sample [-h] [--test]
  798. <info>Options:</info>
  799. --help, -h Display this help.
  800. --test A test option.
  801. TEXT;
  802. $this->assertTextEquals($expected, $result, 'Help is not correct.');
  803. }
  804. /**
  805. * test that getCommandError() with an unknown subcommand param shows a helpful message
  806. *
  807. * @return void
  808. */
  809. public function testHelpUnknownSubcommand()
  810. {
  811. $subParser = [
  812. 'options' => [
  813. 'foo' => [
  814. 'short' => 'f',
  815. 'help' => 'Foo.',
  816. 'boolean' => true,
  817. ],
  818. ],
  819. ];
  820. $parser = new ConsoleOptionParser('mycommand', false);
  821. $parser
  822. ->addSubcommand('method', [
  823. 'help' => 'This is a subcommand',
  824. 'parser' => $subParser,
  825. ])
  826. ->addOption('test', ['help' => 'A test option.'])
  827. ->addSubcommand('unstash');
  828. try {
  829. $result = $parser->help('unknown');
  830. } catch (InvalidOptionException $e) {
  831. $result = $e->getFullMessage();
  832. $this->assertStringContainsString(
  833. "Unable to find the `mycommand unknown` subcommand. See `bin/cake mycommand --help`.\n" .
  834. "\n" .
  835. "Other valid choices:\n" .
  836. "\n" .
  837. "- method",
  838. $result
  839. );
  840. }
  841. }
  842. /**
  843. * test building a parser from an array.
  844. *
  845. * @return void
  846. */
  847. public function testBuildFromArray()
  848. {
  849. $spec = [
  850. 'command' => 'test',
  851. 'arguments' => [
  852. 'name' => ['help' => 'The name'],
  853. 'other' => ['help' => 'The other arg'],
  854. ],
  855. 'options' => [
  856. 'name' => ['help' => 'The name'],
  857. 'other' => ['help' => 'The other arg'],
  858. ],
  859. 'subcommands' => [
  860. 'initdb' => ['help' => 'make database'],
  861. ],
  862. 'description' => 'description text',
  863. 'epilog' => 'epilog text',
  864. ];
  865. $parser = ConsoleOptionParser::buildFromArray($spec);
  866. $this->assertEquals($spec['description'], $parser->getDescription());
  867. $this->assertEquals($spec['epilog'], $parser->getEpilog());
  868. $options = $parser->options();
  869. $this->assertArrayHasKey('name', $options);
  870. $this->assertArrayHasKey('other', $options);
  871. $args = $parser->arguments();
  872. $this->assertCount(2, $args);
  873. $commands = $parser->subcommands();
  874. $this->assertCount(1, $commands);
  875. }
  876. /**
  877. * test that create() returns instances
  878. *
  879. * @return void
  880. */
  881. public function testCreateFactory()
  882. {
  883. $parser = ConsoleOptionParser::create('factory', false);
  884. $this->assertInstanceOf('Cake\Console\ConsoleOptionParser', $parser);
  885. $this->assertSame('factory', $parser->getCommand());
  886. }
  887. /**
  888. * test that getCommand() inflects the command name.
  889. *
  890. * @return void
  891. */
  892. public function testCommandInflection()
  893. {
  894. $parser = new ConsoleOptionParser('CommandLine');
  895. $this->assertSame('command_line', $parser->getCommand());
  896. }
  897. /**
  898. * test that parse() takes a subcommand argument, and that the subcommand parser
  899. * is used.
  900. *
  901. * @return void
  902. */
  903. public function testParsingWithSubParser()
  904. {
  905. $parser = new ConsoleOptionParser('test', false);
  906. $parser->addOption('primary')
  907. ->addArgument('one', ['required' => true, 'choices' => ['a', 'b']])
  908. ->addArgument('two', ['required' => true])
  909. ->addSubcommand('sub', [
  910. 'parser' => [
  911. 'options' => [
  912. 'secondary' => ['boolean' => true],
  913. 'fourth' => ['help' => 'fourth option'],
  914. ],
  915. 'arguments' => [
  916. 'sub_arg' => ['choices' => ['c', 'd']],
  917. ],
  918. ],
  919. ]);
  920. $result = $parser->parse(['sub', '--secondary', '--fourth', '4', 'c']);
  921. $expected = [[
  922. 'secondary' => true,
  923. 'fourth' => '4',
  924. 'help' => false,
  925. 'verbose' => false,
  926. 'quiet' => false], ['c']];
  927. $this->assertEquals($expected, $result, 'Sub parser did not parse request.');
  928. }
  929. /**
  930. * Tests toArray()
  931. *
  932. * @return void
  933. */
  934. public function testToArray()
  935. {
  936. $spec = [
  937. 'command' => 'test',
  938. 'arguments' => [
  939. 'name' => ['help' => 'The name'],
  940. 'other' => ['help' => 'The other arg'],
  941. ],
  942. 'options' => [
  943. 'name' => ['help' => 'The name'],
  944. 'other' => ['help' => 'The other arg'],
  945. ],
  946. 'subcommands' => [
  947. 'initdb' => ['help' => 'make database'],
  948. ],
  949. 'description' => 'description text',
  950. 'epilog' => 'epilog text',
  951. ];
  952. $parser = ConsoleOptionParser::buildFromArray($spec);
  953. $result = $parser->toArray();
  954. $this->assertEquals($spec['description'], $result['description']);
  955. $this->assertEquals($spec['epilog'], $result['epilog']);
  956. $options = $result['options'];
  957. $this->assertArrayHasKey('name', $options);
  958. $this->assertArrayHasKey('other', $options);
  959. $this->assertCount(2, $result['arguments']);
  960. $this->assertCount(1, $result['subcommands']);
  961. }
  962. /**
  963. * Tests merge()
  964. *
  965. * @return void
  966. */
  967. public function testMerge()
  968. {
  969. $parser = new ConsoleOptionParser('test');
  970. $parser->addOption('test', ['short' => 't', 'boolean' => true])
  971. ->addArgument('one', ['required' => true, 'choices' => ['a', 'b']])
  972. ->addArgument('two', ['required' => true]);
  973. $parserTwo = new ConsoleOptionParser('test');
  974. $parserTwo->addOption('file', ['short' => 'f', 'boolean' => true])
  975. ->addOption('output', ['short' => 'o', 'boolean' => true])
  976. ->addArgument('one', ['required' => true, 'choices' => ['a', 'b']]);
  977. $parser->merge($parserTwo);
  978. $result = $parser->toArray();
  979. $options = $result['options'];
  980. $this->assertArrayHasKey('quiet', $options);
  981. $this->assertArrayHasKey('test', $options);
  982. $this->assertArrayHasKey('file', $options);
  983. $this->assertArrayHasKey('output', $options);
  984. $this->assertCount(2, $result['arguments']);
  985. $this->assertCount(6, $result['options']);
  986. }
  987. }