ConsoleOptionParserTest.php 35 KB

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