Skip to content

Commit

Permalink
Merge pull request #12 from navarr/bin-command
Browse files Browse the repository at this point in the history
Add "depanno" CLI command
  • Loading branch information
navarr authored Jul 14, 2021
2 parents 607ba1d + 328c05a commit 00e6a21
Show file tree
Hide file tree
Showing 14 changed files with 428 additions and 35 deletions.
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
"main": "2.x-dev"
}
},
"bin": [
"depanno"
],
"archive": {
"exclude": [
".github",
Expand Down
45 changes: 45 additions & 0 deletions depanno
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env php
<?php

/**
* @copyright 2021 Navarr Barnier. All Rights Reserved.
*/

declare(strict_types=1);

use Navarr\Depends\Controller\CliApplication;

if (version_compare('7.1.0', PHP_VERSION, '>')) {
fwrite(
STDERR,
"This version of DepAnno requires PHP 7.1.0 or greater." . PHP_EOL .
"You are currently using PHP " . PHP_VERSION . PHP_EOL
);
die(1);
}

if (!ini_get('date.timezone')) {
ini_set('date.timezone', 'UTC');
}

$found = false;
foreach (
[
__DIR__ . '/../../autoload.php',
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/vendor/autoload.php',
] as $autoload
) {
if (file_exists($autoload)) {
require_once($autoload);
$found = true;
break;
}
}

if (!$found) {
fwrite(STDERR, "You must install DepAnno using Composer");
die(1);
}

die(CliApplication::execute());
25 changes: 25 additions & 0 deletions src/Controller/CliApplication.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/**
* @copyright 2021 Navarr Barnier. All Rights Reserved.
*/

declare(strict_types=1);

namespace Navarr\Depends\Controller;

use Navarr\Attribute\Dependency;
use Symfony\Component\Console\Application;

#[Dependency('symfony/console', '^5', 'Creates a Symfony Application')]
class CliApplication
{
private const VERSION = '2.1.0';

public static function execute(): int
{
$application = new Application('DepAnno', static::VERSION);
$application->add(new WhyBlockCommandController());
return $application->run();
}
}
158 changes: 158 additions & 0 deletions src/Controller/WhyBlockCommandController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php

/**
* @copyright 2021 Navarr Barnier. All Rights Reserved.
*/

declare(strict_types=1);

namespace Navarr\Depends\Controller;

use DI\ContainerBuilder;
use InvalidArgumentException;
use Navarr\Attribute\Dependency;
use Navarr\Depends\Command\WhyBlockCommand;
use Navarr\Depends\Command\WhyBlockCommand\CsvOutputHandler;
use Navarr\Depends\Command\WhyBlockCommand\JsonOutputHandler;
use Navarr\Depends\Command\WhyBlockCommand\OutputHandlerInterface;
use Navarr\Depends\Command\WhyBlockCommand\StandardOutputHandler;
use Navarr\Depends\Command\WhyBlockCommand\XmlOutputHandler;
use Navarr\Depends\IssueHandler\FailOnIssueHandler;
use Navarr\Depends\IssueHandler\IssueHandlerInterface;
use Navarr\Depends\IssueHandler\NotifyOnIssueHandler;
use Navarr\Depends\Parser\AstParser;
use Navarr\Depends\Parser\LegacyParser;
use Navarr\Depends\Parser\ParserInterface;
use Navarr\Depends\Parser\ParserPool;
use Navarr\Depends\Proxy\StdOutWriter;
use Navarr\Depends\Proxy\WriterInterface;
use Navarr\Depends\ScopeDeterminer\ComposerScopeDeterminer;
use Navarr\Depends\ScopeDeterminer\DirectoryScopeDeterminer;
use Navarr\Depends\ScopeDeterminer\PhpFileFinder;
use Navarr\Depends\ScopeDeterminer\ScopeDeterminerInterface;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

use function DI\autowire;
use function DI\create;

class WhyBlockCommandController extends Command
{
private const LEGACY_ANNOTATION = 'include-legacy-annotations';
private const FAIL_ON_ERROR = 'fail-on-error';
private const OUTPUT_FORMAT = 'format';

private const FORMAT_TEXT = 'text';
private const FORMAT_CSV = 'csv';
private const FORMAT_JSON = 'json';
private const FORMAT_XML = 'xml';

private const ACCEPTABLE_FORMATS = [
self::FORMAT_CSV,
self::FORMAT_TEXT,
self::FORMAT_JSON,
self::FORMAT_XML,
];

private const FORMAT_MAPPER = [
self::FORMAT_CSV => CsvOutputHandler::class,
self::FORMAT_TEXT => StandardOutputHandler::class,
self::FORMAT_JSON => JsonOutputHandler::class,
self::FORMAT_XML => XmlOutputHandler::class,
];

// phpcs:disable Generic.Files.LineLength.TooLong -- Attribute support pre PHP 8
#[Dependency('symfony/console', '^5', 'Command\'s setName, addArgument and addOption methods as well as InputArgument\'s constants of REQUIRED and VALUE_NONE')]
#[Dependency('php-di/php-di', '^6', 'DI\ContainerBuilder::addDefinitions and the existence of the DI\autowire function')]
// phpcs:enable Generic.Files.LineLength.TooLong
protected function configure(): void
{
$this->setName('why-block')
->addArgument('package', InputArgument::REQUIRED, 'Package to inspect')
->addArgument('version', InputArgument::REQUIRED, 'Version you want to update it to')
->addArgument('directory', InputArgument::REQUIRED, 'Directory to search in')
->addOption(
self::OUTPUT_FORMAT,
['f'],
InputOption::VALUE_OPTIONAL,
'Format to output results in. Accepted values: text, csv, json, xml',
'text'
)
->addOption(
self::FAIL_ON_ERROR,
['e'],
InputOption::VALUE_NONE,
'Immediately fail on parsing errors'
)
->addOption(
self::LEGACY_ANNOTATION,
['l'],
InputOption::VALUE_NONE,
'Include old @dependency/@composerDependency annotations in search'
);
}

#[Dependency('symfony/console', '^5', 'InputInterface::getOption and OutputInterface::writeln')]
protected function execute(
InputInterface $input,
OutputInterface $output
): int {
$packageToSearchFor = $input->getArgument('package');
$versionToCompareTo = $input->getArgument('version');
$directory = $input->getArgument('directory');
$outputFormat = $input->getOption(self::OUTPUT_FORMAT);

if (!is_string($directory)) {
throw new InvalidArgumentException('Only one directory is allowed');
}
if (!is_string($packageToSearchFor)) {
throw new InvalidArgumentException('Only one package is allowed');
}
if (!is_string($versionToCompareTo)) {
throw new InvalidArgumentException('Only one version is allowed');
}
if (!is_string($outputFormat)) {
throw new InvalidArgumentException('Only one output format is allowed');
}

$outputFormat = strtolower($outputFormat);
if (!in_array($outputFormat, static::ACCEPTABLE_FORMATS)) {
$outputFormat = 'text';
}

$containerBuilder = new ContainerBuilder();
$containerBuilder->addDefinitions(
[
InputInterface::class => $input,
OutputInterface::class => $output,
IssueHandlerInterface::class => $input->getOption(static::FAIL_ON_ERROR)
? FailOnIssueHandler::class
: NotifyOnIssueHandler::class,
ParserInterface::class => static function (ContainerInterface $container) use ($input) {
$parsers = [$container->get(AstParser::class)];
if ($input->getOption(static::LEGACY_ANNOTATION)) {
$parsers[] = $container->get(LegacyParser::class);
}
return new ParserPool($parsers);
},
WriterInterface::class => autowire(StdOutWriter::class),
ScopeDeterminerInterface::class => static function (ContainerInterface $container) use ($directory) {
return new DirectoryScopeDeterminer(
$container->get(PhpFileFinder::class),
$directory
);
},
OutputHandlerInterface::class => autowire(static::FORMAT_MAPPER[$outputFormat]),
]
);
$container = $containerBuilder->build();

/** @var WhyBlockCommand $command */
$command = $container->get(WhyBlockCommand::class);
return $command->execute($packageToSearchFor, $versionToCompareTo);
}
}
40 changes: 5 additions & 35 deletions src/ScopeDeterminer/ComposerScopeDeterminer.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,20 @@ class ComposerScopeDeterminer implements ScopeDeterminerInterface
/** @var Composer */
private $composer;

/** @var PhpFileDeterminer */
private $phpFileDeterminer;
/** @var PhpFileFinder */
private $phpFileFinder;

/** @var int */
private $scope;

public function __construct(
Composer $composer,
PhpFileDeterminer $phpFileDeterminer,
PhpFileFinder $phpFileFinder,
#[ExpectedValues(valuesFromClass: ComposerScopeDeterminer::class)]
int $scope = self::SCOPE_PROJECT_ONLY
) {
$this->composer = $composer;
$this->phpFileDeterminer = $phpFileDeterminer;
$this->phpFileFinder = $phpFileFinder;
$this->scope = $scope;
}

Expand Down Expand Up @@ -133,40 +133,10 @@ private function getAllFilesForAutoload(
continue;
}
if (is_dir($realDir)) {
$results = $this->getAllPhpFiles($realDir, $results);
$results = $this->phpFileFinder->findAll($dir, $results);
}
}
}
return $results;
}

