PK ?VjӢ phpunit.xml.distnu W+A
tests
src
FormatterInterface.php.php
OverrideRestructureInterface.php.php
RestructureInterface.php.php
ValidationInterface.php
Formatters/RenderDataInterface.php
StructuredData/ListDataInterface.php.php
StructuredData/RenderCellInterface.php.php
StructuredData/TableDataInterface.php.php
PK ?Vƻ
.editorconfignu W+A # This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[**.php]
indent_style = space
indent_size = 4
PK ?V1 1 LICENSEnu W+A Copyright (c) 2016 Consolidation Org Developers
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
PK ?VEIU U CONTRIBUTING.mdnu W+A # Contributing to Consolidation
Thank you for your interest in contributing to the Consolidation effort! Consolidation aims to provide reusable, loosely-coupled components useful for building command-line tools. Consolidation is built on top of Symfony Console, but aims to separate the tool from the implementation details of Symfony.
Here are some of the guidelines you should follow to make the most of your efforts:
## Code Style Guidelines
Consolidation adheres to the [PSR-2 Coding Style Guide](http://www.php-fig.org/psr/psr-2/) for PHP code.
## Pull Request Guidelines
Every pull request is run through:
- phpcs -n --standard=PSR2 src
- phpunit
- [Scrutinizer](https://scrutinizer-ci.com/g/consolidation/output-formatters/)
It is easy to run the unit tests and code sniffer locally; just run:
- composer cs
To run the code beautifier, which will fix many of the problems reported by phpcs:
- composer cbf
These two commands (`composer cs` and `composer cbf`) are defined in the `scripts` section of [composer.json](composer.json).
After submitting a pull request, please examine the Scrutinizer report. It is not required to fix all Scrutinizer issues; you may ignore recommendations that you disagree with. The spacing patches produced by Scrutinizer do not conform to PSR2 standards, and therefore should never be applied. DocBlock patches may be applied at your discression. Things that Scrutinizer identifies as a bug nearly always need to be addressed.
Pull requests must pass phpcs and phpunit in order to be merged; ideally, new functionality will also include new unit tests.
PK ?VH .travis.ymlnu W+A language: php
branches:
# Only test the master branch and SemVer tags.
only:
- master
- /^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+.*$/
php:
- 7.0
- 5.6
- 5.5
- 5.4
sudo: false
cache:
directories:
- vendor
- $HOME/.composer/cache
before_script:
- composer install
script:
- vendor/bin/phpunit
- vendor/bin/phpcs --standard=PSR2 -n src
after_success:
- travis_retry php vendor/bin/coveralls -v
PK ?V
mkdocs.ymlnu W+A site_name: Consolidation Output Formatters docs
theme: readthedocs
repo_url: https://github.com/consolidation/output-formatters
include_search: true
pages:
- Home: index.md
- API: api.md
PK ?Vˢ .github/pull_request_template.mdnu W+A ### Overview
This pull request:
- [ ] Fixes a bug
- [ ] Adds a feature
- [ ] Breaks backwards compatibility
- [ ] Has tests that cover changes
### Summary
Short overview of what changed.
### Description
Any additional information.
PK ?V .github/issue_template.mdnu W+A ### Steps to reproduce
What did you do?
### Expected behavior
Tell us what should happen
### Actual behavior
Tell us what happens instead
### System Configuration
Which O.S. and PHP version are you using?
PK ?V]<~ ~
composer.jsonnu W+A {
"name": "consolidation/output-formatters",
"description": "Format text by applying transformations provided by plug-in formatters.",
"license": "MIT",
"authors": [
{
"name": "Greg Anderson",
"email": "greg.1.anderson@greenknowe.org"
}
],
"autoload":{
"psr-4":{
"Consolidation\\OutputFormatters\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Consolidation\\TestUtils\\": "tests/src"
}
},
"require": {
"php": ">=5.4.0",
"symfony/console": "~2.5|~3.0",
"symfony/finder": "~2.5|~3.0",
"victorjonsson/markdowndocs": "^1.3"
},
"require-dev": {
"phpunit/phpunit": "4.*",
"satooshi/php-coveralls": "^1.0",
"squizlabs/php_codesniffer": "2.*"
},
"scripts": {
"api": "phpdoc-md generate src > docs/api.md",
"cs": "phpcs --standard=PSR2 -n src",
"cbf": "phpcbf --standard=PSR2 -n src",
"test": "phpunit --colors=always"
},
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
}
}
PK ?V CHANGELOG.mdnu W+A # Change Log
### 3.0.0 - 14 November 2016
- **Breaking** The RenderCellInterface is now provided a reference to the entire row data. Existing clients need only add the new parameter to their method defnition to update.
- Rename AssociativeList to PropertyList, as many people seemed to find the former name confusing. AssociativeList is still available for use to preserve backwards compatibility, but it is deprecated.
### 2.1.0 - 7 November 2016
- Add RenderCellCollections to structured lists, so that commands may add renderers to structured data without defining a new structured data subclass.
### 2.0.1 - 4 October 2016
- Throw an exception if the client requests a field that does not exist.
- Remove unwanted extra layer of nesting when formatting an PropertyList with an array formatter (json, yaml, etc.).
### 2.0.0 - 30 September 2016
- **Breaking** The default `string` format now converts non-string results into a tab-separated-value table if possible. Commands may select a single field to emit in this instance with an annotation: `@default-string-field email`. By this means, a given command may by default emit a single value, but also provide more rich output that may be shown by selecting --format=table, --format=yaml or the like. This change might cause some commands to produce output in situations that previously were not documented as producing output.
- **Breaking** FormatterManager::addFormatter() now takes the format identifier and a FormatterInterface, rather than an identifier and a Formatter classname (string).
- --field is a synonym for --fields with a single field.
- Wildcards and regular expressions can now be used in --fields expressions.
### 1.1.0 - 14 September 2016
Add tab-separated-value (tsv) formatter.
### 1.0.0 - 19 May 2016
First stable release.
PK ?V:# # tests/testFormatterOptions.phpnu W+A bind($definition);
return $input;
}
protected function getFormat(FormatterOptions $options, $defaults = [])
{
return $options->get(FormatterOptions::FORMAT, [], $options->get(FormatterOptions::DEFAULT_FORMAT, $defaults, ''));
}
public function testFormatterOptions() {
$configurationData = [
FormatterOptions::DEFAULT_FORMAT => 'table',
'test' => 'one',
'try' => 'two',
];
$userOptions = [
'try' => 'three',
];
$defaults = [
FormatterOptions::DEFAULT_FORMAT => 'var_export',
'try' => 'four',
'default-only' => 'defaulty',
];
// Create a StringInput object and ensure that Symfony Console is working right.
$input = $this->createStringInput('test --format=yaml --include-field-labels');
$testValue = $input->getOption(FormatterOptions::INCLUDE_FIELD_LABELS);
$this->assertTrue($testValue);
$testValue = $input->getOption(FormatterOptions::FORMAT);
$this->assertEquals('yaml', $testValue);
// $options->get() only returns the default parameter is there is
// no matching key in configuration, userOptions or defaults.
$options = new FormatterOptions($configurationData, $userOptions);
$this->assertEquals('', $options->get('default-only'));
$this->assertEquals('defaulty', $options->get('default-only', $defaults));
$this->assertEquals('defaulty', $options->get('default-only', $defaults, 'irrelevant'));
$this->assertEquals('three', $options->get('try'));
$this->assertEquals('three', $options->get('try', $defaults));
$this->assertEquals('three', $options->get('try', $defaults, 'irrelevant'));
$this->assertFalse($options->get('no-such-key'));
$this->assertFalse($options->get('no-such-key', $defaults));
$this->assertEquals('last-chance', $options->get('no-such-key', $defaults, 'last-chance'));
// Change a user option
$options = new FormatterOptions($configurationData, $userOptions);
$options->setOption('try', 'changed');
$this->assertEquals('changed', $options->get('try'));
$this->assertEquals('changed', $options->get('try', $defaults));
$this->assertEquals('changed', $options->get('try', $defaults, 'irrelevant'));
// Configuration has higher priority than defaults
$options = new FormatterOptions($configurationData, $userOptions);
$this->assertEquals('table', $this->getFormat($options));
$this->assertEquals('table', $this->getFormat($options, $defaults));
// Override has higher priority than configuration and defaults
$options = new FormatterOptions($configurationData, $userOptions);
$newOptions = $options->override([FormatterOptions::DEFAULT_FORMAT => 'json']);
$this->assertEquals('json', $this->getFormat($newOptions));
$this->assertEquals('json', $this->getFormat($newOptions, $defaults));
$options = new FormatterOptions($configurationData, $userOptions);
$options->setConfigurationDefault(FormatterOptions::DEFAULT_FORMAT, 'php');
$this->assertEquals('table', $this->getFormat($options));
$options = new FormatterOptions($configurationData, $userOptions);
$options->setConfigurationData([]);
$this->assertEquals('', $this->getFormat($options));
// It is only possible to override options that appear in '$default'
// with $input; if there are no defaults, then the --format=yaml
// option will not be picked up.
$options = new FormatterOptions($configurationData, $userOptions);
$options->setInput($input);
$this->assertEquals('table', $options->get(FormatterOptions::DEFAULT_FORMAT));
$this->assertEquals('table', $options->get(FormatterOptions::DEFAULT_FORMAT, $defaults, 'irrelevant'));
// We won't see the default value unless the configuration value is empty.
$options = new FormatterOptions([], $userOptions);
$this->assertEquals('var_export', $options->get(FormatterOptions::DEFAULT_FORMAT, $defaults, 'irrelevant'));
}
}
PK ?Vl tests/testFormatters.phpnu W+A formatterManager = new FormatterManager();
}
function assertFormattedOutputMatches($expected, $format, $data, FormatterOptions $options = null, $userOptions = []) {
if (!$options) {
$options = new FormatterOptions();
}
$options->setOptions($userOptions);
$output = new BufferedOutput();
$this->formatterManager->write($output, $format, $data, $options);
$actual = preg_replace('#[ \t]*$#sm', '', $output->fetch());
$this->assertEquals(rtrim($expected), rtrim($actual));
}
function testSimpleYaml()
{
$data = [
'one' => 'a',
'two' => 'b',
'three' => 'c',
];
$expected = <<assertFormattedOutputMatches($expected, 'yaml', $data);
}
function testNestedYaml()
{
$data = [
'one' => [
'i' => ['a', 'b', 'c'],
],
'two' => [
'ii' => ['q', 'r', 's'],
],
'three' => [
'iii' => ['t', 'u', 'v'],
],
];
$expected = <<assertFormattedOutputMatches($expected, 'yaml', $data);
}
function testSimpleJson()
{
$data = [
'one' => 'a',
'two' => 'b',
'three' => 'c',
];
$expected = <<assertFormattedOutputMatches($expected, 'json', $data);
}
function testSerializeFormat()
{
$data = [
'one' => 'a',
'two' => 'b',
'three' => 'c',
];
$expected = 'a:3:{s:3:"one";s:1:"a";s:3:"two";s:1:"b";s:5:"three";s:1:"c";}';
$this->assertFormattedOutputMatches($expected, 'php', $data);
}
function testNestedJson()
{
$data = [
'one' => [
'i' => ['a', 'b', 'c'],
],
'two' => [
'ii' => ['q', 'r', 's'],
],
'three' => [
'iii' => ['t', 'u', 'v'],
],
];
$expected = <<assertFormattedOutputMatches($expected, 'json', $data);
}
function testSimplePrintR()
{
$data = [
'one' => 'a',
'two' => 'b',
'three' => 'c',
];
$expected = << a
[two] => b
[three] => c
)
EOT;
$this->assertFormattedOutputMatches($expected, 'print-r', $data);
}
function testNestedPrintR()
{
$data = [
'one' => [
'i' => ['a', 'b', 'c'],
],
'two' => [
'ii' => ['q', 'r', 's'],
],
'three' => [
'iii' => ['t', 'u', 'v'],
],
];
$expected = << Array
(
[i] => Array
(
[0] => a
[1] => b
[2] => c
)
)
[two] => Array
(
[ii] => Array
(
[0] => q
[1] => r
[2] => s
)
)
[three] => Array
(
[iii] => Array
(
[0] => t
[1] => u
[2] => v
)
)
)
EOT;
$this->assertFormattedOutputMatches($expected, 'print-r', $data);
}
function testSimpleVarExport()
{
$data = [
'one' => 'a',
'two' => 'b',
'three' => 'c',
];
$expected = << 'a',
'two' => 'b',
'three' => 'c',
)
EOT;
$this->assertFormattedOutputMatches($expected, 'var_export', $data);
}
function testNestedVarExport()
{
$data = [
'one' => [
'i' => ['a', 'b', 'c'],
],
'two' => [
'ii' => ['q', 'r', 's'],
],
'three' => [
'iii' => ['t', 'u', 'v'],
],
];
$expected = <<
array (
'i' =>
array (
0 => 'a',
1 => 'b',
2 => 'c',
),
),
'two' =>
array (
'ii' =>
array (
0 => 'q',
1 => 'r',
2 => 's',
),
),
'three' =>
array (
'iii' =>
array (
0 => 't',
1 => 'u',
2 => 'v',
),
),
)
EOT;
$this->assertFormattedOutputMatches($expected, 'var_export', $data);
}
function testList()
{
$data = [
'one' => 'a',
'two' => 'b',
'three' => 'c',
];
$expected = <<assertFormattedOutputMatches($expected, 'list', $data);
}
/**
* @expectedException \Consolidation\OutputFormatters\Exception\UnknownFormatException
* @expectedExceptionCode 1
* @expectedExceptionMessage The requested format, 'no-such-format', is not available.
*/
function testBadFormat()
{
$this->assertFormattedOutputMatches('Will fail, not return', 'no-such-format', ['a' => 'b']);
}
/**
* @expectedException \Consolidation\OutputFormatters\Exception\IncompatibleDataException
* @expectedExceptionCode 1
* @expectedExceptionMessage Data provided to Consolidation\OutputFormatters\Formatters\CsvFormatter must be one of an instance of Consolidation\OutputFormatters\StructuredData\RowsOfFields, an instance of Consolidation\OutputFormatters\StructuredData\PropertyList or an array. Instead, a string was provided.
*/
function testBadDataTypeForCsv()
{
$this->assertFormattedOutputMatches('Will fail, not return', 'csv', 'String cannot be converted to csv');
}
/**
* @expectedException \Consolidation\OutputFormatters\Exception\IncompatibleDataException
* @expectedExceptionCode 1
* @expectedExceptionMessage Data provided to Consolidation\OutputFormatters\Formatters\JsonFormatter must be an array. Instead, a string was provided.
*/
function testBadDataTypeForJson()
{
$this->assertFormattedOutputMatches('Will fail, not return', 'json', 'String cannot be converted to json');
}
function testNoFormatterSelected()
{
$data = 'Hello';
$expected = $data;
$this->assertFormattedOutputMatches($expected, '', $data);
}
function testRenderTableAsString()
{
$data = new RowsOfFields([['f1' => 'A', 'f2' => 'B', 'f3' => 'C'], ['f1' => 'x', 'f2' => 'y', 'f3' => 'z']]);
$expected = "A\tB\tC\nx\ty\tz";
$this->assertFormattedOutputMatches($expected, 'string', $data);
}
function testRenderTableAsStringWithSingleField()
{
$data = new RowsOfFields([['f1' => 'q', 'f2' => 'r', 'f3' => 's'], ['f1' => 'x', 'f2' => 'y', 'f3' => 'z']]);
$expected = "q\nx";
$options = new FormatterOptions([FormatterOptions::DEFAULT_STRING_FIELD => 'f1']);
$this->assertFormattedOutputMatches($expected, 'string', $data, $options);
}
function testRenderTableAsStringWithSingleFieldAndUserSelectedField()
{
$data = new RowsOfFields([['f1' => 'q', 'f2' => 'r', 'f3' => 's'], ['f1' => 'x', 'f2' => 'y', 'f3' => 'z']]);
$expected = "r\ny";
$options = new FormatterOptions([FormatterOptions::DEFAULT_STRING_FIELD => 'f1']);
$this->assertFormattedOutputMatches($expected, 'string', $data, $options, ['fields' => 'f2']);
}
function testSimpleCsv()
{
$data = ['a', 'b', 'c'];
$expected = "a,b,c";
$this->assertFormattedOutputMatches($expected, 'csv', $data);
}
function testLinesOfCsv()
{
$data = [['a', 'b', 'c'], ['x', 'y', 'z']];
$expected = "a,b,c\nx,y,z";
$this->assertFormattedOutputMatches($expected, 'csv', $data);
}
function testCsvWithEscapedValues()
{
$data = ["Red apple", "Yellow lemon"];
$expected = '"Red apple","Yellow lemon"';
$this->assertFormattedOutputMatches($expected, 'csv', $data);
}
function testCsvWithEmbeddedSingleQuote()
{
$data = ["John's book", "Mary's laptop"];
$expected = <<assertFormattedOutputMatches($expected, 'csv', $data);
}
function testCsvWithEmbeddedDoubleQuote()
{
$data = ['The "best" solution'];
$expected = <<assertFormattedOutputMatches($expected, 'csv', $data);
}
function testCsvBothKindsOfQuotes()
{
$data = ["John's \"new\" book", "Mary's \"modified\" laptop"];
$expected = <<assertFormattedOutputMatches($expected, 'csv', $data);
}
function testSimpleTsv()
{
$data = ['a', 'b', 'c'];
$expected = "a\tb\tc";
$this->assertFormattedOutputMatches($expected, 'tsv', $data);
}
function testLinesOfTsv()
{
$data = [['a', 'b', 'c'], ['x', 'y', 'z']];
$expected = "a\tb\tc\nx\ty\tz";
$this->assertFormattedOutputMatches($expected, 'tsv', $data);
}
function testTsvBothKindsOfQuotes()
{
$data = ["John's \"new\" book", "Mary's \"modified\" laptop"];
$expected = "John's \"new\" book\tMary's \"modified\" laptop";
$this->assertFormattedOutputMatches($expected, 'tsv', $data);
}
function testTsvWithEscapedValues()
{
$data = ["Red apple", "Yellow lemon", "Embedded\ttab"];
$expected = "Red apple\tYellow lemon\tEmbedded\\ttab";
$this->assertFormattedOutputMatches($expected, 'tsv', $data);
}
protected function missingCellTableExampleData()
{
$data = [
[
'one' => 'a',
'two' => 'b',
'three' => 'c',
],
[
'one' => 'x',
'three' => 'z',
],
];
return new RowsOfFields($data);
}
function testTableWithMissingCell()
{
$data = $this->missingCellTableExampleData();
$expected = <<assertFormattedOutputMatches($expected, 'table', $data);
$expectedCsv = <<assertFormattedOutputMatches($expectedCsv, 'csv', $data);
$expectedTsv = <<assertFormattedOutputMatches($expectedTsv, 'tsv', $data);
$expectedTsvWithHeaders = <<assertFormattedOutputMatches($expectedTsvWithHeaders, 'tsv', $data, new FormatterOptions(), ['include-field-labels' => true]);
}
function testTableWithWordWrapping()
{
$options = new FormatterOptions();
$options->setWidth(42);
$data = [
[
'first' => 'This is a really long cell that contains a lot of data. When it is rendered, it should be wrapped across multiple lines.',
'second' => 'This is the second column of the same table. It is also very long, and should be wrapped across multiple lines, just like the first column.',
]
];
$data = new RowsOfFields($data);
$expected = <<assertFormattedOutputMatches($expected, 'table', $data, $options);
}
protected function simpleTableExampleData()
{
$data = [
'id-123' =>
[
'one' => 'a',
'two' => 'b',
'three' => 'c',
],
'id-456' =>
[
'one' => 'x',
'two' => 'y',
'three' => 'z',
],
];
return new RowsOfFields($data);
}
/**
* @expectedException \Consolidation\OutputFormatters\Exception\InvalidFormatException
* @expectedExceptionCode 1
* @expectedExceptionMessage The format table cannot be used with the data produced by this command, which was an array. Valid formats are: csv,json,list,php,print-r,string,tsv,var_export,xml,yaml
*/
function testIncompatibleDataForTableFormatter()
{
$data = $this->simpleTableExampleData()->getArrayCopy();
$this->assertFormattedOutputMatches('Should throw an exception before comparing the table data', 'table', $data);
}
/**
* @expectedException \Consolidation\OutputFormatters\Exception\InvalidFormatException
* @expectedExceptionCode 1
* @expectedExceptionMessage The format sections cannot be used with the data produced by this command, which was an array. Valid formats are: csv,json,list,php,print-r,string,tsv,var_export,xml,yaml
*/
function testIncompatibleDataForSectionsFormatter()
{
$data = $this->simpleTableExampleData()->getArrayCopy();
$this->assertFormattedOutputMatches('Should throw an exception before comparing the table data', 'sections', $data);
}
function testSimpleTable()
{
$data = $this->simpleTableExampleData();
$expected = <<assertFormattedOutputMatches($expected, 'table', $data);
$expectedBorderless = <<assertFormattedOutputMatches($expectedBorderless, 'table', $data, new FormatterOptions(['table-style' => 'borderless']));
$expectedJson = <<assertFormattedOutputMatches($expectedJson, 'json', $data);
$expectedCsv = <<assertFormattedOutputMatches($expectedCsv, 'csv', $data);
$expectedList = <<assertFormattedOutputMatches($expectedList, 'list', $data);
}
protected function tableWithAlternativesExampleData()
{
$data = [
'id-123' =>
[
'one' => 'a',
'two' => ['this', 'that', 'the other thing'],
'three' => 'c',
],
'id-456' =>
[
'one' => 'x',
'two' => 'y',
'three' => ['apples', 'oranges'],
],
];
return new RowsOfFieldsWithAlternatives($data);
}
function testTableWithAlternatives()
{
$data = $this->tableWithAlternativesExampleData();
$expected = <<assertFormattedOutputMatches($expected, 'table', $data);
$expectedBorderless = <<assertFormattedOutputMatches($expectedBorderless, 'table', $data, new FormatterOptions(['table-style' => 'borderless']));
$expectedJson = <<assertFormattedOutputMatches($expectedJson, 'json', $data);
$expectedCsv = <<assertFormattedOutputMatches($expectedCsv, 'csv', $data);
$expectedList = <<assertFormattedOutputMatches($expectedList, 'list', $data);
}
function testSimpleTableWithFieldLabels()
{
$data = $this->simpleTableExampleData();
$configurationData = new FormatterOptions(
[
'field-labels' => ['one' => 'Ichi', 'two' => 'Ni', 'three' => 'San'],
'row-labels' => ['id-123' => 'Walrus', 'id-456' => 'Carpenter'],
]
);
$configurationDataAnnotationFormat = new FormatterOptions(
[
'field-labels' => "one: Uno\ntwo: Dos\nthree: Tres",
]
);
$expected = <<assertFormattedOutputMatches($expected, 'table', $data, $configurationData);
$expectedSidewaysTable = <<assertFormattedOutputMatches($expectedSidewaysTable, 'table', $data, $configurationData->override(['list-orientation' => true]));
$expectedAnnotationFormatConfigData = <<assertFormattedOutputMatches($expectedAnnotationFormatConfigData, 'table', $data, $configurationDataAnnotationFormat);
$expectedWithNoFields = <<assertFormattedOutputMatches($expectedWithNoFields, 'table', $data, $configurationData, ['include-field-labels' => false]);
$expectedWithReorderedFields = <<assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => ['three', 'one']]);
$this->assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => ['San', 'Ichi']]);
$this->assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => 'San,Ichi']);
$expectedWithRegexField = <<assertFormattedOutputMatches($expectedWithRegexField, 'table', $data, $configurationData, ['fields' => ['/e$/']]);
$this->assertFormattedOutputMatches($expectedWithRegexField, 'table', $data, $configurationData, ['fields' => ['*e']]);
$this->assertFormattedOutputMatches($expectedWithRegexField, 'table', $data, $configurationData, ['default-fields' => ['*e']]);
$expectedSections = <<assertFormattedOutputMatches($expectedSections, 'sections', $data, $configurationData);
$expectedJson = <<assertFormattedOutputMatches($expectedJson, 'json', $data, $configurationData, ['fields' => ['San', 'Ichi']]);
$expectedSingleField = <<assertFormattedOutputMatches($expectedSingleField, 'table', $data, $configurationData, ['field' => 'San']);
}
/**
* @expectedException \Consolidation\OutputFormatters\Exception\UnknownFieldException
* @expectedExceptionCode 1
* @expectedExceptionMessage The requested field, 'Shi', is not defined.
*/
function testNoSuchFieldException()
{
$configurationData = new FormatterOptions(
[
'field-labels' => ['one' => 'Ichi', 'two' => 'Ni', 'three' => 'San'],
'row-labels' => ['id-123' => 'Walrus', 'id-456' => 'Carpenter'],
]
);
$data = $this->simpleTableExampleData();
$this->assertFormattedOutputMatches('Will throw before comparing', 'table', $data, $configurationData, ['field' => 'Shi']);
}
protected function simpleListExampleData()
{
$data = [
'one' => 'apple',
'two' => 'banana',
'three' => 'carrot',
];
return new PropertyList($data);
}
// Test with the deprecated data structure
protected function simpleListExampleDataUsingAssociativeList()
{
$data = [
'one' => 'apple',
'two' => 'banana',
'three' => 'carrot',
];
return new AssociativeList($data);
}
/**
* @expectedException \Consolidation\OutputFormatters\Exception\InvalidFormatException
* @expectedExceptionCode 1
* @expectedExceptionMessage The format table cannot be used with the data produced by this command, which was an array. Valid formats are: csv,json,list,php,print-r,string,tsv,var_export,xml,yaml
*/
function testIncompatibleListDataForTableFormatter()
{
$data = $this->simpleListExampleData();
$this->assertFormattedOutputMatches('Should throw an exception before comparing the table data', 'table', $data->getArrayCopy());
}
function testEmptyList()
{
$data = new RowsOfFields([]);
$expected = <<setFieldLabels(['one' => 'I', 'two' => 'II', 'three' => 'III']);
$this->assertFormattedOutputMatches($expected, 'table', $data, $formatterOptionsWithFieldLables);
}
function testSimpleList()
{
$expected = <<simpleListExampleDataUsingAssociativeList();
$this->assertFormattedOutputMatches($expected, 'table', $data);
$data = $this->simpleListExampleData();
$this->assertFormattedOutputMatches($expected, 'table', $data);
$expected = <<setFieldLabels(['one' => 'I', 'two' => 'II', 'three' => 'III']);
$this->assertFormattedOutputMatches($expected, 'table', $data, $formatterOptionsWithFieldLables);
$expectedDrushStyleTable = <<setTableStyle('compact')
->setListDelimiter(':');
$this->assertFormattedOutputMatches($expectedDrushStyleTable, 'table', $data, $formatterOptionsWithFieldLables);
// Adding an extra field that does not exist in the data set should not change the output
$formatterOptionsWithExtraFieldLables = new FormatterOptions();
$formatterOptionsWithExtraFieldLables
->setFieldLabels(['one' => 'I', 'two' => 'II', 'three' => 'III', 'four' => 'IV']);
$this->assertFormattedOutputMatches($expected, 'table', $data, $formatterOptionsWithExtraFieldLables);
$expectedRotated = <<assertFormattedOutputMatches($expectedRotated, 'table', $data, new FormatterOptions(['list-orientation' => false]));
$expectedList = <<< EOT
apple
banana
carrot
EOT;
$this->assertFormattedOutputMatches($expectedList, 'list', $data);
$expectedReorderedList = <<< EOT
carrot
apple
EOT;
$options = new FormatterOptions([FormatterOptions::FIELDS => 'three,one']);
$this->assertFormattedOutputMatches($expectedReorderedList, 'list', $data, $options);
$expectedCsv = <<< EOT
One,Two,Three
apple,banana,carrot
EOT;
$this->assertFormattedOutputMatches($expectedCsv, 'csv', $data);
$expectedCsvNoHeaders = 'apple,banana,carrot';
$this->assertFormattedOutputMatches($expectedCsvNoHeaders, 'csv', $data, new FormatterOptions(), ['include-field-labels' => false]);
// Next, configure the formatter options with 'include-field-labels',
// but set --include-field-labels to turn the option back on again.
$options = new FormatterOptions(['include-field-labels' => false]);
$input = new StringInput('test --include-field-labels');
$optionDefinitions = [
new InputArgument('unused', InputArgument::REQUIRED),
new InputOption('include-field-labels', null, InputOption::VALUE_NONE),
];
$definition = new InputDefinition($optionDefinitions);
$input->bind($definition);
$testValue = $input->getOption('include-field-labels');
$this->assertTrue($testValue);
$hasFieldLabels = $input->hasOption('include-field-labels');
$this->assertTrue($hasFieldLabels);
$this->assertFormattedOutputMatches($expectedCsvNoHeaders, 'csv', $data, $options);
$options->setInput($input);
$this->assertFormattedOutputMatches($expectedCsv, 'csv', $data, $options);
}
protected function associativeListWithRenderer()
{
$data = [
'one' => 'apple',
'two' => ['banana', 'plantain'],
'three' => 'carrot',
'four' => ['peaches', 'pumpkin pie'],
];
$list = new PropertyList($data);
$list->addRendererFunction(
function ($key, $cellData, FormatterOptions $options)
{
if (is_array($cellData)) {
return implode(',', $cellData);
}
return $cellData;
}
);
return $list;
}
protected function associativeListWithCsvCells()
{
$data = [
'one' => 'apple',
'two' => ['banana', 'plantain'],
'three' => 'carrot',
'four' => ['peaches', 'pumpkin pie'],
];
return new PropertyListWithCsvCells($data);
}
function testPropertyListWithCsvCells()
{
$this->doPropertyListWithCsvCells($this->associativeListWithRenderer());
$this->doPropertyListWithCsvCells($this->associativeListWithCsvCells());
}
function doPropertyListWithCsvCells($data)
{
$expected = <<assertFormattedOutputMatches($expected, 'table', $data);
$expectedList = <<< EOT
apple
banana,plantain
carrot
peaches,pumpkin pie
EOT;
$this->assertFormattedOutputMatches($expectedList, 'list', $data);
$expectedCsv = <<< EOT
One,Two,Three,Four
apple,"banana,plantain",carrot,"peaches,pumpkin pie"
EOT;
$this->assertFormattedOutputMatches($expectedCsv, 'csv', $data);
$expectedCsvNoHeaders = 'apple,"banana,plantain",carrot,"peaches,pumpkin pie"';
$this->assertFormattedOutputMatches($expectedCsvNoHeaders, 'csv', $data, new FormatterOptions(), ['include-field-labels' => false]);
$expectedTsv = <<< EOT
apple\tbanana,plantain\tcarrot\tpeaches,pumpkin pie
EOT;
$this->assertFormattedOutputMatches($expectedTsv, 'tsv', $data);
}
function testSimpleListWithFieldLabels()
{
$data = $this->simpleListExampleData();
$configurationData = new FormatterOptions(
[
'field-labels' => ['one' => 'Ichi', 'two' => 'Ni', 'three' => 'San'],
]
);
$expected = <<assertFormattedOutputMatches($expected, 'table', $data, $configurationData);
$expectedWithReorderedFields = <<assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => ['three', 'one']]);
$this->assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => ['San', 'Ichi']]);
$expectedJson = <<assertFormattedOutputMatches($expectedJson, 'json', $data, $configurationData, ['fields' => ['San', 'Ichi']]);
}
function testSimpleXml()
{
$data = [
'name' => 'primary',
'description' => 'The primary colors of the color wheel.',
'colors' =>
[
'red',
'yellow',
'blue',
],
];
$expected = <<
The primary colors of the color wheel.
red
yellow
blue
EOT;
$this->assertFormattedOutputMatches($expected, 'xml', $data);
}
function domDocumentData()
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$document = $dom->createElement('document');
$dom->appendChild($document);
$document->setAttribute('name', 'primary');
$description = $dom->createElement('description');
$document->appendChild($description);
$description->appendChild($dom->createTextNode('The primary colors of the color wheel.'));
$this->domCreateElements($dom, $document, 'color', ['red', 'yellow', 'blue']);
return $dom;
}
function domCreateElements($dom, $element, $name, $data)
{
$container = $dom->createElement("{$name}s");
$element->appendChild($container);
foreach ($data as $value) {
$child = $dom->createElement($name);
$container->appendChild($child);
$child->appendChild($dom->createTextNode($value));
}
}
function complexDomDocumentData()
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$document = $dom->createElement('document');
$dom->appendChild($document);
$document->setAttribute('name', 'widget-collection');
$description = $dom->createElement('description');
$document->appendChild($description);
$description->appendChild($dom->createTextNode('A couple of widgets.'));
$widgets = $dom->createElement('widgets');
$document->appendChild($widgets);
$widget = $dom->createElement('widget');
$widgets->appendChild($widget);
$widget->setAttribute('name', 'usual');
$this->domCreateElements($dom, $widget, 'color', ['red', 'yellow', 'blue']);
$this->domCreateElements($dom, $widget, 'shape', ['square', 'circle', 'triangle']);
$widget = $dom->createElement('widget');
$widgets->appendChild($widget);
$widget->setAttribute('name', 'unusual');
$this->domCreateElements($dom, $widget, 'color', ['muave', 'puce', 'umber']);
$this->domCreateElements($dom, $widget, 'shape', ['elipse', 'rhombus', 'trapazoid']);
return $dom;
}
function domDocumentTestValues()
{
$expectedXml = <<
The primary colors of the color wheel.
red
yellow
blue
EOT;
$expectedJson = <<
A couple of widgets.
red
yellow
blue
square
circle
triangle
muave
puce
umber
elipse
rhombus
trapazoid
EOT;
$expectedComplexJson = <<domDocumentData(),
$expectedXml,
$expectedJson,
],
[
$this->complexDomDocumentData(),
$expectedComplexXml,
$expectedComplexJson,
],
];
}
/**
* @dataProvider domDocumentTestValues
*/
function testDomData($data, $expectedXml, $expectedJson)
{
$this->assertFormattedOutputMatches($expectedXml, 'xml', $data);
$this->assertFormattedOutputMatches($expectedJson, 'json', $data);
// Check to see if we get the same xml data if we convert from
// DOM -> array -> DOM.
$expectedJsonAsArray = (array)json_decode($expectedJson);
$this->assertFormattedOutputMatches($expectedXml, 'xml', $expectedJsonAsArray);
}
/**
* @expectedException \Exception
* @expectedExceptionCode 1
* @expectedExceptionMessage Data provided to Consolidation\OutputFormatters\Formatters\XmlFormatter must be either an instance of DOMDocument or an array. Instead, a string was provided.
*/
function testDataTypeForXmlFormatter()
{
$this->assertFormattedOutputMatches('Will fail, not return', 'xml', 'Strings cannot be converted to XML');
}
}
PK ?VF tests/testAPIDocs.phpnu W+A markTestIncomplete(
'API generation has slight variations when run on CI server. This test is therefore skipped on CI until we can make the test results consistent.'
);
}
$testDocs = tempnam(sys_get_temp_dir(), 'TestAPIDocs.md');
$currentDocs = getcwd() . '/docs/api.md';
passthru("vendor/bin/phpdoc-md generate src > $testDocs");
$testDocsContent = file_get_contents($testDocs);
$currentDocsContent = file_get_contents($currentDocs);
$testDocsContent = str_replace (array("\r\n", "\r"), "\n", $testDocsContent);
$currentDocsContent = str_replace (array("\r\n", "\r"), "\n", $currentDocsContent);
$this->assertEquals($testDocsContent, $currentDocsContent, "API docuementation out of date. Run 'composer api' to update.");
}
}
PK ?Vnx;' ' tests/testIncompatibleData.phpnu W+A formatterManager = new FormatterManager();
}
protected function assertIncompatibleDataMessage($expected, $formatter, $data)
{
$e = new IncompatibleDataException($formatter, $data, $formatter->validDataTypes());
$this->assertEquals($expected, $e->getMessage());
}
public function testIncompatibleData()
{
$tableFormatter = $this->formatterManager->getFormatter('table');
$this->assertIncompatibleDataMessage('Data provided to Consolidation\OutputFormatters\Formatters\TableFormatter must be either an instance of Consolidation\OutputFormatters\StructuredData\RowsOfFields or an instance of Consolidation\OutputFormatters\StructuredData\PropertyList. Instead, a string was provided.', $tableFormatter, 'a string');
$this->assertIncompatibleDataMessage('Data provided to Consolidation\OutputFormatters\Formatters\TableFormatter must be either an instance of Consolidation\OutputFormatters\StructuredData\RowsOfFields or an instance of Consolidation\OutputFormatters\StructuredData\PropertyList. Instead, an instance of Consolidation\OutputFormatters\FormatterManager was provided.', $tableFormatter, $this->formatterManager);
$this->assertIncompatibleDataMessage('Data provided to Consolidation\OutputFormatters\Formatters\TableFormatter must be either an instance of Consolidation\OutputFormatters\StructuredData\RowsOfFields or an instance of Consolidation\OutputFormatters\StructuredData\PropertyList. Instead, an array was provided.', $tableFormatter, []);
$this->assertIncompatibleDataMessage('Data provided to Consolidation\OutputFormatters\Formatters\TableFormatter must be either an instance of Consolidation\OutputFormatters\StructuredData\RowsOfFields or an instance of Consolidation\OutputFormatters\StructuredData\PropertyList. Instead, an instance of Consolidation\OutputFormatters\StructuredData\PropertyList was provided.', $tableFormatter, new PropertyList([]));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Undescribable data error: NULL
*/
public function testUndescribableData()
{
$tableFormatter = $this->formatterManager->getFormatter('table');
$data = $tableFormatter->validate(null);
$this->assertEquals('Will throw before comparing.', $data);
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Data provided to Consolidation\OutputFormatters\Formatters\TableFormatter must be either an instance of Consolidation\OutputFormatters\StructuredData\RowsOfFields or an instance of Consolidation\OutputFormatters\StructuredData\PropertyList. Instead, a string was provided.
*/
public function testInvalidTableData()
{
$tableFormatter = $this->formatterManager->getFormatter('table');
$data = $tableFormatter->validate('bad data type');
$this->assertEquals('Will throw before comparing.', $data);
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Data provided to Consolidation\OutputFormatters\Formatters\SectionsFormatter must be an instance of Consolidation\OutputFormatters\StructuredData\RowsOfFields. Instead, a string was provided.
*/
public function testInvalidSectionsData()
{
$tableFormatter = $this->formatterManager->getFormatter('sections');
$data = $tableFormatter->validate('bad data type');
$this->assertEquals('Will throw before comparing.', $data);
}
}
PK ?VK$d * tests/src/RowsOfFieldsWithAlternatives.phpnu W+A formatterManager = new FormatterManager();
$this->formatterManager->addDefaultFormatters();
$this->formatterManager->addDefaultSimplifiers();
}
function testValidFormats()
{
$arrayObjectRef = new \ReflectionClass('\ArrayObject');
$associativeListRef = new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\PropertyList');
$rowsOfFieldsRef = new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\RowsOfFields');
$notADataType = new \ReflectionClass('\Consolidation\OutputFormatters\FormatterManager');
$jsonFormatter = $this->formatterManager->getFormatter('json');
$isValid = $this->formatterManager->isValidFormat($jsonFormatter, $notADataType);
$this->assertFalse($isValid);
$isValid = $this->formatterManager->isValidFormat($jsonFormatter, new \ArrayObject());
$this->assertTrue($isValid);
$isValid = $this->formatterManager->isValidFormat($jsonFormatter, $arrayObjectRef);
$this->assertTrue($isValid);
$isValid = $this->formatterManager->isValidFormat($jsonFormatter, []);
$this->assertTrue($isValid);
$isValid = $this->formatterManager->isValidFormat($jsonFormatter, $associativeListRef);
$this->assertTrue($isValid);
$isValid = $this->formatterManager->isValidFormat($jsonFormatter, $rowsOfFieldsRef);
$this->assertTrue($isValid);
$sectionsFormatter = $this->formatterManager->getFormatter('sections');
$isValid = $this->formatterManager->isValidFormat($sectionsFormatter, $notADataType);
$this->assertFalse($isValid);
$isValid = $this->formatterManager->isValidFormat($sectionsFormatter, []);
$this->assertFalse($isValid);
$isValid = $this->formatterManager->isValidFormat($sectionsFormatter, $arrayObjectRef);
$this->assertFalse($isValid);
$isValid = $this->formatterManager->isValidFormat($sectionsFormatter, $rowsOfFieldsRef);
$this->assertTrue($isValid);
$isValid = $this->formatterManager->isValidFormat($sectionsFormatter, $associativeListRef);
$this->assertFalse($isValid);
// Check to see which formats can handle a simple array
$validFormats = $this->formatterManager->validFormats([]);
$this->assertEquals('csv,json,list,php,print-r,string,tsv,var_export,xml,yaml', implode(',', $validFormats));
// Check to see which formats can handle an PropertyList
$validFormats = $this->formatterManager->validFormats($associativeListRef);
$this->assertEquals('csv,json,list,php,print-r,string,table,tsv,var_export,xml,yaml', implode(',', $validFormats));
// Check to see which formats can handle an RowsOfFields
$validFormats = $this->formatterManager->validFormats($rowsOfFieldsRef);
$this->assertEquals('csv,json,list,php,print-r,sections,string,table,tsv,var_export,xml,yaml', implode(',', $validFormats));
// TODO: it woud be better if this returned an empty set instead of 'string'.
$validFormats = $this->formatterManager->validFormats($notADataType);
$this->assertEquals('string', implode(',', $validFormats));
}
function testAutomaticOptions()
{
$rowsOfFieldsRef = new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\RowsOfFields');
$formatterOptions = new FormatterOptions(
[
FormatterOptions::FIELD_LABELS => "name: Name\nphone_number: Phone Number",
]
);
$inputOptions = $this->formatterManager->automaticOptions($formatterOptions, $rowsOfFieldsRef);
$this->assertInputOptionDescriptionsEquals("Format the result data. Available formats: csv,json,list,php,print-r,sections,string,table,tsv,var_export,xml,yaml [Default: 'table']\nAvailable fields: Name (name), Phone Number (phone_number) [Default: '']\nSelect just one field, and force format to 'string'. [Default: '']", $inputOptions);
}
function assertInputOptionDescriptionsEquals($expected, $inputOptions)
{
$descriptions = [];
foreach ($inputOptions as $inputOption) {
$descriptions[] = $inputOption->getDescription() . " [Default: '" . $inputOption->getDefault() . "']";
}
$this->assertEquals($expected, implode("\n", $descriptions));
}
}
PK ?Vxm@ @
.gitignorenu W+A .DS_Store
.idea
phpunit.xml
build
vendor
composer.lock
main.php
PK ?V<k* * README.mdnu W+A # Consolidation\OutputFormatters
Apply transformations to structured data to write output in different formats.
[![Travis CI](https://travis-ci.org/consolidation/output-formatters.svg?branch=master)](https://travis-ci.org/consolidation/output-formatters) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/consolidation/output-formatters/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/consolidation/output-formatters/?branch=master) [![Coverage Status](https://coveralls.io/repos/github/consolidation/output-formatters/badge.svg?branch=master)](https://coveralls.io/github/consolidation/output-formatters?branch=master) [![License](https://poser.pugx.org/consolidation/output-formatters/license)](https://packagist.org/packages/consolidation/output-formatters)
## Component Status
Currently in use in [Robo](https://github.com/consolidation/Robo) (1.x+), [Drush](https://github.com/drush-ops/drush) (9.x+) and [Terminus](https://github.com/pantheon-systems/terminus) (1.x+).
## Motivation
Formatters are used to allow simple commandline tool commands to be implemented in a manner that is completely independent from the Symfony Console output interfaces. A command receives its input via its method parameters, and returns its result as structured data (e.g. a php standard object or array). The structured data is then formatted by a formatter, and the result is printed.
This process is managed by the [Consolidation/AnnotationCommand](https://github.com/consolidation/annotation-command) project.
## Library Usage
This is a library intended to be used in some other project. Require from your composer.json file:
```
"require": {
"consolidation/output-formatters": "~3"
},
```
If you require the feature that allows a data table to be automatically reduced to a single element when the `string` format is selected, then you should require version "~2" instead. In most other respects, the 1.x and 2.x versions are compatible. See the [CHANGELOG](CHANGELOG.md) for details.
## Example Formatter
Simple formatters are very easy to write.
```php
class YamlFormatter implements FormatterInterface
{
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
$dumper = new Dumper();
$output->writeln($dumper->dump($data));
}
}
```
The formatter is passed the set of `$options` that the user provided on the command line. These may optionally be examined to alter the behavior of the formatter, if needed.
Formatters may also implement different interfaces to alter the behavior of the rendering engine.
- `ValidationInterface`: A formatter should implement this interface to test to see if the provided data type can be processed. Any formatter that does **not** implement this interface is presumed to operate exclusively on php arrays. The formatter manager will always convert any provided data into an array before passing it to a formatter that does not implement ValidationInterface. These formatters will not be made available when the returned data type cannot be converted into an array.
- `OverrideRestructureInterface`: A formatter that implements this interface will be given the option to act on the provided structured data object before it restructures itself. See the section below on structured data for details on data restructuring.
## Structured Data
Most formatters will operate on any array or ArrayObject data. Some formatters require that specific data types be used. The following data types, all of which are subclasses of ArrayObject, are available for use:
- `RowsOfFields`: Each row contains an associative array of field:value pairs. It is also assumed that the fields of each row are the same for every row. This format is ideal for displaying in a table, with labels in the top row.
- `PropertyList`: Each row contains a field:value pair. Each field is unique. This format is ideal for displaying in a table, with labels in the first column and values in the second common.
- `DOMDocument`: The standard PHP DOM document class may be used by functions that need to be able to presicely specify the exact attributes and children when the XML output format is used.
Commands that return table structured data with fields can be filtered and/or re-ordered by using the --fields option. These structured data types can also be formatted into a more generic type such as yaml or json, even after being filtered. This capabilities are not available if the data is returned in a bare php array.
The formatter manager will do its best to convert from an array to a DOMDocument, or from a DOMDocument to an array. It is important to note that a DOMDocument does not have a 1-to-1 mapping with a PHP array. DOM elements contain both attributes and elements; a simple string property 'foo' may be represented either as or value. Also, there may be multiple XML elements with the same name, whereas php associative arrays must always have unique keys. When converting from an array to a DOM document, the XML formatter will default to representing the string properties of an array as attributes of the element. Sets of elements with the same name may be used only if they are wrapped in a containing parent element--e.g. onetwo. The XMLSchema class may be used to provide control over whether a property is rendered as an attribute or an element; however, in instances where the schema of the XML output is important, it is best for a function to return its result as a DOMDocument rather than an array.
A function may also define its own structured data type to return, usually by extending one of the types mentioned above. If a custom structured data class implements an appropriate interface, then it can provide its own conversion function to one of the other data types:
- `DomDataInterface`: The data object may produce a DOMDocument via its `getDomData()` method, which will be called in any instance where a DOM document is needed--typically with the xml formatter.
- `ListDataInterface`: Any structured data object that implements this interface may use the `getListData()` method to produce the data set that will be used with the list formatter.
- `TableDataInterface`: Any structured data object that implements this interface may use the `getTableData()` method to produce the data set that will be used with the table formatter.
- `RenderCellInterface`: Structured data can also provide fine-grain control over how each cell in a table is rendered by implementing the RenderCellInterface. See the section below for information on how this is done.
- `RestructureInterface`: The restructure interface can be implemented by a structured data object to restructure the data in response to options provided by the user. For example, the RowsOfFields and PropertyList data types use this interface to select and reorder the fields that were selected to appear in the output. Custom data types usually will not need to implement this interface, as they can inherit this behavior by extending RowsOfFields or PropertyList.
Additionally, structured data may be simplified to arrays via an array simplification object. To provide an array simplifier, implement `SimplifyToArrayInterface`, and register the simplifier via `FormatterManager::addSimplifier()`.
## Rendering Table Cells
By default, both the RowsOfFields and PropertyList data types presume that the contents of each cell is a simple string. To render more complicated cell contents, create a custom structured data class by extending either RowsOfFields or PropertyList, as desired, and implement RenderCellInterface. The `renderCell()` method of your class will then be called for each cell, and you may act on it as appropriate.
```php
public function renderCell($key, $cellData, FormatterOptions $options, $rowData)
{
// 'my-field' is always an array; convert it to a comma-separated list.
if ($key == 'my-field') {
return implode(',', $cellData);
}
// MyStructuredCellType has its own render function
if ($cellData instanceof MyStructuredCellType) {
return $cellData->myRenderfunction();
}
// If we do not recognize the cell data, return it unchnaged.
return $cellData;
}
```
Note that if your data structure is printed with a formatter other than one such as the table formatter, it will still be reordered per the selected fields, but cell rendering will **not** be done.
## API Usage
It is recommended to use [Consolidation/AnnotationCommand](https://github.com/consolidation/annotation-command) to manage commands and formatters. See the [AnnotationCommand API Usage](https://github.com/consolidation/annotation-command#api-usage) for details.
The FormatterManager may also be used directly, if desired:
```php
/**
* @param OutputInterface $output Output stream to write to
* @param string $format Data format to output in
* @param mixed $structuredOutput Data to output
* @param FormatterOptions $options Configuration informatin and User options
*/
function doFormat(
OutputInterface $output,
string $format,
array $data,
FormatterOptions $options)
{
$formatterManager = new FormatterManager();
$formatterManager->write(output, $format, $data, $options);
}
```
The FormatterOptions class is used to hold the configuration for the command output--things such as the default field list for tabular output, and so on--and also the current user-selected options to use during rendering, which may be provided using a Symfony InputInterface object:
```
public function execute(InputInterface $input, OutputInterface $output)
{
$options = new FormatterOptions();
$options
->setInput($input)
->setFieldLabels(['id' => 'ID', 'one' => 'First', 'two' => 'Second'])
->setDefaultStringField('id');
$data = new RowsOfFields($this->getSomeData($input));
return $this->doFormat($output, $options->getFormat(), $data, $options);
}
```
## Comparison to Existing Solutions
Formatters have been in use in Drush since version 5. Drush allows formatters to be defined using simple classes, some of which may be configured using metadata. Furthermore, nested formatters are also allowed; for example, a list formatter may be given another formatter to use to format each of its rows. Nested formatters also require nested metadata, causing the code that constructed formatters to become very complicated and unweildy.
Consolidation/OutputFormatters maintains the simplicity of use provided by Drush formatters, but abandons nested metadata configuration in favor of using code in the formatter to configure itself, in order to keep the code simpler.
PK ?V9 9 ( src/Exception/InvalidFormatException.phpnu W+A getName() == 'ArrayObject')) {
return 'an array';
}
return 'an instance of ' . $data->getName();
}
if (is_string($data)) {
return 'a string';
}
if (is_object($data)) {
return 'an instance of ' . get_class($data);
}
throw new \Exception("Undescribable data error: " . var_export($data, true));
}
protected static function describeAllowedTypes($allowedTypes)
{
if (is_array($allowedTypes) && !empty($allowedTypes)) {
if (count($allowedTypes) > 1) {
return static::describeListOfAllowedTypes($allowedTypes);
}
$allowedTypes = $allowedTypes[0];
}
return static::describeDataType($allowedTypes);
}
protected static function describeListOfAllowedTypes($allowedTypes)
{
$descriptions = [];
foreach ($allowedTypes as $oneAllowedType) {
$descriptions[] = static::describeDataType($oneAllowedType);
}
if (count($descriptions) == 2) {
return "either {$descriptions[0]} or {$descriptions[1]}";
}
$lastDescription = array_pop($descriptions);
$otherDescriptions = implode(', ', $descriptions);
return "one of $otherDescriptions or $lastDescription";
}
}
PK ?VfӖV V ( src/Exception/UnknownFormatException.phpnu W+A getSelectedFieldKeys($fields, $fieldLabels);
if (empty($fields)) {
return array_intersect_key($fieldLabels, $firstRow);
}
return $this->reorderFieldLabels($fields, $fieldLabels, $data);
}
protected function reorderFieldLabels($fields, $fieldLabels, $data)
{
$result = [];
$firstRow = reset($data);
foreach ($fields as $field) {
if (array_key_exists($field, $firstRow)) {
if (array_key_exists($field, $fieldLabels)) {
$result[$field] = $fieldLabels[$field];
}
}
}
return $result;
}
protected function getSelectedFieldKeys($fields, $fieldLabels)
{
if (is_string($fields)) {
$fields = explode(',', $fields);
}
$selectedFields = [];
foreach ($fields as $field) {
$matchedFields = $this->matchFieldInLabelMap($field, $fieldLabels);
if (empty($matchedFields)) {
throw new UnknownFieldException($field);
}
$selectedFields = array_merge($selectedFields, $matchedFields);
}
return $selectedFields;
}
protected function matchFieldInLabelMap($field, $fieldLabels)
{
$fieldRegex = $this->convertToRegex($field);
return
array_filter(
array_keys($fieldLabels),
function ($key) use ($fieldRegex, $fieldLabels) {
$value = $fieldLabels[$key];
return preg_match($fieldRegex, $value) || preg_match($fieldRegex, $key);
}
);
}
/**
* Convert the provided string into a regex suitable for use in
* preg_match.
*
* Matching occurs in the same way as the Symfony Finder component:
* http://symfony.com/doc/current/components/finder.html#file-name
*/
protected function convertToRegex($str)
{
return $this->isRegex($str) ? $str : Glob::toRegex($str);
}
/**
* Checks whether the string is a regex. This function is copied from
* MultiplePcreFilterIterator in the Symfony Finder component.
*
* @param string $str
*
* @return bool Whether the given string is a regex
*/
protected function isRegex($str)
{
if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) {
$start = substr($m[1], 0, 1);
$end = substr($m[1], -1);
if ($start === $end) {
return !preg_match('/[*?[:alnum:] \\\\]/', $start);
}
foreach (array(array('{', '}'), array('(', ')'), array('[', ']'), array('<', '>')) as $delimiters) {
if ($start === $delimiters[0] && $end === $delimiters[1]) {
return true;
}
}
}
return false;
}
}
PK ?Vc 7 src/Transformations/PropertyListTableTransformation.phpnu W+A getArrayCopy();
return $data[0];
}
}
PK ?V٣s &