ConsoleOptionParserTest.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  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\n" .
  342. "- no-commit",
  343. $e->getFullMessage()
  344. );
  345. }
  346. }
  347. /**
  348. * test parsing short options that do not exist.
  349. *
  350. * @return void
  351. */
  352. public function testShortOptionThatDoesNotExist()
  353. {
  354. $parser = new ConsoleOptionParser('test', false);
  355. $parser->addOption('no-commit', ['boolean' => true, 'short' => 'n']);
  356. $parser->addOption('construct', ['boolean' => true]);
  357. $parser->addOption('clear', ['boolean' => true, 'short' => 'c']);
  358. try {
  359. $parser->parse(['-f']);
  360. } catch (InvalidOptionException $e) {
  361. $this->assertStringContainsString(
  362. "Unknown short option `f`.\n" .
  363. "\n" .
  364. "Other valid choices:\n" .
  365. "\n" .
  366. "- c (short for `--clear`)\n" .
  367. "- h (short for `--help`)\n" .
  368. "- n (short for `--no-commit`)",
  369. $e->getFullMessage()
  370. );
  371. }
  372. }
  373. /**
  374. * test that options with choices enforce them.
  375. *
  376. * @return void
  377. */
  378. public function testOptionWithChoices()
  379. {
  380. $this->expectException(\Cake\Console\Exception\ConsoleException::class);
  381. $parser = new ConsoleOptionParser('test', false);
  382. $parser->addOption('name', ['choices' => ['mark', 'jose']]);
  383. $result = $parser->parse(['--name', 'mark']);
  384. $expected = ['name' => 'mark', 'help' => false];
  385. $this->assertEquals($expected, $result[0], 'Got the correct value.');
  386. $result = $parser->parse(['--name', 'jimmy']);
  387. }
  388. /**
  389. * Ensure that option values can start with -
  390. *
  391. * @return void
  392. */
  393. public function testOptionWithValueStartingWithMinus()
  394. {
  395. $parser = new ConsoleOptionParser('test', false);
  396. $parser->addOption('name')
  397. ->addOption('age');
  398. $result = $parser->parse(['--name', '-foo', '--age', 'old']);
  399. $expected = ['name' => '-foo', 'age' => 'old', 'help' => false];
  400. $this->assertEquals($expected, $result[0], 'Option values starting with "-" are broken.');
  401. }
  402. /**
  403. * test positional argument parsing.
  404. *
  405. * @return void
  406. */
  407. public function testPositionalArgument()
  408. {
  409. $parser = new ConsoleOptionParser('test', false);
  410. $result = $parser->addArgument('name', ['help' => 'An argument']);
  411. $this->assertEquals($parser, $result, 'Should return this');
  412. }
  413. /**
  414. * test addOption with an object.
  415. *
  416. * @return void
  417. */
  418. public function testAddArgumentObject()
  419. {
  420. $parser = new ConsoleOptionParser('test', false);
  421. $parser->addArgument(new ConsoleInputArgument('test'));
  422. $result = $parser->arguments();
  423. $this->assertCount(1, $result);
  424. $this->assertSame('test', $result[0]->name());
  425. }
  426. /**
  427. * Test adding arguments out of order.
  428. *
  429. * @return void
  430. */
  431. public function testAddArgumentOutOfOrder()
  432. {
  433. $parser = new ConsoleOptionParser('test', false);
  434. $parser->addArgument('name', ['index' => 1, 'help' => 'first argument'])
  435. ->addArgument('bag', ['index' => 2, 'help' => 'second argument'])
  436. ->addArgument('other', ['index' => 0, 'help' => 'Zeroth argument']);
  437. $result = $parser->arguments();
  438. $this->assertCount(3, $result);
  439. $this->assertSame('other', $result[0]->name());
  440. $this->assertSame('name', $result[1]->name());
  441. $this->assertSame('bag', $result[2]->name());
  442. $this->assertSame([0, 1, 2], array_keys($result));
  443. $this->assertEquals(
  444. ['other', 'name', 'bag'],
  445. $parser->argumentNames()
  446. );
  447. }
  448. /**
  449. * test overwriting positional arguments.
  450. *
  451. * @return void
  452. */
  453. public function testPositionalArgOverwrite()
  454. {
  455. $parser = new ConsoleOptionParser('test', false);
  456. $parser->addArgument('name', ['help' => 'An argument'])
  457. ->addArgument('other', ['index' => 0]);
  458. $result = $parser->arguments();
  459. $this->assertCount(1, $result, 'Overwrite did not occur');
  460. }
  461. /**
  462. * test parsing arguments.
  463. *
  464. * @return void
  465. */
  466. public function testParseArgumentTooMany()
  467. {
  468. $this->expectException(\Cake\Console\Exception\ConsoleException::class);
  469. $parser = new ConsoleOptionParser('test', false);
  470. $parser->addArgument('name', ['help' => 'An argument'])
  471. ->addArgument('other');
  472. $expected = ['one', 'two'];
  473. $result = $parser->parse($expected);
  474. $this->assertEquals($expected, $result[1], 'Arguments are not as expected');
  475. $result = $parser->parse(['one', 'two', 'three']);
  476. }
  477. /**
  478. * test parsing arguments with 0 value.
  479. *
  480. * @return void
  481. */
  482. public function testParseArgumentZero()
  483. {
  484. $parser = new ConsoleOptionParser('test', false);
  485. $expected = ['one', 'two', 0, 'after', 'zero'];
  486. $result = $parser->parse($expected);
  487. $this->assertEquals($expected, $result[1], 'Arguments are not as expected');
  488. }
  489. /**
  490. * test that when there are not enough arguments an exception is raised
  491. *
  492. * @return void
  493. */
  494. public function testPositionalArgNotEnough()
  495. {
  496. $this->expectException(\Cake\Console\Exception\ConsoleException::class);
  497. $parser = new ConsoleOptionParser('test', false);
  498. $parser->addArgument('name', ['required' => true])
  499. ->addArgument('other', ['required' => true]);
  500. $parser->parse(['one']);
  501. }
  502. /**
  503. * test that when there are required arguments after optional ones an exception is raised
  504. *
  505. * @return void
  506. */
  507. public function testPositionalArgRequiredAfterOptional()
  508. {
  509. $this->expectException(\LogicException::class);
  510. $parser = new ConsoleOptionParser('test');
  511. $parser->addArgument('name', ['required' => false])
  512. ->addArgument('other', ['required' => true]);
  513. }
  514. /**
  515. * test that arguments with choices enforce them.
  516. *
  517. * @return void
  518. */
  519. public function testPositionalArgWithChoices()
  520. {
  521. $this->expectException(\Cake\Console\Exception\ConsoleException::class);
  522. $parser = new ConsoleOptionParser('test', false);
  523. $parser->addArgument('name', ['choices' => ['mark', 'jose']])
  524. ->addArgument('alias', ['choices' => ['cowboy', 'samurai']])
  525. ->addArgument('weapon', ['choices' => ['gun', 'sword']]);
  526. $result = $parser->parse(['mark', 'samurai', 'sword']);
  527. $expected = ['mark', 'samurai', 'sword'];
  528. $this->assertEquals($expected, $result[1], 'Got the correct value.');
  529. $result = $parser->parse(['jose', 'coder']);
  530. }
  531. /**
  532. * Test adding multiple arguments.
  533. *
  534. * @return void
  535. */
  536. public function testAddArguments()
  537. {
  538. $parser = new ConsoleOptionParser('test', false);
  539. $result = $parser->addArguments([
  540. 'name' => ['help' => 'The name'],
  541. 'other' => ['help' => 'The other arg'],
  542. ]);
  543. $this->assertEquals($parser, $result, 'addArguments is not chainable.');
  544. $result = $parser->arguments();
  545. $this->assertCount(2, $result, 'Not enough arguments');
  546. }
  547. /**
  548. * test setting a subcommand up.
  549. *
  550. * @return void
  551. */
  552. public function testSubcommand()
  553. {
  554. $parser = new ConsoleOptionParser('test', false);
  555. $result = $parser->addSubcommand('initdb', [
  556. 'help' => 'Initialize the database',
  557. ]);
  558. $this->assertEquals($parser, $result, 'Adding a subcommand is not chainable');
  559. }
  560. /**
  561. * Tests setting a subcommand up for a Shell method `initMyDb`.
  562. *
  563. * @return void
  564. */
  565. public function testSubcommandCamelBacked()
  566. {
  567. $parser = new ConsoleOptionParser('test', false);
  568. $result = $parser->addSubcommand('initMyDb', [
  569. 'help' => 'Initialize the database',
  570. ]);
  571. $subcommands = array_keys($result->subcommands());
  572. $this->assertEquals(['init_my_db'], $subcommands, 'Adding a subcommand does not work with camel backed method names.');
  573. }
  574. /**
  575. * Test addSubcommand inherits options as no
  576. * parser is created.
  577. *
  578. * @return void
  579. */
  580. public function testAddSubcommandInheritOptions()
  581. {
  582. $parser = new ConsoleOptionParser('test', false);
  583. $parser->addSubcommand('build', [
  584. 'help' => 'Build things',
  585. ])->addOption('connection', [
  586. 'short' => 'c',
  587. 'default' => 'default',
  588. ])->addArgument('name', ['required' => false]);
  589. $result = $parser->parse(['build']);
  590. $this->assertSame('default', $result[0]['connection']);
  591. $result = $parser->subcommands();
  592. $this->assertArrayHasKey('build', $result);
  593. $this->assertNull($result['build']->parser(), 'No parser should be created');
  594. }
  595. /**
  596. * test addSubcommand with an object.
  597. *
  598. * @return void
  599. */
  600. public function testAddSubcommandObject()
  601. {
  602. $parser = new ConsoleOptionParser('test', false);
  603. $parser->addSubcommand(new ConsoleInputSubcommand('test'));
  604. $result = $parser->subcommands();
  605. $this->assertCount(1, $result);
  606. $this->assertSame('test', $result['test']->name());
  607. }
  608. /**
  609. * test addSubcommand without sorting applied.
  610. */
  611. public function testAddSubcommandSort()
  612. {
  613. $parser = new ConsoleOptionParser('test', false);
  614. $this->assertTrue($parser->isSubcommandSortEnabled());
  615. $parser->enableSubcommandSort(false);
  616. $this->assertFalse($parser->isSubcommandSortEnabled());
  617. $parser->addSubcommand(new ConsoleInputSubcommand('betaTest'), []);
  618. $parser->addSubcommand(new ConsoleInputSubcommand('alphaTest'), []);
  619. $result = $parser->subcommands();
  620. $this->assertCount(2, $result);
  621. $firstResult = key($result);
  622. $this->assertSame('betaTest', $firstResult);
  623. }
  624. /**
  625. * test removeSubcommand with an object.
  626. *
  627. * @return void
  628. */
  629. public function testRemoveSubcommand()
  630. {
  631. $parser = new ConsoleOptionParser('test', false);
  632. $parser->addSubcommand(new ConsoleInputSubcommand('test'));
  633. $result = $parser->subcommands();
  634. $this->assertCount(1, $result);
  635. $parser->removeSubcommand('test');
  636. $result = $parser->subcommands();
  637. $this->assertCount(0, $result, 'Remove a subcommand does not work');
  638. }
  639. /**
  640. * test adding multiple subcommands
  641. *
  642. * @return void
  643. */
  644. public function testAddSubcommands()
  645. {
  646. $parser = new ConsoleOptionParser('test', false);
  647. $result = $parser->addSubcommands([
  648. 'initdb' => ['help' => 'Initialize the database'],
  649. 'create' => ['help' => 'Create something'],
  650. ]);
  651. $this->assertEquals($parser, $result, 'Adding a subcommands is not chainable');
  652. $result = $parser->subcommands();
  653. $this->assertCount(2, $result, 'Not enough subcommands');
  654. }
  655. /**
  656. * test that no exception is triggered when help is being generated
  657. *
  658. * @return void
  659. */
  660. public function testHelpNoExceptionWhenGettingHelp()
  661. {
  662. $parser = new ConsoleOptionParser('mycommand', false);
  663. $parser->addOption('test', ['help' => 'A test option.'])
  664. ->addArgument('model', ['help' => 'The model to make.', 'required' => true]);
  665. $result = $parser->parse(['--help']);
  666. $this->assertTrue($result[0]['help']);
  667. }
  668. /**
  669. * test that help() with a command param shows the help for a subcommand
  670. *
  671. * @return void
  672. */
  673. public function testHelpSubcommandHelp()
  674. {
  675. $subParser = new ConsoleOptionParser('method', false);
  676. $subParser->addOption('connection', ['help' => 'Db connection.']);
  677. $subParser->addOption('zero', ['short' => '0', 'help' => 'Zero.']);
  678. $parser = new ConsoleOptionParser('mycommand', false);
  679. $parser->addSubcommand('method', [
  680. 'help' => 'This is another command',
  681. 'parser' => $subParser,
  682. ])
  683. ->addOption('test', ['help' => 'A test option.']);
  684. $result = $parser->help('method');
  685. $expected = <<<TEXT
  686. This is another command
  687. <info>Usage:</info>
  688. cake mycommand method [--connection] [-h] [-0]
  689. <info>Options:</info>
  690. --connection Db connection.
  691. --help, -h Display this help.
  692. --zero, -0 Zero.
  693. TEXT;
  694. $this->assertTextEquals($expected, $result, 'Help is not correct.');
  695. }
  696. /**
  697. * Test addSubcommand inherits options as no
  698. * parser is created.
  699. *
  700. * @return void
  701. */
  702. public function testHelpSubcommandInheritOptions()
  703. {
  704. $parser = new ConsoleOptionParser('mycommand', false);
  705. $parser->addSubcommand('build', [
  706. 'help' => 'Build things.',
  707. ])->addSubcommand('destroy', [
  708. 'help' => 'Destroy things.',
  709. ])->addOption('connection', [
  710. 'help' => 'Db connection.',
  711. 'short' => 'c',
  712. ])->addArgument('name', ['required' => false]);
  713. $result = $parser->help('build');
  714. $expected = <<<TEXT
  715. Build things.
  716. <info>Usage:</info>
  717. cake mycommand build [-c] [-h] [-q] [-v] [<name>]
  718. <info>Options:</info>
  719. --connection, -c Db connection.
  720. --help, -h Display this help.
  721. --quiet, -q Enable quiet output.
  722. --verbose, -v Enable verbose output.
  723. <info>Arguments:</info>
  724. name <comment>(optional)</comment>
  725. TEXT;
  726. $this->assertTextEquals($expected, $result, 'Help is not correct.');
  727. }
  728. /**
  729. * test that help() with a command param shows the help for a subcommand
  730. *
  731. * @return void
  732. */
  733. public function testHelpSubcommandHelpArray()
  734. {
  735. $subParser = [
  736. 'options' => [
  737. 'foo' => [
  738. 'short' => 'f',
  739. 'help' => 'Foo.',
  740. 'boolean' => true,
  741. ],
  742. ],
  743. ];
  744. $parser = new ConsoleOptionParser('mycommand', false);
  745. $parser->addSubcommand('method', [
  746. 'help' => 'This is a subcommand',
  747. 'parser' => $subParser,
  748. ])
  749. ->setRootName('tool')
  750. ->addOption('test', ['help' => 'A test option.']);
  751. $result = $parser->help('method');
  752. $expected = <<<TEXT
  753. This is a subcommand
  754. <info>Usage:</info>
  755. tool mycommand method [-f] [-h] [-q] [-v]
  756. <info>Options:</info>
  757. --foo, -f Foo.
  758. --help, -h Display this help.
  759. --quiet, -q Enable quiet output.
  760. --verbose, -v Enable verbose output.
  761. TEXT;
  762. $this->assertTextEquals($expected, $result, 'Help is not correct.');
  763. }
  764. /**
  765. * test that help() with a command param shows the help for a subcommand
  766. *
  767. * @return void
  768. */
  769. public function testHelpSubcommandInheritParser()
  770. {
  771. $subParser = new ConsoleOptionParser('method', false);
  772. $subParser->addOption('connection', ['help' => 'Db connection.']);
  773. $subParser->addOption('zero', ['short' => '0', 'help' => 'Zero.']);
  774. $parser = new ConsoleOptionParser('mycommand', false);
  775. $parser->addSubcommand('method', [
  776. 'help' => 'This is another command',
  777. 'parser' => $subParser,
  778. ])
  779. ->addOption('test', ['help' => 'A test option.']);
  780. $result = $parser->help('method');
  781. $expected = <<<TEXT
  782. This is another command
  783. <info>Usage:</info>
  784. cake mycommand method [--connection] [-h] [-0]
  785. <info>Options:</info>
  786. --connection Db connection.
  787. --help, -h Display this help.
  788. --zero, -0 Zero.
  789. TEXT;
  790. $this->assertTextEquals($expected, $result, 'Help is not correct.');
  791. }
  792. /**
  793. * test that help() with a custom rootName
  794. *
  795. * @return void
  796. */
  797. public function testHelpWithRootName()
  798. {
  799. $parser = new ConsoleOptionParser('sample', false);
  800. $parser->setDescription('A command!')
  801. ->setRootName('tool')
  802. ->addOption('test', ['help' => 'A test option.']);
  803. $result = $parser->help();
  804. $expected = <<<TEXT
  805. A command!
  806. <info>Usage:</info>
  807. tool sample [-h] [--test]
  808. <info>Options:</info>
  809. --help, -h Display this help.
  810. --test A test option.
  811. TEXT;
  812. $this->assertTextEquals($expected, $result, 'Help is not correct.');
  813. }
  814. /**
  815. * test that getCommandError() with an unknown subcommand param shows a helpful message
  816. *
  817. * @return void
  818. */
  819. public function testHelpUnknownSubcommand()
  820. {
  821. $subParser = [
  822. 'options' => [
  823. 'foo' => [
  824. 'short' => 'f',
  825. 'help' => 'Foo.',
  826. 'boolean' => true,
  827. ],
  828. ],
  829. ];
  830. $parser = new ConsoleOptionParser('mycommand', false);
  831. $parser
  832. ->addSubcommand('method', [
  833. 'help' => 'This is a subcommand',
  834. 'parser' => $subParser,
  835. ])
  836. ->addOption('test', ['help' => 'A test option.'])
  837. ->addSubcommand('unstash');
  838. try {
  839. $result = $parser->help('unknown');
  840. } catch (InvalidOptionException $e) {
  841. $result = $e->getFullMessage();
  842. $this->assertStringContainsString(
  843. "Unable to find the `mycommand unknown` subcommand. See `bin/cake mycommand --help`.\n" .
  844. "\n" .
  845. "Other valid choices:\n" .
  846. "\n" .
  847. "- method",
  848. $result
  849. );
  850. }
  851. }
  852. /**
  853. * test building a parser from an array.
  854. *
  855. * @return void
  856. */
  857. public function testBuildFromArray()
  858. {
  859. $spec = [
  860. 'command' => 'test',
  861. 'arguments' => [
  862. 'name' => ['help' => 'The name'],
  863. 'other' => ['help' => 'The other arg'],
  864. ],
  865. 'options' => [
  866. 'name' => ['help' => 'The name'],
  867. 'other' => ['help' => 'The other arg'],
  868. ],
  869. 'subcommands' => [
  870. 'initdb' => ['help' => 'make database'],
  871. ],
  872. 'description' => 'description text',
  873. 'epilog' => 'epilog text',
  874. ];
  875. $parser = ConsoleOptionParser::buildFromArray($spec);
  876. $this->assertEquals($spec['description'], $parser->getDescription());
  877. $this->assertEquals($spec['epilog'], $parser->getEpilog());
  878. $options = $parser->options();
  879. $this->assertArrayHasKey('name', $options);
  880. $this->assertArrayHasKey('other', $options);
  881. $args = $parser->arguments();
  882. $this->assertCount(2, $args);
  883. $commands = $parser->subcommands();
  884. $this->assertCount(1, $commands);
  885. }
  886. /**
  887. * test that create() returns instances
  888. *
  889. * @return void
  890. */
  891. public function testCreateFactory()
  892. {
  893. $parser = ConsoleOptionParser::create('factory', false);
  894. $this->assertInstanceOf('Cake\Console\ConsoleOptionParser', $parser);
  895. $this->assertSame('factory', $parser->getCommand());
  896. }
  897. /**
  898. * test that getCommand() inflects the command name.
  899. *
  900. * @return void
  901. */
  902. public function testCommandInflection()
  903. {
  904. $parser = new ConsoleOptionParser('CommandLine');
  905. $this->assertSame('command_line', $parser->getCommand());
  906. }
  907. /**
  908. * test that parse() takes a subcommand argument, and that the subcommand parser
  909. * is used.
  910. *
  911. * @return void
  912. */
  913. public function testParsingWithSubParser()
  914. {
  915. $parser = new ConsoleOptionParser('test', false);
  916. $parser->addOption('primary')
  917. ->addArgument('one', ['required' => true, 'choices' => ['a', 'b']])
  918. ->addArgument('two', ['required' => true])
  919. ->addSubcommand('sub', [
  920. 'parser' => [
  921. 'options' => [
  922. 'secondary' => ['boolean' => true],
  923. 'fourth' => ['help' => 'fourth option'],
  924. ],
  925. 'arguments' => [
  926. 'sub_arg' => ['choices' => ['c', 'd']],
  927. ],
  928. ],
  929. ]);
  930. $result = $parser->parse(['sub', '--secondary', '--fourth', '4', 'c']);
  931. $expected = [[
  932. 'secondary' => true,
  933. 'fourth' => '4',
  934. 'help' => false,
  935. 'verbose' => false,
  936. 'quiet' => false], ['c']];
  937. $this->assertEquals($expected, $result, 'Sub parser did not parse request.');
  938. }
  939. /**
  940. * Tests toArray()
  941. *
  942. * @return void
  943. */
  944. public function testToArray()
  945. {
  946. $spec = [
  947. 'command' => 'test',
  948. 'arguments' => [
  949. 'name' => ['help' => 'The name'],
  950. 'other' => ['help' => 'The other arg'],
  951. ],
  952. 'options' => [
  953. 'name' => ['help' => 'The name'],
  954. 'other' => ['help' => 'The other arg'],
  955. ],
  956. 'subcommands' => [
  957. 'initdb' => ['help' => 'make database'],
  958. ],
  959. 'description' => 'description text',
  960. 'epilog' => 'epilog text',
  961. ];
  962. $parser = ConsoleOptionParser::buildFromArray($spec);
  963. $result = $parser->toArray();
  964. $this->assertEquals($spec['description'], $result['description']);
  965. $this->assertEquals($spec['epilog'], $result['epilog']);
  966. $options = $result['options'];
  967. $this->assertArrayHasKey('name', $options);
  968. $this->assertArrayHasKey('other', $options);
  969. $this->assertCount(2, $result['arguments']);
  970. $this->assertCount(1, $result['subcommands']);
  971. }
  972. /**
  973. * Tests merge()
  974. *
  975. * @return void
  976. */
  977. public function testMerge()
  978. {
  979. $parser = new ConsoleOptionParser('test');
  980. $parser->addOption('test', ['short' => 't', 'boolean' => true])
  981. ->addArgument('one', ['required' => true, 'choices' => ['a', 'b']])
  982. ->addArgument('two', ['required' => true]);
  983. $parserTwo = new ConsoleOptionParser('test');
  984. $parserTwo->addOption('file', ['short' => 'f', 'boolean' => true])
  985. ->addOption('output', ['short' => 'o', 'boolean' => true])
  986. ->addArgument('one', ['required' => true, 'choices' => ['a', 'b']]);
  987. $parser->merge($parserTwo);
  988. $result = $parser->toArray();
  989. $options = $result['options'];
  990. $this->assertArrayHasKey('quiet', $options);
  991. $this->assertArrayHasKey('test', $options);
  992. $this->assertArrayHasKey('file', $options);
  993. $this->assertArrayHasKey('output', $options);
  994. $this->assertCount(2, $result['arguments']);
  995. $this->assertCount(6, $result['options']);
  996. }
  997. }