Skip to content

Commit

Permalink
Merge pull request #24 from jjanvier/dispatch-events
Browse files Browse the repository at this point in the history
Dispatch events (to get a fancy decoupled output)
  • Loading branch information
jjanvier committed Mar 22, 2016
2 parents a2de96d + cef051b commit c5df176
Show file tree
Hide file tree
Showing 20 changed files with 980 additions and 99 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ But adding a new kind of coupling detections is doable in the future. We could f
coupling issues of Symfony services that are defined in YAML or XML

At the moment, 3 types of rules are supported:
* *forbidden*: A node respects such a rule if no rule token is present in the node. In case the node does not respect this rule, an error violation will be sent.
* *discouraged*: A node respects such a rule if no rule token is present in the node. In case the node does not respect this rule, a warning violation will be sent.
* *only*: A node respects such a rule if the node contains only tokens defined in the rule. In case the node does not respect this rule, an error violation will be sent.

* _forbidden_: A node respects such a rule if no rule token is present in the node. In case the node does not respect this rule, an error violation will be sent.
* _discouraged_: A node respects such a rule if no rule token is present in the node. In case the node does not respect this rule, a warning violation will be sent.
* _only_: A node respects such a rule if the node contains only tokens defined in the rule. In case the node does not respect this rule, an error violation will be sent.

## Requirements

Expand Down Expand Up @@ -40,8 +41,8 @@ you're good to go:
The detect command detects coupling problems for a given file or directory depending on the
coupling rules that have been defined:

php bin/coupling-detector detect /path/to/dir
php bin/coupling-detector detect /path/to/file
php bin/php-coupling-detector detect /path/to/dir
php bin/php-coupling-detector detect /path/to/file

The exit status of the detect command can be: 0 if no violations have been raised, 10 in case of
warnings and 99 in case of errors.
Expand Down Expand Up @@ -91,4 +92,4 @@ The detect command detects coupling problems for a given file or directory depen

With the ``--config-file`` option you can specify the path to the ``.php_cd`` file:

php bin/coupling-detector detect /path/to/dir --config-file=/path/to/my/configuration.php_cd
php bin/php-coupling-detector detect /path/to/dir --config-file=/path/to/my/configuration.php_cd
File renamed without changes.
10 changes: 7 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@
],
"require": {
"php": ">=5.3.6",
"fabpot/php-cs-fixer": "1.11.*"
"fabpot/php-cs-fixer": "~1.11",
"symfony/event-dispatcher": "~2.3|~3.0",
"symfony/finder": "~2.3|~3.0",
"symfony/console": "~2.3|~3.0",
"symfony/filesystem": "~2.3|~3.0"
},
"require-dev": {
"phpspec/phpspec": "2.3.*"
"phpspec/phpspec": "~2.3"
},
"config": {
"bin-dir": "bin"
Expand All @@ -26,7 +30,7 @@
"Akeneo\\CouplingDetector\\": "src/Akeneo/CouplingDetector/"
}
},
"bin": ["bin/coupling-detector"],
"bin": ["bin/php-coupling-detector"],
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
Expand Down
20 changes: 17 additions & 3 deletions spec/Akeneo/CouplingDetector/CouplingDetectorSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,29 @@
use Akeneo\CouplingDetector\Domain\NodeInterface;
use Akeneo\CouplingDetector\Domain\RuleInterface;
use Akeneo\CouplingDetector\Domain\ViolationInterface;
use Akeneo\CouplingDetector\Event\Events;
use Akeneo\CouplingDetector\NodeParser\NodeParserInterface;
use Akeneo\CouplingDetector\NodeParser\NodeParserResolver;
use Akeneo\CouplingDetector\RuleChecker;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Finder\Finder;

