├── .docksal
├── docksal.yml
└── docksal.env
├── .gitignore
├── phpspec.yml.dist
├── src
├── Patch
│ ├── LocalPatch.php
│ ├── Exception
│ │ └── NoIssueFoundException.php
│ ├── PatchInterface.php
│ ├── HasIssueUriInterface.php
│ ├── GithubPatch.php
│ ├── DrupalOrgPatch.php
│ ├── Factory.php
│ └── PatchBase.php
├── Formatter
│ ├── Json.php
│ ├── FormatterInterface.php
│ ├── Csv.php
│ └── Factory.php
├── Parser
│ ├── ParserInterface.php
│ ├── Factory.php
│ ├── ComposerJson.php
│ ├── DrushMake.php
│ └── Drush
│ │ └── DrushIniParser.php
├── Application.php
├── Analyze
│ └── Patches.php
└── AnalyzeCommand.php
├── .travis.yml
├── phpcs-ruleset.xml
├── spec
├── ApplicationSpec.php
├── Formatter
│ ├── JsonSpec.php
│ └── FactorySpec.php
├── AnalyzeCommandSpec.php
├── Parser
│ ├── ComposerJsonSpec.php
│ ├── DrushMakeSpec.php
│ └── FactorySpec.php
├── Patch
│ ├── LocalPatchSpec.php
│ ├── FactorySpec.php
│ ├── DrupalOrgPatchSpec.php
│ └── GithubPatchSpec.php
└── Analyze
│ └── PatchesSpec.php
├── phpunit.xml.dist
├── README.md
├── bin
└── composer-analyze
├── tests
├── AnalyzeCommandTest.php
├── GeneratePatchesTrait.php
├── Patch
│ └── GithubPatchTest.php
└── Analyze
│ └── PatchesTest.php
└── composer.json
/.docksal/docksal.yml:
--------------------------------------------------------------------------------
1 | version: "2.1"
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /reports
2 | /composer.lock
3 | /vendor
4 | .phpunit.result.cache
5 |
--------------------------------------------------------------------------------
/phpspec.yml.dist:
--------------------------------------------------------------------------------
1 | suites:
2 | default:
3 | namespace: Phase2\ComposerAnalytics
4 | psr4_prefix: Phase2\ComposerAnalytics
5 |
--------------------------------------------------------------------------------
/src/Patch/LocalPatch.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | PHP CodeSniffer configuration Composer Analytics.
4 | ./src
5 |
6 |
7 | ./src/Parser/Drush/*
8 |
9 |
--------------------------------------------------------------------------------
/spec/ApplicationSpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType(Application::class);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/spec/Formatter/JsonSpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType(Json::class);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/spec/AnalyzeCommandSpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType(AnalyzeCommand::class);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Formatter/FormatterInterface.php:
--------------------------------------------------------------------------------
1 | insertAll($analyzed);
19 | return (string) $csv;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Formatter/Factory.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ./tests/
7 |
8 |
9 |
10 |
11 |
12 | src/
13 |
14 |
15 | vendor/
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/spec/Parser/ComposerJsonSpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType(ComposerJson::class);
14 | }
15 |
16 | function it_has_a_pattern()
17 | {
18 | $this->getPattern()->shouldReturn('composer.json');
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/spec/Parser/DrushMakeSpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType(DrushMake::class);
14 | }
15 |
16 | function it_has_a_pattern()
17 | {
18 | $this->getPattern()->shouldReturn('/.+.make(\.(yaml|yml))?$/');
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Parser/ParserInterface.php:
--------------------------------------------------------------------------------
1 | beConstructedWith('project', 'src/patches/hackzor.patch', 'description');
14 | }
15 |
16 | function it_is_initializable()
17 | {
18 | $this->shouldHaveType(LocalPatch::class);
19 | }
20 |
21 | function it_gets_the_project()
22 | {
23 | $this->getProject()->shouldReturn('project');
24 | }
25 |
26 | function it_gets_the_description()
27 | {
28 | $this->getDescription()->shouldReturn('description');
29 | }
30 |
31 | function it_gets_the_raw_uri()
32 | {
33 | $this->getPatchUri()->shouldReturn('src/patches/hackzor.patch');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/bin/composer-analyze:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | run();
27 |
28 | }, '1.0.0');
29 |
--------------------------------------------------------------------------------
/src/Patch/GithubPatch.php:
--------------------------------------------------------------------------------
1 | rawUri, $matches)) {
28 | return sprintf(static::URL_TEMPLATE, $matches[4]);
29 | }
30 |
31 | throw new NoIssueFoundException(sprintf('No issue URI could be extracted from the patch: %s.', $this->rawUri));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Parser/ComposerJson.php:
--------------------------------------------------------------------------------
1 | extra->patches)) {
18 | foreach ($contents->extra->patches as $project => $patches) {
19 | foreach ($patches as $description => $uri) {
20 | // @todo Use a factory to determine patch type.
21 | $found_patches[] = PatchFactory::getPatch($project, $uri, $description);
22 | }
23 | }
24 | }
25 |
26 | return $found_patches;
27 | }
28 |
29 | /**
30 | * {@inheritdoc}
31 | */
32 | public function getPattern()
33 | {
34 | return 'composer.json';
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/spec/Formatter/FactorySpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType(Factory::class);
17 | }
18 |
19 | function it_gets_formatters()
20 | {
21 | $this->get('csv')->shouldImplement(FormatterInterface::class);
22 | $this->get('csv')->shouldHaveType(Csv::class);
23 | $this->get('json')->shouldImplement(FormatterInterface::class);
24 | $this->get('json')->shouldHaveType(Json::class);
25 | }
26 |
27 | function it_throws_invalid_types()
28 | {
29 | $this->shouldThrow(\LogicException::class)->during('get', ['bad_format']);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/spec/Parser/FactorySpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType(Factory::class);
17 | }
18 |
19 | function it_returns_a_parser()
20 | {
21 | $this->get('composer')->shouldImplement(ParserInterface::class);
22 | $this->get('composer')->shouldHaveType(ComposerJson::class);
23 | $this->get('make')->shouldImplement(ParserInterface::class);
24 | $this->get('make')->shouldHaveType(DrushMake::class);
25 | }
26 |
27 | function it_throws_invalid_types()
28 | {
29 | $this->shouldThrow(\LogicException::class)->during('get', ['bad']);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Application.php:
--------------------------------------------------------------------------------
1 | setArguments();
37 | return $input;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Patch/DrupalOrgPatch.php:
--------------------------------------------------------------------------------
1 | rawUri, $matches)) {
28 | $issue_number = $matches[1];
29 | return sprintf(static::URL_TEMPLATE, $issue_number);
30 | }
31 |
32 | throw new NoIssueFoundException(sprintf('No issue URI could be extracted from the patch: %s.', $this->rawUri));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Patch/Factory.php:
--------------------------------------------------------------------------------
1 | shouldHaveType(Factory::class);
17 | }
18 |
19 | function it_gets_local_patches()
20 | {
21 | $this->getPatch('project', 'src/patches/foo.patch', 'description')->shouldHaveType(LocalPatch::class);
22 | }
23 |
24 | function it_gets_drupalorg_patches()
25 | {
26 | $this->getPatch('drupal/core', 'https://www.drupal.org/foo.patch', 'description')->shouldHaveType(DrupalOrgPatch::class);
27 | }
28 |
29 | function it_gets_github_patches()
30 | {
31 | $this->getPatch('drupal/message', 'https://github.com/foo/message/pulls/123.diff', 'description')->shouldHaveType(GithubPatch::class);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/spec/Patch/DrupalOrgPatchSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith('project', 'http://www.drupal.org/files/foo-12345-02.patch', 'description');
14 | }
15 |
16 | function it_is_initializable()
17 | {
18 | $this->shouldHaveType(DrupalOrgPatch::class);
19 | }
20 |
21 | function it_gets_the_project()
22 | {
23 | $this->getProject()->shouldReturn('project');
24 | }
25 |
26 | function it_gets_the_description()
27 | {
28 | $this->getDescription()->shouldReturn('description');
29 | }
30 |
31 | function it_gets_the_raw_uri()
32 | {
33 | $this->getPatchUri()->shouldReturn('http://www.drupal.org/files/foo-12345-02.patch');
34 | }
35 |
36 | function it_calculates_a_uri()
37 | {
38 | $this->getIssueUri()->shouldReturn('https://www.drupal.org/node/12345');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/spec/Patch/GithubPatchSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith('project', 'https://patch-diff.githubusercontent.com/raw/Gizra/message_subscribe/pull/64.diff', 'description');
14 | }
15 |
16 | function it_is_initializable()
17 | {
18 | $this->shouldHaveType(GithubPatch::class);
19 | }
20 |
21 | function it_gets_the_project()
22 | {
23 | $this->getProject()->shouldReturn('project');
24 | }
25 |
26 | function it_gets_the_description()
27 | {
28 | $this->getDescription()->shouldReturn('description');
29 | }
30 |
31 | function it_gets_the_raw_uri()
32 | {
33 | $this->getPatchUri()->shouldReturn('https://patch-diff.githubusercontent.com/raw/Gizra/message_subscribe/pull/64.diff');
34 | }
35 |
36 | function it_calculates_a_uri()
37 | {
38 | $this->getIssueUri()->shouldReturn('https://github.com/Gizra/message_subscribe/pull/64');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/.docksal/docksal.env:
--------------------------------------------------------------------------------
1 | # This is a shared configuration file that is intended to be stored in the project repo.
2 | # To override a variable locally:
3 | # - create .docksal/docksal-local.env file and local variable overrides there
4 | # - add .docksal/docksal-local.env to .gitignore
5 |
6 | # Use the default Docksal stack
7 | DOCKSAL_STACK=default
8 |
9 | # Lock images versions for LAMP services
10 | # This will prevent images from being updated when Docksal is updated
11 | #WEB_IMAGE='docksal/web:2.1-apache2.4'
12 | #DB_IMAGE='docksal/db:1.1-mysql-5.7'
13 | CLI_IMAGE='docksal/cli:2.11-php7.4'
14 |
15 | # Override virtual host (matches project folder name by default)
16 | #VIRTUAL_HOST=composer-patches.docksal
17 | # Docksal configuration.
18 | DOCROOT=public
19 |
20 | # MySQL settings.
21 | # MySQL will be exposed on a random port. Use "fin ps" to check the port.
22 | # To have a static MySQL port assigned, copy the line below into the .docksal/docksal-local.env file
23 | # and replace the host port "0" with a unique host port number (e.g. MYSQL_PORT_MAPPING='33061:3306')
24 | MYSQL_PORT_MAPPING='0:3306'
25 |
26 | # Enable/disable xdebug
27 | # To override locally, copy the two lines below into .docksal/docksal-local.env and adjust as necessary
28 | XDEBUG_ENABLED=0
29 |
--------------------------------------------------------------------------------
/tests/AnalyzeCommandTest.php:
--------------------------------------------------------------------------------
1 | find('composer-analyze');
31 | $this->commandTester = new CommandTester($command);
32 | }
33 |
34 | /**
35 | * @covers ::execute
36 | */
37 | public function testEmpty()
38 | {
39 | vfsStream::setup('foo', null, ['bar' => ['composer.json' => '']]);
40 | $this->commandTester->execute(['directory' => vfsStream::url('foo'), '-f' => 'bad']);
41 | $this->assertEquals('No patches found in ' . vfsStream::url('foo') . ".\n", $this->commandTester->getDisplay());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/GeneratePatchesTrait.php:
--------------------------------------------------------------------------------
1 | description = $description;
41 | $this->project = $project;
42 | $this->rawUri = $uri;
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | public function getProject()
49 | {
50 | return $this->project;
51 | }
52 |
53 | /**
54 | * {@inheritdoc}
55 | */
56 | public function getDescription()
57 | {
58 | return $this->description;
59 | }
60 |
61 | /**
62 | * {@inheritdoc}
63 | */
64 | public function getPatchUri()
65 | {
66 | return $this->rawUri;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/Patch/GithubPatchTest.php:
--------------------------------------------------------------------------------
1 | expectException(NoIssueFoundException::class);
23 | }
24 | $patch = new GithubPatch('test_project', $patch_uri, 'test description');
25 | $this->assertEquals($expected, $patch->getIssueUri());
26 | }
27 |
28 | /**
29 | * Data provider for ::testGetIssueUri.
30 | */
31 | public function providerTestGetIssueUri()
32 | {
33 | return [
34 | ['https://github.com/Gizra/message_subscribe/pull/64.diff',
35 | 'https://github.com/Gizra/message_subscribe/pull/64'],
36 | ['https://github.com/Gizra/message_subscribe/pull/64.patch',
37 | 'https://github.com/Gizra/message_subscribe/pull/64'],
38 | ['https://patch-diff.githubusercontent.com/raw/Gizra/message_subscribe/pull/64.diff',
39 | 'https://github.com/Gizra/message_subscribe/pull/64'],
40 | ['https://patch-diff.githubusercontent.com/raw/Gizra/message_subscribe/pull/64.patch',
41 | 'https://github.com/Gizra/message_subscribe/pull/64'],
42 | ['https://github.com/pull/file.diff', '', true],
43 | ];
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/spec/Analyze/PatchesSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith($output);
17 | }
18 |
19 | function it_is_initializable()
20 | {
21 | $this->shouldHaveType(Patches::class);
22 | }
23 |
24 | function it_can_set_and_get_patches(DrupalOrgPatch $patch)
25 | {
26 | $patches = [
27 | $patch,
28 | ];
29 | $this->setPatches($patches);
30 | $this->getPatches()->shouldReturn($patches);
31 | }
32 |
33 | function it_can_analyze_patches()
34 | {
35 | $drupal = new DrupalOrgPatch(
36 | 'project',
37 | 'http://www.drupal.org/files/foo-12345-02.patch',
38 | 'description'
39 | );
40 | $bad = new DrupalOrgPatch(
41 | 'project',
42 | 'http://www.drupal.org/files/foo-no-issue.patch',
43 | 'description'
44 | );
45 |
46 | $patches = [$drupal, $bad];
47 | $this->setPatches($patches);
48 | $return = [
49 | ['Project', 'Issue', 'Raw patch', 'Description'],
50 | [$drupal->getProject(), $drupal->getIssueUri(), $drupal->getPatchUri(), $drupal->getDescription()],
51 | [$bad->getProject(), Patches::NO_ISSUE_URI, $bad->getPatchUri(), $bad->getDescription()],
52 | ];
53 |
54 | $this->analyze()->shouldReturn($return);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phase2/composer-analytics",
3 | "description": "Parses composer files and gathers analytics such as patch use.",
4 | "license": "GPL-2.0+",
5 | "authors": [
6 | {
7 | "name": "Jonathan Hedstrom",
8 | "email": "jhedstrom@gmail.com"
9 | }
10 | ],
11 | "require": {
12 | "php": ">=7.3",
13 | "ext-json": "*",
14 | "symfony/console": "^5.1",
15 | "symfony/finder": "^5.1",
16 | "league/csv": "^9.6",
17 | "symfony/yaml": "^5.1"
18 | },
19 | "require-dev": {
20 | "phpunit/phpunit": "^9.3",
21 | "squizlabs/php_codesniffer": "^3.5",
22 | "mikey179/vfsstream": "^1.6",
23 | "jakub-onderka/php-parallel-lint": "^1.0",
24 | "phpspec/phpspec": "^6.2",
25 | "phpspec/prophecy-phpunit": "^2.0"
26 | },
27 | "autoload": {
28 | "psr-4": {"Phase2\\ComposerAnalytics\\": "src"}
29 | },
30 | "autoload-dev": {
31 | "psr-4": {"Phase2\\ComposerAnalytics\\Tests\\": "tests"}
32 | },
33 | "scripts": {
34 | "lint": "parallel-lint src tests spec",
35 | "spec": "phpspec run",
36 | "phpunit":"phpunit --log-junit=reports/unitreport.xml --coverage-text --coverage-html=reports/coverage --coverage-clover=reports/coverage.xml",
37 | "phpcs": "phpcs --encoding=utf-8 --standard=./phpcs-ruleset.xml --report-checkstyle=reports/checkstyle-phpcs.xml --report-full --extensions=php src/* tests/*",
38 | "test": [
39 | "composer validate --no-interaction",
40 | "@lint",
41 | "@spec",
42 | "@phpunit",
43 | "@phpcs"
44 | ]
45 | },
46 | "bin": ["bin/composer-analyze"],
47 | "extra": {
48 | "branch-alias": {
49 | "dev-master": "1.0.x-dev"
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Analyze/PatchesTest.php:
--------------------------------------------------------------------------------
1 | prophesize(OutputInterface::class)->reveal();
36 | $this->patchAnalyzer = new Patches($output);
37 | $this->patchAnalyzer->setPatches($this->generatePatches());
38 | }
39 |
40 | /**
41 | * @covers ::analyze
42 | */
43 | public function testAnalyze()
44 | {
45 | $expected = [
46 | ['Project', 'Issue', 'Raw patch', 'Description'],
47 | ['drupal/core', 'No known issue', 'src/patches/foo.patch', 'A local patch'],
48 | ['drupal/core', 'https://www.drupal.org/node/12345', 'https://www.drupal.org/files/12345-04.patch',
49 | 'Terse'],
50 | ['drupal/core', 'https://www.drupal.org/node/12345', 'https://www.drupal.org/files/12345-277.patch',
51 | 'A different desc'],
52 | ['drupal/message_subscribe', 'https://github.com/foo/bar/pull/123',
53 | 'https://github.com/foo/bar/pull/123.diff', 'A description'],
54 | ['drupal/token', 'Could not determine issue', 'https://www.drupal.org/files/no-context-at-all-patch.patch',
55 | 'Fix it'],
56 | ];
57 | $this->assertEquals($expected, $this->patchAnalyzer->analyze());
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Parser/DrushMake.php:
--------------------------------------------------------------------------------
1 | parse($file);
28 | if (!isset($contents['projects'])) {
29 | return $found_patches;
30 | }
31 | foreach ($contents['projects'] as $project_name => $project_data) {
32 | if (isset($project_data['patch'])) {
33 | foreach ($project_data['patch'] as $description => $patch) {
34 | $found_patches[] = PatchFactory::getPatch($project_name, $patch, $description);
35 | }
36 | }
37 | }
38 |
39 | return $found_patches;
40 | }
41 |
42 | /**
43 | * Determine YAML or INI format, and parse.
44 | *
45 | * @param string $data
46 | * @return array
47 | *
48 | * @throws ParseException
49 | * This will be thrown if the data cannot be parsed as INI or YAML.
50 | */
51 | protected function parse($data)
52 | {
53 | // Try YAML first.
54 | try {
55 | $parsed = Yaml::parse($data);
56 | return $parsed;
57 | } catch (ParseException $e) {
58 | // Either an INI file, or invalid YAML.
59 | }
60 |
61 | // Try INI.
62 | // @todo Consider using Drush's INI parser which is more robust, but might not be needed for patch analysis.
63 | if ($parsed = DrushIniParser::parse($data)) {
64 | return $parsed;
65 | }
66 |
67 | // Throw YAML exception.
68 | throw $e;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Analyze/Patches.php:
--------------------------------------------------------------------------------
1 | output = $output;
49 | }
50 |
51 | /**
52 | * Set patches to analyze.
53 | *
54 | * @param PatchInterface[] $patches
55 | */
56 | public function setPatches(array $patches)
57 | {
58 | $this->patches = $patches;
59 | }
60 |
61 | /**
62 | * Get raw patches.
63 | *
64 | * @return \Phase2\ComposerAnalytics\Patch\PatchInterface[]
65 | */
66 | public function getPatches()
67 | {
68 | return $this->patches;
69 | }
70 |
71 | /**
72 | * Analyze patches.
73 | *
74 | * @return array
75 | */
76 | public function analyze()
77 | {
78 | $analysis = [static::$header];
79 |
80 | $bad_patches = [];
81 | foreach ($this->patches as $patch) {
82 | if ($patch instanceof HasIssueUriInterface) {
83 | try {
84 | $issue_uri = $patch->getIssueUri();
85 | } catch (NoIssueFoundException $e) {
86 | $bad_patches[] = $patch->getPatchUri();
87 | $issue_uri = static::NO_ISSUE_URI;
88 | }
89 | } else {
90 | $issue_uri = 'No known issue';
91 | }
92 |
93 | $analysis[] = [
94 | $patch->getProject(),
95 | $issue_uri,
96 | $patch->getPatchUri(),
97 | $patch->getDescription(),
98 | ];
99 | }
100 |
101 | if (!empty($bad_patches)) {
102 | $this->output->writeln(
103 | sprintf(
104 | 'Found %s remote patches with no issue number associated:',
105 | count($bad_patches)
106 | )
107 | );
108 | $this->output->write(sprintf("\n * %s\n\n", implode("\n * ", $bad_patches)));
109 | }
110 |
111 | // @todo Some sort of sorting, and potentially aggregation.
112 | return $analysis;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Parser/Drush/DrushIniParser.php:
--------------------------------------------------------------------------------
1 | $parent[$key]);
59 | }
60 | if (!isset($parent[$key]) || !is_array($parent[$key])) {
61 | $parent[$key] = array();
62 | }
63 | $parent = &$parent[$key];
64 | }
65 |
66 | // Handle PHP constants.
67 | if (defined($value)) {
68 | $value = constant($value);
69 | }
70 |
71 | // Insert actual value.
72 | if ($last == '') {
73 | $last = count($parent);
74 | }
75 | if (isset($merge_item) && isset($parent[$last]) && is_array($parent[$last])) {
76 | $parent[$last][$merge_item] = $value;
77 | }
78 | else {
79 | $parent[$last] = $value;
80 | }
81 | }
82 | return $info;
83 | }
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/src/AnalyzeCommand.php:
--------------------------------------------------------------------------------
1 | setName('composer-analyze')
24 | ->setDescription('Analyze composer files for patch data')
25 | ->setHelp('This command will find all composer.json files within a given root directory.')
26 | ->addArgument(
27 | 'directory',
28 | InputArgument::OPTIONAL,
29 | 'Root directory to scan for composer.json files',
30 | getcwd()
31 | )
32 | ->addOption(
33 | 'type',
34 | 't',
35 | InputOption::VALUE_OPTIONAL,
36 | 'File type to process (either `composer` or `make`). Defaults to composer.json',
37 | 'composer'
38 | )
39 | ->addOption(
40 | 'format',
41 | 'f',
42 | InputOption::VALUE_OPTIONAL,
43 | 'The output format (either `json` or `csv`). Defaults to csv',
44 | 'csv'
45 | )
46 | ->addOption(
47 | 'output',
48 | 'o',
49 | InputOption::VALUE_OPTIONAL,
50 | 'The output location file path. Defaults to stdout'
51 | );
52 | }
53 |
54 | /**
55 | * {@inheritdoc}
56 | */
57 | protected function execute(InputInterface $input, OutputInterface $output)
58 | {
59 | $type = $input->getOption('type');
60 | $parser = Factory::get($type);
61 |
62 | $finder = new Finder();
63 | $finder->files();
64 | $finder->name($parser->getPattern());
65 |
66 | $directory = $input->getArgument('directory');
67 | $output->writeln(
68 | sprintf('Scanning for %s files in %s.', $type, $directory),
69 | OutputInterface::VERBOSITY_VERBOSE
70 | );
71 | $patches = [];
72 | foreach ($finder->in($directory) as $file) {
73 | $output->writeln(
74 | sprintf('Found %s file.', $file->getRealPath()),
75 | OutputInterface::VERBOSITY_VERBOSE
76 | );
77 | $patches += $parser->findPatches($file->getContents());
78 | }
79 | if (empty($patches)) {
80 | $output->writeln(sprintf('No patches found in %s.', $directory));
81 | return 0;
82 | }
83 |
84 | $analyzer = new Patches($output);
85 | $analyzer->setPatches($patches);
86 | $analyzed = $analyzer->analyze();
87 |
88 | $formatted = FormatterFactory::get($input->getOption('format'))->format($analyzed);
89 |
90 | if ($input->getOption('output')) {
91 | file_put_contents($input->getOption('output'), $formatted);
92 | $output->writeln(sprintf('Report written to %s.', $input->getOption('output')));
93 | } else {
94 | $output->writeln($formatted);
95 | }
96 |
97 | return 0;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------