/**
* Find all PHP files by recursively searching a directory
*
* @param string $dir Directory to search recursively
* @param string[] $results Array of file paths to merge with
* @return string[] File paths
*/
private function getAllPhpFiles(string $dir, array $results = []): array
{
$files = scandir($dir);
if ($files === false) {
return $results;
}

foreach ($files as $value) {
$path = realpath($dir . DIRECTORY_SEPARATOR . $value);
if ($path === false) {
continue;
}

if (is_file($path) && $this->phpFileDeterminer->isPhp($path)) {
$results[] = $path;
} elseif (is_dir($path) && !in_array($value, ['.', '..'])) {
$results = $this->getAllPhpFiles($path, $results);
}
}

return $results;
}
}
29 changes: 29 additions & 0 deletions src/ScopeDeterminer/DirectoryScopeDeterminer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/**
* @copyright 2021 Navarr Barnier. All Rights Reserved.
*/

declare(strict_types=1);

namespace Navarr\Depends\ScopeDeterminer;

class DirectoryScopeDeterminer implements ScopeDeterminerInterface
{
/** @var string */
private $directory;

/** @var PhpFileFinder */
private $phpfileFinder;

public function __construct(PhpFileFinder $phpfileFinder, string $directory = '.')
{
$this->directory = $directory;
$this->phpfileFinder = $phpfileFinder;
}

public function getFiles(): array
{
return $this->phpfileFinder->findAll($this->directory);
}
}
53 changes: 53 additions & 0 deletions src/ScopeDeterminer/PhpFileFinder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

