Page Menu
Home
In-Portal Phabricator
Search
Configure Global Search
Log In
Files
F1098719
D146.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Thu, Aug 14, 4:49 AM
Size
14 KB
Mime Type
text/x-diff
Expires
Fri, Aug 15, 4:49 AM (7 h, 1 m)
Engine
blob
Format
Raw Data
Handle
711423
Attached To
D146: INP-1494 - Create "phing:run" console command
D146.diff
View Options
Index: core/install/cache/class_structure.php
===================================================================
--- core/install/cache/class_structure.php
+++ core/install/cache/class_structure.php
@@ -87,11 +87,13 @@
'InPortal\\Core\\kernel\\Console\\Command\\IConsoleCommand' => '/core/kernel/Console/Command/IConsoleCommand.php',
'InPortal\\Core\\kernel\\Console\\Command\\ResetCacheCommand' => '/core/kernel/Console/Command/ResetCacheCommand.php',
'InPortal\\Core\\kernel\\Console\\Command\\RunEventCommand' => '/core/kernel/Console/Command/RunEventCommand.php',
+ 'InPortal\\Core\\kernel\\Console\\Command\\RunPhingCommand' => '/core/kernel/Console/Command/RunPhingCommand.php',
'InPortal\\Core\\kernel\\Console\\Command\\RunScheduledTaskCommand' => '/core/kernel/Console/Command/RunScheduledTaskCommand.php',
'InPortal\\Core\\kernel\\Console\\ConsoleApplication' => '/core/kernel/Console/ConsoleApplication.php',
'InPortal\\Core\\kernel\\Console\\ConsoleCommandProvider' => '/core/kernel/Console/ConsoleCommandProvider.php',
'InPortal\\Core\\kernel\\Console\\ConsoleIO' => '/core/kernel/Console/ConsoleIO.php',
'InPortal\\Core\\kernel\\Console\\IConsoleCommandProvider' => '/core/kernel/Console/IConsoleCommandProvider.php',
+ 'InPortal\\Core\\kernel\\Console\\PhingCache' => '/core/kernel/Console/PhingCache.php',
'InPortal\\Core\\kernel\\utility\\ClassDiscovery\\ClassDetector' => '/core/kernel/utility/ClassDiscovery/ClassDetector.php',
'InPortal\\Core\\kernel\\utility\\ClassDiscovery\\ClassMapBuilder' => '/core/kernel/utility/ClassDiscovery/ClassMapBuilder.php',
'InPortal\\Core\\kernel\\utility\\ClassDiscovery\\CodeFolderFilterIterator' => '/core/kernel/utility/ClassDiscovery/CodeFolderFilterIterator.php',
@@ -882,6 +884,13 @@
0 => 'InPortal\\Core\\kernel\\Console\\Command\\AbstractCommand',
),
),
+ 'InPortal\\Core\\kernel\\Console\\Command\\RunPhingCommand' => array(
+ 'type' => 1,
+ 'modifiers' => 0,
+ 'extends' => array(
+ 0 => 'InPortal\\Core\\kernel\\Console\\Command\\AbstractCommand',
+ ),
+ ),
'InPortal\\Core\\kernel\\Console\\Command\\RunScheduledTaskCommand' => array(
'type' => 1,
'modifiers' => 0,
@@ -911,6 +920,13 @@
'InPortal\\Core\\kernel\\Console\\IConsoleCommandProvider' => array(
'type' => 2,
),
+ 'InPortal\\Core\\kernel\\Console\\PhingCache' => array(
+ 'type' => 1,
+ 'modifiers' => 0,
+ 'extends' => array(
+ 0 => 'kBase',
+ ),
+ ),
'InPortal\\Core\\kernel\\utility\\ClassDiscovery\\ClassDetector' => array(
'type' => 1,
'modifiers' => 0,
Index: core/kernel/Console/Command/CompletionCommand.php
===================================================================
--- core/kernel/Console/Command/CompletionCommand.php
+++ core/kernel/Console/Command/CompletionCommand.php
@@ -17,6 +17,7 @@
use InPortal\Core\kernel\Console\ConsoleApplication;
use Stecman\Component\Symfony\Console\BashCompletion\Completion;
+use Stecman\Component\Symfony\Console\BashCompletion\Completion\ShellPathCompletion;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionCommand as BashCompletionCommand;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionHandler;
@@ -66,6 +67,14 @@
*/
protected function configureCompletion(CompletionHandler $handler)
{
+ $handler->addHandler(
+ new ShellPathCompletion(
+ 'phing:run',
+ 'path',
+ Completion::TYPE_ARGUMENT
+ )
+ );
+
// This can be removed once https://github.com/stecman/symfony-console-completion v0.5.2 will be released.
$handler->addHandler(
new Completion(
Index: core/kernel/Console/Command/RunPhingCommand.php
===================================================================
--- /dev/null
+++ core/kernel/Console/Command/RunPhingCommand.php
@@ -0,0 +1,347 @@
+<?php
+/**
+* @version $Id$
+* @package In-Portal
+* @copyright Copyright (C) 1997 - 2015 Intechnic. All rights reserved.
+* @license GNU/GPL
+* In-Portal is Open Source software.
+* This means that this software may have been modified pursuant
+* the GNU General Public License, and as distributed it includes
+* or is derivative of works licensed under the GNU General Public License
+* or other free or open source software licenses.
+* See http://www.in-portal.org/license for copyright notices and details.
+*/
+
+namespace InPortal\Core\kernel\Console\Command;
+
+
+use InPortal\Core\kernel\Console\PhingCache;
+use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+defined('FULL_PATH') or die('restricted access!');
+
+class RunPhingCommand extends AbstractCommand
+{
+
+ /**
+ * Phing cache.
+ *
+ * @var PhingCache
+ */
+ protected $phingCache;
+
+ /**
+ * Phing process builder.
+ *
+ * @var \ProcessBuilder
+ */
+ protected $phing;
+
+ /**
+ * Configures the current command.
+ *
+ * @return void
+ */
+ protected function configure()
+ {
+ $this
+ ->setName('phing:run')
+ ->setDescription('Executes/lists Phing targets')
+ ->addArgument(
+ 'target',
+ InputArgument::OPTIONAL,
+ 'Build target (e.g. "<info>build</info>")'
+ )
+ ->addArgument(
+ 'path',
+ InputArgument::OPTIONAL,
+ 'Path to limit target activity with'
+ );
+ }
+
+ /**
+ * Initializes dependencies.
+ *
+ * @return void
+ */
+ protected function initDependencies()
+ {
+ parent::initDependencies();
+
+ $this->phingCache = $this->Application->makeClass('InPortal\\Core\\kernel\\Console\\PhingCache');
+ $this->phing = $this->createPhing();
+ }
+
+ /**
+ * Executes the current command.
+ *
+ * @param InputInterface $input An InputInterface instance.
+ * @param OutputInterface $output An OutputInterface instance.
+ *
+ * @return null|integer
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $target = $this->io->getArgument('target');
+
+ if ( $target !== null ) {
+ return $this->executePhingTarget($target);
+ }
+
+ return $this->listPhingTargets();
+ }
+
+ /**
+ * Executes specific Phing target.
+ *
+ * @param string $target Target.
+ *
+ * @return integer
+ */
+ protected function executePhingTarget($target)
+ {
+ if ( !array_key_exists($target, $this->getTargets()) ) {
+ throw new \RuntimeException('The "' . $target . '" target is unknown.');
+ }
+
+ $path = $this->io->getArgument('path');
+ $phing = $this->getPhing()->add($target);
+
+ if ( $path ) {
+ $absolute_path = realpath($path);
+ $relative_path = $this->getRelativePath($absolute_path);
+
+ if ( $relative_path === $absolute_path ) {
+ throw new \RuntimeException('The "path" argument must be relative to root In-Portal directory.');
+ }
+
+ $phing->add('-Dscan.dir=${base.dir}' . $relative_path);
+ }
+
+ $io = $this->io;
+ $process = $phing->build();
+ $process->run(function ($type, $data) use ($io) {
+ $io->write($data);
+ });
+
+ return $process->getExitCode();
+ }
+
+ /**
+ * Lists Phing targets.
+ *
+ * @return integer
+ */
+ protected function listPhingTargets()
+ {
+ $build_file = $this->getRelativePath($this->getBuildFile());
+ $this->io->writeln(array('Phing targets (build file: <info>' . $build_file . '</info>):', ''));
+
+ $table = new Table($this->io->getOutput());
+ $table->setHeaders(array('Name', 'Description'));
+
+ foreach ( $this->getTargets() as $name => $description ) {
+ $table->addRow(array($name, $description));
+ }
+
+ $table->render();
+
+ return 0;
+ }
+
+ /**
+ * Return possible values for the named argument.
+ *
+ * @param string $argumentName Argument name.
+ * @param CompletionContext $context Completion context.
+ *
+ * @return array
+ */
+ public function completeArgumentValues($argumentName, CompletionContext $context)
+ {
+ $suggestions = parent::completeArgumentValues($argumentName, $context);
+
+ if ( $argumentName === 'target' ) {
+ return array_keys($this->getTargets());
+ }
+
+ return $suggestions;
+ }
+
+ /**
+ * Create process builder for executing Phing commands.
+ *
+ * @return \ProcessBuilder
+ * @throws \RuntimeException When "phing" executable not found in PATH.
+ */
+ protected function createPhing()
+ {
+ if ( !$this->isExecutableInPath('phing') ) {
+ throw new \RuntimeException('Unable to locate "phing" executable in PATH.');
+ }
+
+ /** @var \ProcessBuilder $phing */
+ $phing = $this->Application->makeClass('ProcessBuilder');
+ $phing->setExecutable('phing');
+
+ // The "$this->io" isn't set during auto-complete.
+ if ( isset($this->io) && $this->io->isDecorated() ) {
+ $phing->add('-logger')->add('phing.listener.AnsiColorLogger');
+ }
+
+ $build_file = $this->getBuildFile();
+
+ if ( $build_file ) {
+ $phing->add('-f')->add($build_file);
+ }
+
+ return $phing;
+ }
+
+ /**
+ * Forks Phing process builder.
+ *
+ * @return \ProcessBuilder
+ */
+ protected function getPhing()
+ {
+ return clone $this->phing;
+ }
+
+ /**
+ * Returns possible task names.
+ *
+ * @return array
+ */
+ protected function getTargets()
+ {
+ $targets = $this->phingCache->getTargets();
+
+ if ( !$targets ) {
+ $targets = $this->parseTargets();
+ $this->phingCache->setTargets($targets);
+ }
+
+ return $targets;
+ }
+
+ /**
+ * Returns possible task names.
+ *
+ * @return array
+ */
+ protected function parseTargets()
+ {
+ $process = $this->getPhing()->add('-l')->build();
+ $process->mustRun();
+
+ $lines = explode(PHP_EOL, $process->getOutput());
+
+ $headings = array(
+ 'Default target:',
+ 'Main targets:',
+ 'Subtargets:',
+ );
+
+ $targets = array();
+ $current_heading = '';
+
+ foreach ( $lines as $line ) {
+ if ( $line === '' ) {
+ $current_heading = '';
+ continue;
+ }
+ elseif ( $line === '-------------------------------------------------------------------------------' ) {
+ continue;
+ }
+
+ if ( in_array($line, $headings) ) {
+ $current_heading = $line;
+ $targets[$current_heading] = array();
+
+ continue;
+ }
+
+ if ( $current_heading ) {
+ list ($target, $description) = explode(' ', $line, 2);
+
+ // Only add targets from topmost project.
+ if ( strpos($target, '.') === false ) {
+ $targets[$current_heading][trim($target)] = trim($description);
+ }
+ }
+ }
+
+ return $targets['Main targets:'];
+ }
+
+ /**
+ * Checks if executable is in PATH.
+ *
+ * @param string $executable Executable.
+ *
+ * @return boolean
+ */
+ protected function isExecutableInPath($executable)
+ {
+ /** @var \ProcessBuilder $process_builder */
+ $process_builder = $this->Application->makeClass('ProcessBuilder');
+
+ $process = $process_builder->setExecutable('which')->add($executable)->build();
+ $process->run();
+
+ return $process->isSuccessful();
+ }
+
+ /**
+ * Allows user to choose build file.
+ *
+ * @return string
+ * @throws \LogicException When no suitable build files found.
+ */
+ protected function getBuildFile()
+ {
+ $build_file = $this->phingCache->getBuildFile();
+
+ // The "$this->io" isn't set during auto-complete.
+ if ( !$build_file && isset($this->io) ) {
+ $build_file_folder = FULL_PATH . '/tools/build';
+ $build_files = glob($build_file_folder . '/*.xml');
+
+ if ( !$build_files ) {
+ throw new \LogicException('No build files found in "' . $build_file_folder . '" directory.');
+ }
+
+ foreach ( $build_files as $index => $build_file ) {
+ $build_files[$index] = $this->getRelativePath($build_file);
+ }
+
+ $build_file = $this->io->choose('Please choose build file:', $build_files, null, 'Incorrect build file');
+
+ $this->phingCache->setBuildFile($build_file);
+ }
+
+ if ( $build_file ) {
+ return FULL_PATH . $build_file;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns relative path to In-Portal root folder.
+ *
+ * @param string $absolute_path Absolute path.
+ *
+ * @return string
+ */
+ protected function getRelativePath($absolute_path)
+ {
+ return preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $absolute_path, 1);
+ }
+
+}
Index: core/kernel/Console/PhingCache.php
===================================================================
--- /dev/null
+++ core/kernel/Console/PhingCache.php
@@ -0,0 +1,133 @@
+<?php
+/**
+* @version $Id$
+* @package In-Portal
+* @copyright Copyright (C) 1997 - 2015 Intechnic. All rights reserved.
+* @license GNU/GPL
+* In-Portal is Open Source software.
+* This means that this software may have been modified pursuant
+* the GNU General Public License, and as distributed it includes
+* or is derivative of works licensed under the GNU General Public License
+* or other free or open source software licenses.
+* See http://www.in-portal.org/license for copyright notices and details.
+*/
+
+namespace InPortal\Core\kernel\Console;
+
+
+class PhingCache extends \kBase
+{
+
+ /**
+ * Cache file.
+ *
+ * @var string
+ */
+ protected $cacheFile;
+
+ /**
+ * Cache data.
+ *
+ * @var array
+ */
+ protected $cacheData = array();
+
+ /**
+ * Creates class instance.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+
+ $this->cacheFile = WRITEABLE . '/phing_settings.json';
+ $this->cacheData = $this->read();
+ }
+
+ /**
+ * Returns path to build file.
+ *
+ * @return string
+ */
+ public function getBuildFile()
+ {
+ $build_file = isset($this->cacheData['build_file']) ? $this->cacheData['build_file'] : '';
+
+ if ( $build_file && file_exists(FULL_PATH . $build_file) ) {
+ return $build_file;
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets build file.
+ *
+ * @param string $build_file Build file.
+ *
+ * @return void
+ */
+ public function setBuildFile($build_file)
+ {
+ $this->cacheData['build_file'] = $build_file;
+
+ // Build file change > force target re-parsing.
+ $this->setTargets(array());
+ $this->store();
+ }
+
+ /**
+ * Returns supported targets.
+ *
+ * @return array
+ */
+ public function getTargets()
+ {
+ return isset($this->cacheData['targets']) ? $this->cacheData['targets'] : array();
+ }
+
+ /**
+ * Sets targets.
+ *
+ * @param array $targets Targets.
+ *
+ * @return void
+ */
+ public function setTargets(array $targets)
+ {
+ $this->cacheData['targets'] = $targets;
+ $this->store();
+ }
+
+ /**
+ * Returns build file from cache.
+ *
+ * @return string
+ * @throws \LogicException When cache file format can't be recognized.
+ */
+ protected function read()
+ {
+ if ( !file_exists($this->cacheFile) ) {
+ return array();
+ }
+
+ $content = json_decode(file_get_contents($this->cacheFile), true);
+
+ if ( $content === null ) {
+ throw new \LogicException('The "' . $this->cacheFile . '" file format can\'t be recognized.');
+ }
+
+ return $content;
+ }
+
+ /**
+ * Stores new cache content.
+ *
+ * @return void
+ */
+ protected function store()
+ {
+ $json_encode_options = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0;
+ file_put_contents($this->cacheFile, json_encode($this->cacheData, $json_encode_options));
+ }
+
+}
Event Timeline
Log In to Comment