PK 1?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 1?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 1?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 1?V4so9 9 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-org/annotation-command/)
It is easy to run the unit tests and code sniffer locally; simply ensure that `./vendor/bin` is in your `$PATH`, cd to the root of the project directory, and run `phpcs` and `phpunit` as shown above. To automatically fix coding standard errors, run:
- phpcbf --standard=PSR2 src
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 needs 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 1?V~ .travis.ymlnu W+A language: php
branches:
# Only test the master branch and SemVer tags.
only:
- master
- /^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+.*$/
php:
- 5.4
- 5.5
- 5.6
- 7.0
sudo: false
cache:
directories:
- vendor
- $HOME/.composer/cache
before_script:
- composer install
script:
- vendor/bin/phpcs --standard=PSR2 -n src
- vendor/bin/phpunit
after_success:
- travis_retry php vendor/bin/coveralls -v
PK 1?V`>R R
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"
},
"require-dev": {
"phpunit/phpunit": "4.*",
"satooshi/php-coveralls": "^1.0",
"squizlabs/php_codesniffer": "2.*"
},
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
}
}
PK 1?VW6 CHANGELOG.mdnu W+A # Change Log
### 1.1.0 - 14 September 2016
Add tab-separated-value (tsv) formatter.
### 1.0.0 - 19 May 2016
First stable release.
PK 1?V~i tests/testFormatterOptions.phpnu W+A bind($definition);
return $input;
}
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', $options->getFormat());
$this->assertEquals('table', $options->getFormat($defaults));
// Override has higher priority than configuration and defaults
$options = new FormatterOptions($configurationData, $userOptions);
$newOptions = $options->override([FormatterOptions::DEFAULT_FORMAT => 'json']);
$this->assertEquals('json', $newOptions->getFormat());
$this->assertEquals('json', $newOptions->getFormat($defaults));
$options = new FormatterOptions($configurationData, $userOptions);
$options->setConfigurationDefault(FormatterOptions::DEFAULT_FORMAT, 'php');
$this->assertEquals('table', $options->getFormat());
$options = new FormatterOptions($configurationData, $userOptions);
$options->setConfigurationData([]);
$this->assertEquals('', $options->getFormat());
// 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 1?VD~v ~v 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\AssociativeList 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 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]);
}
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\IncompatibleDataException
* @expectedExceptionCode 1
* @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\AssociativeList. Instead, an array was provided.
*/
function testIncompatibleDataForTableFormatter()
{
$data = $this->simpleTableExampleData()->getArrayCopy();
$this->assertFormattedOutputMatches('Should throw an exception before comparing the table data', 'table', $data);
}
/**
* @expectedException \Consolidation\OutputFormatters\Exception\IncompatibleDataException
* @expectedExceptionCode 1
* @expectedExceptionMessage Data provided to Consolidation\OutputFormatters\Formatters\SectionsFormatter must be an instance of Consolidation\OutputFormatters\StructuredData\RowsOfFields. Instead, an array was provided.
*/
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']);
$expectedSections = <<assertFormattedOutputMatches($expectedSections, 'sections', $data, $configurationData);
$expectedJson = <<assertFormattedOutputMatches($expectedJson, 'json', $data, $configurationData, ['fields' => ['San', 'Ichi']]);
}
protected function simpleListExampleData()
{
$data = [
'one' => 'apple',
'two' => 'banana',
'three' => 'carrot',
];
return new AssociativeList($data);
}
/**
* @expectedException \Consolidation\OutputFormatters\Exception\IncompatibleDataException
* @expectedExceptionCode 1
* @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\AssociativeList. Instead, an array was provided.
*/
function testIncompatibleListDataForTableFormatter()
{
$data = $this->simpleListExampleData();
$this->assertFormattedOutputMatches('Should throw an exception before comparing the table data', 'table', $data->getArrayCopy());
}
function testSimpleList()
{
$data = $this->simpleListExampleData();
$expected = <<assertFormattedOutputMatches($expected, 'table', $data);
$expectedRotated = <<assertFormattedOutputMatches($expectedRotated, 'table', $data, new FormatterOptions(['list-orientation' => false]));
$expectedList = <<< EOT
apple
banana
carrot
EOT;
$this->assertFormattedOutputMatches($expectedList, 'list', $data);
$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 associativeListWithCsvCells()
{
$data = [
'one' => 'apple',
'two' => ['banana', 'plantain'],
'three' => 'carrot',
'four' => ['peaches', 'pumpkin pie'],
];
return new AssociativeListWithCsvCells($data);
}
function testAssociativeListWithCsvCells()
{
$data = $this->associativeListWithCsvCells();
$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]);
}
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 1?Vhn
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\AssociativeList. 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\AssociativeList. 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\AssociativeList. 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\AssociativeList. Instead, an instance of Consolidation\OutputFormatters\StructuredData\AssociativeList was provided.', $tableFormatter, new AssociativeList([]));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Undescribable data error: NULL
*/
public function testUndescribableData()
{
$tableFormatter = $this->formatterManager->getFormatter('table');
$this->assertIncompatibleDataMessage('Will throw before comparing.', $tableFormatter, null);
}
}
PK 1?Va1 * tests/src/RowsOfFieldsWithAlternatives.phpnu W+A formatterManager = new FormatterManager();
}
function testValidFormats()
{
$arrayObjectRef = new \ReflectionClass('\ArrayObject');
$associativeListRef = new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\AssociativeList');
$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,tsv,var_export,xml,yaml', implode(',', $validFormats));
// Check to see which formats can handle an AssociativeList
$validFormats = $this->formatterManager->validFormats($associativeListRef);
$this->assertEquals('csv,json,list,php,print-r,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,table,tsv,var_export,xml,yaml', implode(',', $validFormats));
// Test error case: no formatter should handle something that is not a data type.
$validFormats = $this->formatterManager->validFormats($notADataType);
$this->assertEquals('', implode(',', $validFormats));
}
}
PK 1?Vxm@ @
.gitignorenu W+A .DS_Store
.idea
phpunit.xml
build
vendor
composer.lock
main.php
PK 1?Vx$ $ 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).
## 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.
## Example Formatter
Simple formatters are very easy to write.
```
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.
- `AssociativeList`: 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 AssociativeList 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 AssociativeList.
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 AssociativeList 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 AssociativeList, 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.
```
public function renderCell($key, $cellData, FormatterOptions $options)
{
// '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 some formatter other than 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:
```
/**
* @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);
}
```
## 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 1?Vu!1A A src/FormatterOptions.phpnu W+A configurationData = $configurationData;
$this->options = $options;
}
public function override($configurationData)
{
$override = new self();
$override
->setConfigurationData($configurationData + $this->getConfigurationData())
->setOptions($this->getOptions());
return $override;
}
public function get($key, $defaults = [], $default = false)
{
$value = $this->fetch($key, $defaults, $default);
return $this->parse($key, $value);
}
public function getXmlSchema()
{
return new XmlSchema();
}
public function getFormat($defaults = [])
{
return $this->get(self::FORMAT, $defaults, $this->get(self::DEFAULT_FORMAT, $defaults, ''));
}
protected function fetch($key, $defaults = [], $default = false)
{
$values = array_merge(
$defaults,
$this->getConfigurationData(),
$this->getOptions(),
$this->getInputOptions($defaults)
);
if (array_key_exists($key, $values)) {
return $values[$key];
}
return $default;
}
protected function parse($key, $value)
{
$optionFormat = $this->getOptionFormat($key);
if ($optionFormat) {
return $this->$optionFormat($value);
}
return $value;
}
public function parsePropertyList($value)
{
return PropertyParser::parse($value);
}
protected function getOptionFormat($key)
{
$propertyFormats = [
self::ROW_LABELS => 'PropertyList',
self::FIELD_LABELS => 'PropertyList',
self::DEFAULT_FIELDS => 'PropertyList',
];
if (array_key_exists($key, $propertyFormats)) {
return "parse{$propertyFormats[$key]}";
}
return false;
}
public function setConfigurationData($configurationData)
{
$this->configurationData = $configurationData;
return $this;
}
protected function setConfigurationValue($key, $value)
{
$this->configurationData[$key] = $value;
return $this;
}
public function setConfigurationDefault($key, $value)
{
if (!array_key_exists($key, $this->configurationData)) {
return $this->setConfigurationValue($key, $value);
}
return $this;
}
public function getConfigurationData()
{
return $this->configurationData;
}
public function setOptions($options)
{
$this->options = $options;
return $this;
}
public function setOption($key, $value)
{
$this->options[$key] = $value;
return $this;
}
public function getOptions()
{
return $this->options;
}
public function setInput(InputInterface $input)
{
$this->input = $input;
}
public function getInputOptions($defaults)
{
if (!isset($this->input)) {
return [];
}
$options = [];
foreach ($defaults as $key => $value) {
if ($this->input->hasOption($key)) {
$result = $this->input->getOption($key);
if (isset($result)) {
$options[$key] = $this->input->getOption($key);
}
}
}
return $options;
}
}
PK 1?VfӖV V ( src/Exception/UnknownFormatException.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 1?V
yu u % src/Transformations/ReorderFields.phpnu W+A getSelectedFieldKeys($fields, $fieldLabels);
if (empty($fields)) {
return $fieldLabels;
}
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);
}
$fieldLablesReverseMap = array_combine(array_values($fieldLabels), array_keys($fieldLabels));
$selectedFields = [];
foreach ($fields as $field) {
if (array_key_exists($field, $fieldLabels)) {
$selectedFields[] = $field;
} elseif (array_key_exists($field, $fieldLablesReverseMap)) {
$selectedFields[] = $fieldLablesReverseMap[$field];
}
}
return $selectedFields;
}
}
PK 1?V٣s &