class CouplingDetectorSpec extends ObjectBehavior
{
function let(NodeParserResolver $nodeExtractorResolver, RuleChecker $ruleChecker)
{
$this->beConstructedWith($nodeExtractorResolver, $ruleChecker);
function let(
NodeParserResolver $nodeExtractorResolver,
RuleChecker $ruleChecker,
EventDispatcherInterface $eventDispatcher
) {
$this->beConstructedWith($nodeExtractorResolver, $ruleChecker, $eventDispatcher);
}

function it_detects_the_violations_of_files(
$ruleChecker,
$nodeExtractorResolver,
$eventDispatcher,
NodeInterface $node,
ViolationInterface $violation,
Finder $finder,
Expand All @@ -37,6 +43,14 @@ function it_detects_the_violations_of_files(
$ruleChecker->check($rule1, $node)->willReturn(null);
$ruleChecker->check($rule2, $node)->willReturn($violation);

$eventDispatcher->dispatch(Events::PRE_NODES_PARSED, Argument::type('Akeneo\CouplingDetector\Event\PreNodesParsedEvent'))->shouldBeCalled();
$eventDispatcher->dispatch(Events::NODE_PARSED, Argument::type('Akeneo\CouplingDetector\Event\NodeParsedEvent'))->shouldBeCalled();
$eventDispatcher->dispatch(Events::POST_NODES_PARSED, Argument::type('Akeneo\CouplingDetector\Event\PostNodesParsedEvent'))->shouldBeCalled();
$eventDispatcher->dispatch(Events::PRE_RULES_CHECKED, Argument::type('Akeneo\CouplingDetector\Event\PreRulesCheckedEvent'))->shouldBeCalled();
$eventDispatcher->dispatch(Events::NODE_CHECKED, Argument::type('Akeneo\CouplingDetector\Event\NodeChecked'))->shouldBeCalledTimes(2);
$eventDispatcher->dispatch(Events::RULE_CHECKED, Argument::type('Akeneo\CouplingDetector\Event\RuleCheckedEvent'))->shouldBeCalledTimes(2);
$eventDispatcher->dispatch(Events::POST_RULES_CHECKED, Argument::type('Akeneo\CouplingDetector\Event\PostRulesCheckedEvent'))->shouldBeCalled();

$violations = $this->detect($finder, array($rule1, $rule2));
$violations->shouldHaveCount(1);
$violations->shouldBeArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use Symfony\Component\Finder\Finder;

/**
* Default finder implementation
* Default finder implementation.
*
* @author Julien Janvier <[email protected]>
* @license http://opensource.org/licenses/MIT MIT
Expand Down
140 changes: 67 additions & 73 deletions src/Akeneo/CouplingDetector/Console/Command/DetectCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
namespace Akeneo\CouplingDetector\Console\Command;

use Akeneo\CouplingDetector\Configuration\Configuration;
use Akeneo\CouplingDetector\Console\OutputFormatter;
use Akeneo\CouplingDetector\CouplingDetector;
use Akeneo\CouplingDetector\Domain\RuleInterface;
use Akeneo\CouplingDetector\Domain\ViolationInterface;
use Akeneo\CouplingDetector\Formatter\Console\DotFormatter;
use Akeneo\CouplingDetector\Formatter\Console\PrettyFormatter;
use Akeneo\CouplingDetector\NodeParser\NodeParserResolver;
use Akeneo\CouplingDetector\RuleChecker;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
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 Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Filesystem\Filesystem;

/**
Expand All @@ -25,8 +28,10 @@
*/
class DetectCommand extends Command
{
const EXIT_WITH_WARNINGS = 10;
const EXIT_WITH_ERRORS = 99;
const EXIT_WITH_WARNINGS = 10;
const EXIT_WITH_ERRORS = 99;

private $formats = ['pretty', 'dot'];

/**
* {@inheritedDoc}.
Expand All @@ -40,21 +45,28 @@ protected function configure()
new InputArgument('path', InputArgument::OPTIONAL, 'path of the project', null),
new InputOption(
'config-file',
null,
'c',
InputOption::VALUE_REQUIRED,
'File path of the configuration file'
),
new InputOption(
'format',
'f',
InputOption::VALUE_REQUIRED,
'file path of the configuration file'
sprintf('Output format (%s)', implode(', ', $this->formats)),
$this->formats[0]
),
)
)
->setDescription('Detect coupling violations')
->setDescription('Detect coupling rules')
->setHelp(
<<<HELP
The <info>%command.name%</info> command detects coupling problems for a given file or directory depending on the
coupling rules that have been defined:
<info>php %command.full_name% /path/to/dir</info>
<info>php %command.full_name% /path/to/file</info>
The exit status of the <info>%command.name%</info> command can be: 0 if no violations have been raised, 10 in case of
The exit status of the <info>%command.name%</info> command can be: 0 if no rules have been raised, 10 in case of
warnings and 99 in case of errors.
You can save the configuration in a <comment>.php_cd</comment> file in the root directory of
Expand Down Expand Up @@ -100,6 +112,9 @@ protected function configure()

With the <comment>--config-file</comment> option you can specify the path to the <comment>.php_cd</comment> file:
<info>php %command.full_name% /path/to/dir --config-file=/path/to/my/configuration.php_cd</info>

With the <comment>--format</comment> option you can specify the output format:
<info>php %command.full_name% /path/to/dir --format=dot</info>
HELP
)
;
Expand All @@ -110,10 +125,20 @@ protected function configure()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->setFormatter(new OutputFormatter(true));

if (null !== $format = $input->getOption('format')) {
if (!in_array($format, $this->formats)) {
throw new \RuntimeException(
sprintf('Format "%s" is unknown. Available formats: %s.', $format, implode(', ', $this->formats))
);
}
}

if (null !== $path = $input->getArgument('path')) {
$filesystem = new Filesystem();
if (!$filesystem->isAbsolutePath($path)) {
$path = getcwd() . DIRECTORY_SEPARATOR . $path;
$path = getcwd().DIRECTORY_SEPARATOR.$path;
}
}

Expand All @@ -125,22 +150,20 @@ protected function execute(InputInterface $input, OutputInterface $output)
$configDir = getcwd();
// path is directory
}
$configFile = $configDir . DIRECTORY_SEPARATOR . '.php_cd';
$configFile = $configDir.DIRECTORY_SEPARATOR.'.php_cd';
}

$output->writeln('<info>Detecting coupling violations...</info>');

$config = $this->loadConfiguration($configFile);
$rules = $config->getRules();
$finder = $config->getFinder();
$finder->in($path);

$nodeParserResolver = new NodeParserResolver();
$ruleChecker = new RuleChecker();
$detector = new CouplingDetector($nodeParserResolver, $ruleChecker);
$eventDispatcher = $this->initEventDispatcher($output, $format, $input->getOption('verbose'));
$detector = new CouplingDetector($nodeParserResolver, $ruleChecker, $eventDispatcher);

$violations = $detector->detect($finder, $rules);
$this->outputViolations($output, $violations, $input->getOption('verbose'));

return $this->determineExitCode($violations);
}
Expand All @@ -150,15 +173,15 @@ protected function execute(InputInterface $input, OutputInterface $output)
*
* @return int
*/
protected function determineExitCode(array $violations)
private function determineExitCode(array $violations)
{
if (0 === count($violations)) {
return 0;
}

$exitCode = self::EXIT_WITH_WARNINGS;
foreach ($violations as $violation) {
if (ViolationInterface::TYPE_ERROR=== $violation->getType()) {
if (ViolationInterface::TYPE_ERROR === $violation->getType()) {
$exitCode = self::EXIT_WITH_ERRORS;
break;
}
Expand All @@ -167,68 +190,12 @@ protected function determineExitCode(array $violations)
return $exitCode;
}

/**
* @param OutputInterface $output
* @param ViolationInterface[] $violations
* @param bool $verbose
*/
protected function outputViolations(OutputInterface $output, array $violations, $verbose = false)
{
$warningStyle = new OutputFormatterStyle('white', 'yellow', array('bold'));
$output->getFormatter()->setStyle('warning', $warningStyle);
$errorStyle = new OutputFormatterStyle('white', 'red', array('bold'));
$output->getFormatter()->setStyle('error', $errorStyle);

$nbErrors = 0;
foreach ($violations as $violation) {
$rule = $violation->getRule();
$node = $violation->getNode();
$errorType = RuleInterface::TYPE_DISCOURAGED === $rule->getType() ? 'warning' : 'error';

$msg = !$verbose ?
sprintf(
'Node <comment>%s</comment> does not respect the rule <comment>%s</comment> because of the tokens:',
$node->getFilepath(),
$rule->getSubject()
):
sprintf(<<<MSG
Node <comment>%s</comment> does not respect the following rule <comment>%s</comment>:
* type: %s
* description: %s
* requirements: %s
The following tokens are wrong:
MSG
,
$node->getFilepath(),
$rule->getSubject(),
$rule->getType(),
$rule->getDescription() ?: 'N/A',
implode(', ', $rule->getRequirements())
);

$output->writeln('');
$output->writeln($msg);
foreach ($violation->getTokenViolations() as $token) {
$output->writeln(sprintf(' * <%s>%s</%s>', $errorType, $token, $errorType));
}

$nbErrors += count($violation->getTokenViolations());
}

if (0 === $nbErrors) {
$output->writeln('<info>No coupling issues found :)</info>');
} else {
$output->writeln('');
$output->writeln(sprintf('<info>%d coupling issues found!</info>', $nbErrors));
}
}

/**
* @param string $filePath
*
* @return Configuration
*/
protected function loadConfiguration($filePath)
private function loadConfiguration($filePath)
{
if (!is_file($filePath)) {
throw new \InvalidArgumentException(sprintf('The configuration file "%s" does not exit', $filePath));
Expand All @@ -247,4 +214,31 @@ protected function loadConfiguration($filePath)

return $config;
}

/**
* Init the event dispatcher by attaching the output formatters.
*
* @param OutputInterface $output
* @param string $formatterName
* @param bool $verbose
*
* @return EventDispatcherInterface
*/
private function initEventDispatcher(OutputInterface $output, $formatterName, $verbose)
{
if ('dot' === $formatterName) {
$formatter = new DotFormatter($output);
} elseif ('pretty' === $formatterName) {
$formatter = new PrettyFormatter($output, $verbose);
} else {
throw new \RuntimeException(
sprintf('Format "%s" is unknown. Available formats: %s.', $formatterName, implode(', ', $this->formats))
);
}

$eventDispatcher = new EventDispatcher();
$eventDispatcher->addSubscriber($formatter);

return $eventDispatcher;
}
}
35 changes: 35 additions & 0 deletions src/Akeneo/CouplingDetector/Console/OutputFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Akeneo\CouplingDetector\Console;

use Symfony\Component\Console\Formatter\OutputFormatter as BaseOutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;

/**
* Output formatter for coupling detector.
* Just adds some styles to the Symfony output formatter.
*
* @author Julien Janvier <[email protected]>
* @license http://opensource.org/licenses/MIT MIT
*/
class OutputFormatter extends BaseOutputFormatter
{
/**
* OutputFormatter constructor.
*
* @param bool $decorated
* @param array $styles
*/
public function __construct($decorated = false, array $styles = array())
{
parent::__construct($decorated, $styles);

$this->setStyle('passed', new OutputFormatterStyle('green', null, array()));
$this->setStyle('passed-bg', new OutputFormatterStyle('black', 'green', array('bold')));

$this->setStyle('broken', new OutputFormatterStyle('red', null, array()));
$this->setStyle('broken-bg', new OutputFormatterStyle('white', 'red', array('bold')));

$this->setStyle('blink', new OutputFormatterStyle(null, null, array('blink')));
}
}
Loading

0 comments on commit c5df176

Please sign in to comment.