Skip to content

Commit 7c31597

Browse files
committed
add JSON formatter
1 parent 3007b7f commit 7c31597

15 files changed

+568
-124
lines changed

README.md

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ Generates packages changes report in Markdown format by comparing `composer.lock
1414

1515
## Example output
1616

17-
| Prod Packages | Action | Base | Target |
18-
|------------------------------------|----------|---------|---------|
19-
| psr/event-dispatcher | New | - | 1.0.0 |
20-
| symfony/deprecation-contracts | New | - | v2.1.2 |
21-
| symfony/event-dispatcher | Upgraded | v2.8.52 | v5.1.2 |
22-
| symfony/event-dispatcher-contracts | New | - | v2.1.2 |
23-
| symfony/polyfill-php80 | New | - | v1.17.1 |
24-
| php | New | - | >=5.3 |
25-
26-
| Dev Packages | Action | Base | Target |
17+
| Prod Packages | Operation | Base | Target |
18+
|------------------------------------|-----------|---------|---------|
19+
| psr/event-dispatcher | New | - | 1.0.0 |
20+
| symfony/deprecation-contracts | New | - | v2.1.2 |
21+
| symfony/event-dispatcher | Upgraded | v2.8.52 | v5.1.2 |
22+
| symfony/event-dispatcher-contracts | New | - | v2.1.2 |
23+
| symfony/polyfill-php80 | New | - | v1.17.1 |
24+
| php | New | - | >=5.3 |
25+
26+
| Dev Packages | Operation | Base | Target |
2727
|------------------------------------|------------|-------|--------|
2828
| phpunit/php-code-coverage | Downgraded | 8.0.2 | 7.0.10 |
2929
| phpunit/php-file-iterator | Downgraded | 3.0.2 | 2.0.2 |
@@ -66,7 +66,7 @@ composer diff # Displays packages changed in current git tree compared with HEAD
6666
- `--no-prod` - ignore prod dependencies (`require`)
6767
- `--with-platform` (`-p`) - include platform dependencies (PHP, extensions, etc.)
6868
- `--with-links` (`-l`) - include compare/release URLs
69-
- `--format` (`-f`) - output format (mdtable, mdlist) - default: `mdtable`
69+
- `--format` (`-f`) - output format (mdtable, mdlist, json) - default: `mdtable`
7070
- `--gitlab-domains` - custom gitlab domains for compare/release URLs - default: use composer config
7171

7272
## Advanced usage
@@ -75,7 +75,24 @@ composer diff # Displays packages changed in current git tree compared with HEAD
7575
composer diff -b master:composer.lock -t develop:composer.lock -p # Compare master and develop branches, including platform dependencies
7676
composer diff --no-dev # ignore dev dependencies
7777
composer diff -p # include platform dependencies
78-
composer diff -f mdlist # Output as Markdown list instead of table
78+
composer diff -f json # Output as JSON instead of table
7979
```
8080

81+
# Similar packages
82+
83+
While there are several existing packages offering similar functionality:
84+
85+
- [jbzoo/composer-diff](https://packagist.org/packages/jbzoo/composer-diff) - requires PHP 7.2+, no composer plugin support
86+
- [josefglatz/composer-diff-plugin](https://packagist.org/packages/josefglatz/composer-diff-plugin) - works only right after install/update
87+
- [davidrjonas/composer-lock-diff](https://packagist.org/packages/davidrjonas/composer-lock-diff) - does not work as composer plugin
88+
89+
This package offers:
90+
91+
- Support for wide range of PHP versions, starting from 5.3.2 up to 8.0 and newer.
92+
- No dependencies if you run it as composer plugin.
93+
- Both standalone executable and composer plugin interface - you choose how you want to use it.
94+
- Allows generating reports in several formats.
95+
- Extra Gitlab domains support.
96+
- 100% test coverage.
97+
- MIT license.
8198

infection.json.dist

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
}
1212
},
1313
"mutators": {
14-
"@default": true
14+
"@default": true,
15+
"IncrementInteger": {
16+
"ignore": [
17+
"IonBazan\\ComposerDiff\\Formatter\\JsonFormatter::format"
18+
]
19+
}
1520
}
1621
}

preview.png

-4.34 KB
Loading