/**
* @copyright 2021 Navarr Barnier. All Rights Reserved.
*/

declare(strict_types=1);

namespace Navarr\Depends\ScopeDeterminer;

class PhpFileFinder
{
/** @var PhpFileDeterminer */
private $phpFileDeterminer;

public function __construct(PhpFileDeterminer $phpFileDeterminer)
{
$this->phpFileDeterminer = $phpFileDeterminer;
}

/**
* Find all PHP files by recursively searching a directory
*
* @param string $dir Directory to search recursively
* @param string[] $results Array of file paths to merge with
* @return string[] File paths
*/
public function findAll(string $dir, array $results = []): array
{
// Directories is ever expanding by the loop. We do this instead of recursion b/c I have an unhealthy fear
// of recursion limits
$directories = [$dir];
for ($i = 0; $i < count($directories); ++$i) {
$files = scandir($directories[$i]);
if ($files !== false) {
foreach ($files as $value) {
$path = realpath($directories[$i] . DIRECTORY_SEPARATOR . $value);
if ($path === false) {
continue;
}

if (is_file($path) && $this->phpFileDeterminer->isPhp($path)) {
$results[] = $path;
} elseif (is_dir($path) && !in_array($value, ['.', '..'])) {
$directories[] = $path;
}
}
}
}

return $results;
}
}
Loading

0 comments on commit 00e6a21

Please sign in to comment.