--- /dev/null
+<?php
+
+/*
+ * This file is part of the Behat Testwork.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Testwork\Output\Cli;
+
+use Behat\Testwork\Cli\Controller;
+use Behat\Testwork\Output\Formatter;
+use Behat\Testwork\Output\OutputManager;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Configures formatters based on user input.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class OutputController implements Controller
+{
+ /**
+ * @var OutputManager
+ */
+ private $manager;
+
+ /**
+ * Initializes controller.
+ *
+ * @param OutputManager $manager
+ */
+ public function __construct(OutputManager $manager)
+ {
+ $this->manager = $manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function configure(Command $command)
+ {
+ $command
+ ->addOption(
+ '--format', '-f', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
+ 'How to format tests output. <comment>pretty</comment> is default.' . PHP_EOL .
+ 'Available formats are:' . PHP_EOL . $this->getFormatterDescriptions() .
+ 'You can use multiple formats at the same time.'
+ )
+ ->addOption(
+ '--out', '-o', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
+ 'Write format output to a file/directory instead of' . PHP_EOL .
+ 'STDOUT <comment>(output_path)</comment>. You can also provide different' . PHP_EOL .
+ 'outputs to multiple formats.'
+ )
+ ->addOption(
+ '--format-settings', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
+ 'Set formatters parameters using json object.' . PHP_EOL .
+ 'Keys are parameter names, values are values.'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute(InputInterface $input, OutputInterface $output)
+ {
+ $formats = $input->getOption('format');
+ $outputs = $input->getOption('out');
+
+ $this->configureFormatters($formats, $input, $output);
+ $this->configureOutputs($formats, $outputs, $output->isDecorated());
+ }
+
+ /**
+ * Configures formatters based on container, input and output configurations.
+ *
+ * @param array $formats
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ */
+ protected function configureFormatters(array $formats, InputInterface $input, OutputInterface $output)
+ {
+ $this->enableFormatters($formats);
+ $this->setFormattersParameters($input, $output);
+ }
+
+ /**
+ * Enables formatters.
+ *
+ * @param array $formats
+ */
+ protected function enableFormatters(array $formats)
+ {
+ if (count($formats)) {
+ $this->manager->disableAllFormatters();
+ foreach ($formats as $format) {
+ $this->manager->enableFormatter($format);
+ }
+ }
+ }
+
+ /**
+ * Sets formatters parameters based on input & output.
+ *
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ */
+ protected function setFormattersParameters(InputInterface $input, OutputInterface $output)
+ {
+ $this->manager->setFormattersParameter('output_decorate', $output->isDecorated());
+
+ if ($input->getOption('format-settings')) {
+ foreach ($input->getOption('format-settings') as $jsonSettings) {
+ $this->loadJsonSettings($jsonSettings);
+ }
+ }
+ }
+
+ /**
+ * Locates output path from relative one.
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ protected function locateOutputPath($path)
+ {
+ if ($this->isAbsolutePath($path)) {
+ return $path;
+ }
+
+ $path = getcwd() . DIRECTORY_SEPARATOR . $path;
+
+ if (!file_exists($path)) {
+ touch($path);
+ $path = realpath($path);
+ unlink($path);
+ } else {
+ $path = realpath($path);
+ }
+
+ return $path;
+ }
+
+ /**
+ * Initializes multiple formatters with different outputs.
+ *
+ * @param array $formats
+ * @param array $outputs
+ * @param Boolean $decorated
+ */
+ private function configureOutputs(array $formats, array $outputs, $decorated = false)
+ {
+ if (1 == count($outputs) && !$this->isStandardOutput($outputs[0])) {
+ $outputPath = $this->locateOutputPath($outputs[0]);
+
+ $this->manager->setFormattersParameter('output_path', $outputPath);
+ $this->manager->setFormattersParameter('output_decorate', $decorated);
+
+ return;
+ }
+
+ foreach ($outputs as $i => $out) {
+ if ($this->isStandardOutput($out)) {
+ continue;
+ }
+
+ $outputPath = $this->locateOutputPath($out);
+ if (isset($formats[$i])) {
+ $this->manager->setFormatterParameter($formats[$i], 'output_path', $outputPath);
+ $this->manager->setFormatterParameter($formats[$i], 'output_decorate', $decorated);
+ }
+ }
+ }
+
+ /**
+ * Checks if provided output identifier represents standard output.
+ *
+ * @param string $outputId
+ *
+ * @return Boolean
+ */
+ private function isStandardOutput($outputId)
+ {
+ return 'std' === $outputId || 'null' === $outputId || 'false' === $outputId;
+ }
+
+ /**
+ * Returns whether the file path is an absolute path.
+ *
+ * @param string $file A file path
+ *
+ * @return Boolean
+ */
+ private function isAbsolutePath($file)
+ {
+ if ($file[0] == '/' || $file[0] == '\\'
+ || (strlen($file) > 3 && ctype_alpha($file[0])
+ && $file[1] == ':'
+ && ($file[2] == '\\' || $file[2] == '/')
+ )
+ || null !== parse_url($file, PHP_URL_SCHEME)
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns formatters description.
+ *
+ * @return string
+ */
+ private function getFormatterDescriptions()
+ {
+ return implode(
+ PHP_EOL,
+ array_map(
+ function (Formatter $formatter) {
+ $comment = '- <comment>' . $formatter->getName() . '</comment>: ';
+ $comment .= $formatter->getDescription();
+
+ return $comment;
+ }, $this->manager->getFormatters()
+ )
+ ) . PHP_EOL;
+ }
+
+ /**
+ * Loads JSON settings as formatter parameters.
+ *
+ * @param string $jsonSettings
+ */
+ private function loadJsonSettings($jsonSettings)
+ {
+ $settings = @json_decode($jsonSettings, true);
+
+ if (!is_array($settings)) {
+ return;
+ }
+
+ foreach ($settings as $name => $value) {
+ $this->manager->setFormattersParameter($name, $value);
+ }
+ }
+}