src/Command/DiffCommand.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Composer\Command\BaseCommand;
66
use IonBazan\ComposerDiff\Formatter\Formatter;
7+
use IonBazan\ComposerDiff\Formatter\JsonFormatter;
78
use IonBazan\ComposerDiff\Formatter\MarkdownListFormatter;
89
use IonBazan\ComposerDiff\Formatter\MarkdownTableFormatter;
910
use IonBazan\ComposerDiff\PackageDiff;
@@ -48,7 +49,7 @@ protected function configure()
4849
->addOption('no-prod', null, InputOption::VALUE_NONE, 'Ignore prod dependencies')
4950
->addOption('with-platform', 'p', InputOption::VALUE_NONE, 'Include platform dependencies (PHP version, extensions, etc.)')
5051
->addOption('with-links', 'l', InputOption::VALUE_NONE, 'Include compare/release URLs')
51-
->addOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format (mdtable, mdlist)', 'mdtable')
52+
->addOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format (mdtable, mdlist, json)', 'mdtable')
5253
->addOption('gitlab-domains', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Gitlab domains', array())
5354
;
5455
}
@@ -66,16 +67,19 @@ protected function execute(InputInterface $input, OutputInterface $output)
6667

6768
$formatter = $this->getFormatter($input, $output);
6869

70+
$prodOperations = array();
71+
$devOperations = array();
72+
6973
if (!$input->getOption('no-prod')) {
70-
$operations = $this->packageDiff->getPackageDiff($base, $target, false, $withPlatform);
71-
$formatter->render($operations, 'Prod Packages', $withUrls);
74+
$prodOperations = $this->packageDiff->getPackageDiff($base, $target, false, $withPlatform);
7275
}
7376

7477
if (!$input->getOption('no-dev')) {
75-
$operations = $this->packageDiff->getPackageDiff($base, $target, true, $withPlatform);
76-
$formatter->render($operations, 'Dev Packages', $withUrls);
78+
$devOperations = $this->packageDiff->getPackageDiff($base, $target, true, $withPlatform);
7779
}
7880

81+
$formatter->render($prodOperations, $devOperations, $withUrls);
82+
7983
return 0;
8084
}
8185

@@ -87,6 +91,8 @@ private function getFormatter(InputInterface $input, OutputInterface $output)
8791
$urlGenerators = new GeneratorContainer($this->gitlabDomains);
8892

8993
switch ($input->getOption('format')) {
94+
case 'json':
95+
return new JsonFormatter($output, $urlGenerators);
9096
case 'mdlist':
9197
return new MarkdownListFormatter($output, $urlGenerators);
9298
// case 'mdtable':

src/Formatter/AbstractFormatter.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,6 @@ private function getReleaseUrl(PackageInterface $package)
7474
return $generator->getReleaseUrl($package);
7575
}
7676

77-
/**
78-
* @param string $url
79-
* @param string $title
80-
*
81-
* @return string
82-
*/
83-
abstract protected function formatUrl($url, $title);
84-
8577
/**
8678
* @return bool
8779
*/

src/Formatter/Formatter.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,23 @@
66

