From 63e3fc7e305e07bdc1adbc8f44cd0d6c84bb7238 Mon Sep 17 00:00:00 2001 From: Greg Anderson Date: Thu, 8 Jun 2023 11:46:35 -0400 Subject: [PATCH 1/2] Add a TableEmptyMessage attribute --- composer.json | 2 +- composer.lock | 139 ++++++++++++-------------- src/Attributes/DefaultFields.php | 3 +- src/Attributes/DefaultTableFields.php | 3 +- src/Attributes/FieldLabels.php | 3 +- src/Attributes/TableEmptyMessage.php | 26 +++++ tests/FullStackTest.php | 4 + tests/HelpTest.php | 6 +- tests/src/alpha/AlphaCommandFile.php | 24 +++++ 9 files changed, 125 insertions(+), 85 deletions(-) create mode 100644 src/Attributes/TableEmptyMessage.php diff --git a/composer.json b/composer.json index 164e5ce..f5a1557 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ }, "require": { "php": ">=7.1.3", - "consolidation/output-formatters": "^4.3.1", + "consolidation/output-formatters": "dev-table-empty-message", "psr/log": "^1 || ^2 || ^3", "symfony/console": "^4.4.8 || ^5 || ^6", "symfony/event-dispatcher": "^4.4.8 || ^5 || ^6", diff --git a/composer.lock b/composer.lock index 41b3e46..d3a66a8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7635dbcf5b08f582c810ef3c9f6a30c2", + "content-hash": "8914698acb49eed6434100fce4d15870", "packages": [ { "name": "consolidation/output-formatters", - "version": "4.3.1", + "version": "dev-table-empty-message", "source": { "type": "git", "url": "https://github.com/consolidation/output-formatters.git", - "reference": "f65524e9ecd2bd0021c4b18710005caaa6dcbd86" + "reference": "05c71eb8753d5f3a7438175e1847a2159bcda1c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/f65524e9ecd2bd0021c4b18710005caaa6dcbd86", - "reference": "f65524e9ecd2bd0021c4b18710005caaa6dcbd86", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/05c71eb8753d5f3a7438175e1847a2159bcda1c6", + "reference": "05c71eb8753d5f3a7438175e1847a2159bcda1c6", "shasum": "" }, "require": { @@ -56,9 +56,9 @@ "description": "Format text by applying transformations provided by plug-in formatters.", "support": { "issues": "https://github.com/consolidation/output-formatters/issues", - "source": "https://github.com/consolidation/output-formatters/tree/4.3.1" + "source": "https://github.com/consolidation/output-formatters/tree/table-empty-message" }, - "time": "2023-05-20T03:23:06+00:00" + "time": "2023-06-07T18:31:06+00:00" }, { "name": "dflydev/dot-access-data", @@ -290,23 +290,23 @@ }, { "name": "symfony/console", - "version": "v6.2.10", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "12288d9f4500f84a4d02254d4aa968b15488476f" + "reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/12288d9f4500f84a4d02254d4aa968b15488476f", - "reference": "12288d9f4500f84a4d02254d4aa968b15488476f", + "url": "https://api.github.com/repos/symfony/console/zipball/8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", + "reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/string": "^5.4|^6.0" }, "conflict": { @@ -328,12 +328,6 @@ "symfony/process": "^5.4|^6.0", "symfony/var-dumper": "^5.4|^6.0" }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, "type": "library", "autoload": { "psr-4": { @@ -366,7 +360,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.2.10" + "source": "https://github.com/symfony/console/tree/v6.3.0" }, "funding": [ { @@ -382,20 +376,20 @@ "type": "tidelift" } ], - "time": "2023-04-28T13:37:43+00:00" + "time": "2023-05-29T12:49:39+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.2.1", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e" + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", - "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", "shasum": "" }, "require": { @@ -404,7 +398,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -433,7 +427,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" }, "funding": [ { @@ -449,28 +443,29 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:25:55+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.2.8", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "04046f35fd7d72f9646e721fc2ecb8f9c67d3339" + "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/04046f35fd7d72f9646e721fc2ecb8f9c67d3339", - "reference": "04046f35fd7d72f9646e721fc2ecb8f9c67d3339", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", + "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/event-dispatcher-contracts": "^2|^3" + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<5.4" + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", @@ -483,13 +478,9 @@ "symfony/error-handler": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/http-foundation": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/stopwatch": "^5.4|^6.0" }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, "type": "library", "autoload": { "psr-4": { @@ -516,7 +507,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.8" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.0" }, "funding": [ { @@ -532,33 +523,30 @@ "type": "tidelift" } ], - "time": "2023-03-20T16:06:02+00:00" + "time": "2023-04-21T14:41:17+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.2.1", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "0ad3b6f1e4e2da5690fefe075cd53a238646d8dd" + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ad3b6f1e4e2da5690fefe075cd53a238646d8dd", - "reference": "0ad3b6f1e4e2da5690fefe075cd53a238646d8dd", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", "shasum": "" }, "require": { "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -595,7 +583,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.3.0" }, "funding": [ { @@ -611,20 +599,20 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:32:47+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/finder", - "version": "v6.2.7", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "20808dc6631aecafbe67c186af5dcb370be3a0eb" + "reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/20808dc6631aecafbe67c186af5dcb370be3a0eb", - "reference": "20808dc6631aecafbe67c186af5dcb370be3a0eb", + "url": "https://api.github.com/repos/symfony/finder/zipball/d9b01ba073c44cef617c7907ce2419f8d00d75e2", + "reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2", "shasum": "" }, "require": { @@ -659,7 +647,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.2.7" + "source": "https://github.com/symfony/finder/tree/v6.3.0" }, "funding": [ { @@ -675,7 +663,7 @@ "type": "tidelift" } ], - "time": "2023-02-16T09:57:23+00:00" + "time": "2023-04-02T01:25:41+00:00" }, { "name": "symfony/polyfill-ctype", @@ -1009,16 +997,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.2.1", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "a8c9cedf55f314f3a186041d19537303766df09a" + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a", - "reference": "a8c9cedf55f314f3a186041d19537303766df09a", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", "shasum": "" }, "require": { @@ -1028,13 +1016,10 @@ "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -1074,7 +1059,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" }, "funding": [ { @@ -1090,20 +1075,20 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:32:47+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/string", - "version": "v6.2.8", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef" + "reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/193e83bbd6617d6b2151c37fff10fa7168ebddef", - "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef", + "url": "https://api.github.com/repos/symfony/string/zipball/f2e190ee75ff0f5eced645ec0be5c66fac81f51f", + "reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f", "shasum": "" }, "require": { @@ -1114,13 +1099,13 @@ "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/translation-contracts": "<2.0" + "symfony/translation-contracts": "<2.5" }, "require-dev": { "symfony/error-handler": "^5.4|^6.0", "symfony/http-client": "^5.4|^6.0", "symfony/intl": "^6.2", - "symfony/translation-contracts": "^2.0|^3.0", + "symfony/translation-contracts": "^2.5|^3.0", "symfony/var-exporter": "^5.4|^6.0" }, "type": "library", @@ -1160,7 +1145,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.2.8" + "source": "https://github.com/symfony/string/tree/v6.3.0" }, "funding": [ { @@ -1176,7 +1161,7 @@ "type": "tidelift" } ], - "time": "2023-03-20T16:06:02+00:00" + "time": "2023-03-21T21:06:29+00:00" } ], "packages-dev": [ @@ -3034,7 +3019,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "consolidation/output-formatters": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Attributes/DefaultFields.php b/src/Attributes/DefaultFields.php index e3fa96f..bbd5329 100644 --- a/src/Attributes/DefaultFields.php +++ b/src/Attributes/DefaultFields.php @@ -4,6 +4,7 @@ use Attribute; use Consolidation\AnnotatedCommand\Parser\CommandInfo; +use Consolidation\OutputFormatters\Options\FormatterOptions; #[Attribute(Attribute::TARGET_METHOD)] class DefaultFields @@ -20,6 +21,6 @@ public function __construct( public static function handle(\ReflectionAttribute $attribute, CommandInfo $commandInfo) { $args = $attribute->getArguments(); - $commandInfo->addAnnotation('default-fields', $args['fields']); + $commandInfo->addAnnotation(FormatterOptions::DEFAULT_FIELDS, $args['fields']); } } diff --git a/src/Attributes/DefaultTableFields.php b/src/Attributes/DefaultTableFields.php index 3689320..efed186 100644 --- a/src/Attributes/DefaultTableFields.php +++ b/src/Attributes/DefaultTableFields.php @@ -4,6 +4,7 @@ use Attribute; use Consolidation\AnnotatedCommand\Parser\CommandInfo; +use Consolidation\OutputFormatters\Options\FormatterOptions; #[Attribute(Attribute::TARGET_METHOD)] class DefaultTableFields @@ -20,6 +21,6 @@ public function __construct( public static function handle(\ReflectionAttribute $attribute, CommandInfo $commandInfo) { $args = $attribute->getArguments(); - $commandInfo->addAnnotation('default-table-fields', $args['fields']); + $commandInfo->addAnnotation(FormatterOptions::DEFAULT_TABLE_FIELDS, $args['fields']); } } diff --git a/src/Attributes/FieldLabels.php b/src/Attributes/FieldLabels.php index ddd82e5..ce5db63 100644 --- a/src/Attributes/FieldLabels.php +++ b/src/Attributes/FieldLabels.php @@ -4,6 +4,7 @@ use Attribute; use Consolidation\AnnotatedCommand\Parser\CommandInfo; +use Consolidation\OutputFormatters\Options\FormatterOptions; #[Attribute(Attribute::TARGET_METHOD)] class FieldLabels @@ -20,6 +21,6 @@ public function __construct( public static function handle(\ReflectionAttribute $attribute, CommandInfo $commandInfo) { $args = $attribute->getArguments(); - $commandInfo->addAnnotation('field-labels', $args['labels']); + $commandInfo->addAnnotation(FormatterOptions::FIELD_LABELS, $args['labels']); } } diff --git a/src/Attributes/TableEmptyMessage.php b/src/Attributes/TableEmptyMessage.php new file mode 100644 index 0000000..08b5243 --- /dev/null +++ b/src/Attributes/TableEmptyMessage.php @@ -0,0 +1,26 @@ +getArguments(); + $commandInfo->addAnnotation(FormatterOptions::TABLE_EMPTY_MESSAGE, $args['message']); + } +} diff --git a/tests/FullStackTest.php b/tests/FullStackTest.php index 3be8567..8cf5c02 100644 --- a/tests/FullStackTest.php +++ b/tests/FullStackTest.php @@ -356,6 +356,10 @@ function testCommandsAndHooksIncludeAllPublicMethods() $this->assertRunCommandViaApplicationEquals('get:serious', 'very serious'); $this->assertRunCommandViaApplicationContains('get:lost', 'Command "get:lost" is not defined.', [], 1); $this->assertRunCommandViaApplicationContains('get:both', 'Here is some data.', [], 3); + + // Test to see if an empty table omits the table headers (labels header) + $this->assertRunCommandViaApplicationContains('tabularify apples peaches pumpkin pie', '----'); + $this->assertRunCommandViaApplicationEquals('tabularify', 'There are no items to list', 0); } function testCommandsAndHooksWithBetaFolder() diff --git a/tests/HelpTest.php b/tests/HelpTest.php index 3174b0a..4fd1da3 100644 --- a/tests/HelpTest.php +++ b/tests/HelpTest.php @@ -114,11 +114,7 @@ function testHelp() $htmlEncodedHelpMessage = htmlspecialchars($expectedHelpMessage); $outputFormattersVersion = ltrim(InstalledVersions::getPrettyVersion('consolidation/output-formatters'), 'v'); - if (version_compare($outputFormattersVersion, '4.1.3', '>=')) { - $expectedFieldMessage = 'Select just one field, and force format to *string*.'; - } else { - $expectedFieldMessage = "Select just one field, and force format to 'string'."; - } + $expectedFieldMessage = 'Select just one field, and force format to *string*.'; $expectedXML = << diff --git a/tests/src/alpha/AlphaCommandFile.php b/tests/src/alpha/AlphaCommandFile.php index 051e1b8..a71e6fb 100644 --- a/tests/src/alpha/AlphaCommandFile.php +++ b/tests/src/alpha/AlphaCommandFile.php @@ -14,6 +14,7 @@ use Consolidation\OutputFormatters\Options\FormatterOptions; use Consolidation\TestUtils\ExampleCommandFile as ExampleAliasedClass; +use Consolidation\AnnotatedCommand\Attributes as CLI; /** * Test file used in the testCommandDiscovery() test. @@ -26,6 +27,29 @@ class AlphaCommandFile implements CustomEventAwareInterface { use CustomEventAwareTrait; + /** + * This command prints its args in a table. + */ + #[CLI\Command(name: 'tabularify')] + #[CLI\Argument(name: 'args', description: 'Any number of arguments separated by spaces.')] + #[CLI\Usage(name: 'tabularify apples peaches pumpkin pie', description: 'Show a list of things in a table.')] + #[CLI\FieldLabels(labels: ['label' => 'Label', 'value' => 'Value'])] + #[CLI\TableEmptyMessage(message: 'There are no items to list')] + public function tabularify(array $args, $options = ['format' => 'table']): RowsOfFields + { + $labels = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eigth']; + + $rows = []; + foreach ($args as $arg) { + $label = array_shift($labels); + + if (!empty($label)) { + $rows[$label] = ['label' => $label, 'value' => $arg]; + } + } + return new RowsOfFields($rows); + } + /** * @command always:fail */ From 20fcb8f728caef824ed42bc9255030cb5b96a2e3 Mon Sep 17 00:00:00 2001 From: Greg Anderson Date: Thu, 8 Jun 2023 13:02:36 -0400 Subject: [PATCH 2/2] Put empty table message test in its own test function, and mark it to only run on php 8+ --- tests/FullStackTest.php | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/FullStackTest.php b/tests/FullStackTest.php index 8cf5c02..4b5899d 100644 --- a/tests/FullStackTest.php +++ b/tests/FullStackTest.php @@ -356,6 +356,42 @@ function testCommandsAndHooksIncludeAllPublicMethods() $this->assertRunCommandViaApplicationEquals('get:serious', 'very serious'); $this->assertRunCommandViaApplicationContains('get:lost', 'Command "get:lost" is not defined.', [], 1); $this->assertRunCommandViaApplicationContains('get:both', 'Here is some data.', [], 3); + } + + /** + * @requires PHP 8.0 + */ + function testAttributeCommands() + { + // First, search for commandfiles in the 'alpha' + // directory. Note that this same functionality + // is tested more thoroughly in isolation in + // testCommandFileDiscovery.php + $discovery = new CommandFileDiscovery(); + $discovery + ->setSearchPattern('*CommandFile.php') + ->setIncludeFilesAtBase(false) + ->setSearchLocations(['alpha']); + + chdir(__DIR__); + $commandFiles = $discovery->discover('.', '\Consolidation\TestUtils'); + + $formatter = new FormatterManager(); + $formatter->addDefaultFormatters(); + $formatter->addDefaultSimplifiers(); + $hookManager = new HookManager(); + $commandProcessor = new CommandProcessor($hookManager); + $commandProcessor->setFormatterManager($formatter); + + // Create a new factory, and load all of the files + // discovered above. The command factory class is + // tested in isolation in testAnnotatedCommandFactory.php, + // but this is the only place where + $factory = new AnnotatedCommandFactory(); + $factory->setCommandProcessor($commandProcessor); + // $factory->addListener(...); + $factory->setIncludeAllPublicMethods(true); + $this->addDiscoveredCommands($factory, $commandFiles); // Test to see if an empty table omits the table headers (labels header) $this->assertRunCommandViaApplicationContains('tabularify apples peaches pumpkin pie', '----');