77
interface Formatter
88
{
9+
/**
10+
* @param OperationInterface[] $prodOperations
11+
* @param OperationInterface[] $devOperations
12+
* @param bool $withUrls
13+
*
14+
* @return void
15+
*/
16+
public function render(array $prodOperations, array $devOperations, $withUrls);
17+
918
/**
1019
* @param OperationInterface[] $operations
1120
* @param string $title
1221
* @param bool $withUrls
1322
*
1423
* @return void
1524
*/
16-
public function render(array $operations, $title, $withUrls);
25+
public function renderSingle(array $operations, $title, $withUrls);
1726

1827
/**
1928
* @return string|null

src/Formatter/JsonFormatter.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
namespace IonBazan\ComposerDiff\Formatter;
4+
5+
use Composer\DependencyResolver\Operation\InstallOperation;
6+
use Composer\DependencyResolver\Operation\OperationInterface;
7+
use Composer\DependencyResolver\Operation\UninstallOperation;
8+
use Composer\DependencyResolver\Operation\UpdateOperation;
9+
10+
class JsonFormatter extends AbstractFormatter
11+
{
12+
/**
13+
* {@inheritdoc}
14+
*/
15+
public function render(array $prodOperations, array $devOperations, $withUrls)
16+
{
17+
$this->format(array(
18+
'packages' => $this->transformOperations($prodOperations, $withUrls),
19+
'packages-dev' => $this->transformOperations($devOperations, $withUrls),
20+
));
21+
}
22+
23+
/**
24+
* {@inheritdoc}
25+
*/
26+
public function renderSingle(array $operations, $title, $withUrls)
27+
{
28+
$this->format($this->transformOperations($operations, $withUrls));
29+
}
30+
31+
/**
32+
* @param array<string, array<string, string|null>>|array<string, array<array<string, string|null>>> $data
33+
*
34+
* @return void
35+
*/
36+
private function format(array $data)
37+
{
38+
$this->output->writeln(json_encode($data, 128)); // JSON_PRETTY_PRINT
39+
}
40+
41+
/**
42+
* @param OperationInterface[] $operations
43+
* @param bool $withUrls
44+
*
45+
* @return array<array<string, string|null>>
46+
*/
47+
private function transformOperations(array $operations, $withUrls)
48+
{
49+
$rows = array();
50+
51+
foreach ($operations as $operation) {
52+
$row = $this->transformOperation($operation);
53+
54+
if ($withUrls) {
55+
$row['compare'] = $this->getUrl($operation);
56+
}
57+
58+
$rows[$row['name']] = $row;
59+
}
60+
61+
return $rows;
62+
}
63+
64+
/**
65+
* @return array<string, string|null>
66+
*/
67+
private function transformOperation(OperationInterface $operation)
68+
{
69+
if ($operation instanceof InstallOperation) {
70+
return array(
71+
'name' => $operation->getPackage()->getName(),
72+
'operation' => 'install',
73+
'version_base' => null,
74+
'version_target' => $operation->getPackage()->getFullPrettyVersion(),
75+
);
76+
}
77+
78+
if ($operation instanceof UpdateOperation) {
79+
return array(
80+
'name' => $operation->getInitialPackage()->getName(),
81+
'operation' => self::isUpgrade($operation) ? 'upgrade' : 'downgrade',
82+
'version_base' => $operation->getInitialPackage()->getFullPrettyVersion(),
83+
'version_target' => $operation->getTargetPackage()->getFullPrettyVersion(),
84+
);
85+
}
86+
87+
if ($operation instanceof UninstallOperation) {
88+
return array(
89+
'name' => $operation->getPackage()->getName(),
90+
'operation' => 'remove',
91+
'version_base' => $operation->getPackage()->getFullPrettyVersion(),
92+
'version_target' => null,
93+
);
94+
}
95+
96+
throw new \InvalidArgumentException('Invalid operation');
97+
}
98+
}

src/Formatter/MarkdownListFormatter.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,16 @@ class MarkdownListFormatter extends MarkdownFormatter
1212
/**
1313
* {@inheritdoc}
1414
*/
15-
public function render(array $operations, $title, $withUrls)
15+
public function render(array $prodOperations, array $devOperations, $withUrls)
16+
{
17+
$this->renderSingle($prodOperations, 'Prod Packages', $withUrls);
18+
$this->renderSingle($devOperations, 'Dev Packages', $withUrls);
19+
}
20+
21+
/**
22+
* {@inheritdoc}
23+
*/
24+
public function renderSingle(array $operations, $title, $withUrls)
1625
{
1726
if (!\count($operations)) {
1827
return;

src/Formatter/MarkdownTableFormatter.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,16 @@ class MarkdownTableFormatter extends MarkdownFormatter
1313
/**
1414
* {@inheritdoc}
1515
*/
16-
public function render(array $operations, $title, $withUrls)
16+
public function render(array $prodOperations, array $devOperations, $withUrls)
17+
{
18+
$this->renderSingle($prodOperations, 'Prod Packages', $withUrls);
19+
$this->renderSingle($devOperations, 'Dev Packages', $withUrls);
20+
}
21+
22+
/**
23+
* {@inheritdoc}
24+
*/
25+
public function renderSingle(array $operations, $title, $withUrls)
1726
{
1827
if (!\count($operations)) {
1928
return;
@@ -32,7 +41,7 @@ public function render(array $operations, $title, $withUrls)
3241
}
3342

3443
$table = new Table($this->output);
35-
$headers = array($title, 'Action', 'Base', 'Target');
44+
$headers = array($title, 'Operation', 'Base', 'Target');
3645

3746
if ($withUrls) {
3847
$headers[] = 'Link';

0 commit comments

Comments
 (0)