default services conflit ?
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Stecman\Component\Symfony\Console\BashCompletion\Tests\Common;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\CompletionHandler;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
/**
|
||||
* Base test case for running CompletionHandlers
|
||||
*/
|
||||
abstract class CompletionHandlerTestCase extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Application
|
||||
*/
|
||||
protected $application;
|
||||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
require_once __DIR__ . '/../Fixtures/CompletionAwareCommand.php';
|
||||
require_once __DIR__ . '/../Fixtures/HiddenCommand.php';
|
||||
require_once __DIR__ . '/../Fixtures/TestBasicCommand.php';
|
||||
require_once __DIR__ . '/../Fixtures/TestSymfonyStyleCommand.php';
|
||||
}
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->application = new Application('Base application');
|
||||
$this->application->addCommands(array(
|
||||
new \CompletionAwareCommand(),
|
||||
new \TestBasicCommand(),
|
||||
new \TestSymfonyStyleCommand()
|
||||
));
|
||||
|
||||
if (method_exists('\HiddenCommand', 'setHidden')) {
|
||||
$this->application->add(new \HiddenCommand());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a handler set up with the given commandline and cursor position
|
||||
*
|
||||
* @param $commandLine
|
||||
* @param int $cursorIndex
|
||||
* @return CompletionHandler
|
||||
*/
|
||||
protected function createHandler($commandLine, $cursorIndex = null)
|
||||
{
|
||||
$context = new CompletionContext();
|
||||
$context->setCommandLine($commandLine);
|
||||
$context->setCharIndex($cursorIndex === null ? strlen($commandLine) : $cursorIndex);
|
||||
|
||||
return new CompletionHandler($this->application, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of terms from the output of CompletionHandler
|
||||
* The array index needs to be reset so that PHPUnit's array equality assertions match correctly.
|
||||
*
|
||||
* @param string $handlerOutput
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getTerms($handlerOutput)
|
||||
{
|
||||
return array_values($handlerOutput);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Stecman\Component\Symfony\Console\BashCompletion\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\CompletionCommand;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\StringInput;
|
||||
use Symfony\Component\Console\Output\NullOutput;
|
||||
|
||||
class CompletionCommandTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Ensure conflicting options names and shortcuts from the application do not break the completion command
|
||||
*/
|
||||
public function testConflictingGlobalOptions()
|
||||
{
|
||||
$app = new Application('Base application');
|
||||
|
||||
// Conflicting option shortcut
|
||||
$app->getDefinition()->addOption(
|
||||
new InputOption('conflicting-shortcut', 'g', InputOption::VALUE_NONE)
|
||||
);
|
||||
|
||||
// Conflicting option name
|
||||
$app->getDefinition()->addOption(
|
||||
new InputOption('program', null, InputOption::VALUE_REQUIRED)
|
||||
);
|
||||
|
||||
$app->add(new CompletionCommand());
|
||||
|
||||
// Check completion command doesn't throw
|
||||
$app->doRun(new StringInput('_completion -g --program foo'), new NullOutput());
|
||||
$app->doRun(new StringInput('_completion --help'), new NullOutput());
|
||||
$app->doRun(new StringInput('help _completion'), new NullOutput());
|
||||
|
||||
// Check default options are available
|
||||
$app->doRun(new StringInput('_completion -V -vv --no-ansi --quiet'), new NullOutput());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace Stecman\Component\Symfony\Console\BashCompletion\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\EnvironmentCompletionContext;
|
||||
|
||||
class CompletionContextTest extends TestCase
|
||||
{
|
||||
|
||||
public function testWordBreakSplit()
|
||||
{
|
||||
$context = new CompletionContext();
|
||||
$context->setCommandLine('console config:application --direction="west" --with-bruce --repeat 3');
|
||||
|
||||
// Cursor at the end of the first word
|
||||
$context->setCharIndex(7);
|
||||
$words = $context->getWords();
|
||||
|
||||
$this->assertEquals(array(
|
||||
'console',
|
||||
'config:application',
|
||||
'--direction',
|
||||
'west',
|
||||
'--with-bruce',
|
||||
'--repeat',
|
||||
'3'
|
||||
), $words);
|
||||
}
|
||||
|
||||
public function testCursorPosition()
|
||||
{
|
||||
$context = new CompletionContext();
|
||||
$context->setCommandLine('make horse --legs 4 --colour black ');
|
||||
|
||||
// Cursor at the start of the line
|
||||
$context->setCharIndex(0);
|
||||
$this->assertEquals(0, $context->getWordIndex());
|
||||
|
||||
// Cursor at the end of the line
|
||||
$context->setCharIndex(34);
|
||||
$this->assertEquals(5, $context->getWordIndex());
|
||||
$this->assertEquals('black', $context->getCurrentWord());
|
||||
|
||||
// Cursor after space at the end of the string
|
||||
$context->setCharIndex(35);
|
||||
$this->assertEquals(6, $context->getWordIndex());
|
||||
$this->assertEquals('', $context->getCurrentWord());
|
||||
|
||||
// Cursor in the middle of 'horse'
|
||||
$context->setCharIndex(8);
|
||||
$this->assertEquals(1, $context->getWordIndex());
|
||||
$this->assertEquals('hor', $context->getCurrentWord());
|
||||
|
||||
// Cursor at the end of '--legs'
|
||||
$context->setCharIndex(17);
|
||||
$this->assertEquals(2, $context->getWordIndex());
|
||||
$this->assertEquals('--legs', $context->getCurrentWord());
|
||||
}
|
||||
|
||||
public function testWordBreakingWithSmallInputs()
|
||||
{
|
||||
$context = new CompletionContext();
|
||||
|
||||
// Cursor at the end of a word and not in the following space has no effect
|
||||
$context->setCommandLine('cmd a');
|
||||
$context->setCharIndex(5);
|
||||
$this->assertEquals(array('cmd', 'a'), $context->getWords());
|
||||
$this->assertEquals(1, $context->getWordIndex());
|
||||
$this->assertEquals('a', $context->getCurrentWord());
|
||||
|
||||
// As above, but in the middle of the command line string
|
||||
$context->setCommandLine('cmd a');
|
||||
$context->setCharIndex(3);
|
||||
$this->assertEquals(array('cmd', 'a'), $context->getWords());
|
||||
$this->assertEquals(0, $context->getWordIndex());
|
||||
$this->assertEquals('cmd', $context->getCurrentWord());
|
||||
|
||||
// Cursor at the end of the command line with a space appends an empty word
|
||||
$context->setCommandLine('cmd a ');
|
||||
$context->setCharIndex(8);
|
||||
$this->assertEquals(array('cmd', 'a', ''), $context->getWords());
|
||||
$this->assertEquals(2, $context->getWordIndex());
|
||||
$this->assertEquals('', $context->getCurrentWord());
|
||||
|
||||
// Cursor in break space before a word appends an empty word in that position
|
||||
$context->setCommandLine('cmd a');
|
||||
$context->setCharIndex(4);
|
||||
$this->assertEquals(array('cmd', '', 'a',), $context->getWords());
|
||||
$this->assertEquals(1, $context->getWordIndex());
|
||||
$this->assertEquals('', $context->getCurrentWord());
|
||||
}
|
||||
|
||||
public function testQuotedStringWordBreaking()
|
||||
{
|
||||
$context = new CompletionContext();
|
||||
$context->setCharIndex(1000);
|
||||
$context->setCommandLine('make horse --legs=3 --name="Jeff the horse" --colour Extreme\\ Blanc \'foo " bar\'');
|
||||
|
||||
// Ensure spaces and quotes are processed correctly
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'make',
|
||||
'horse',
|
||||
'--legs',
|
||||
'3',
|
||||
'--name',
|
||||
'Jeff the horse',
|
||||
'--colour',
|
||||
'Extreme Blanc',
|
||||
'foo " bar',
|
||||
'',
|
||||
),
|
||||
$context->getWords()
|
||||
);
|
||||
|
||||
// Confirm the raw versions of the words are indexed correctly
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'make',
|
||||
'horse',
|
||||
'--legs',
|
||||
'3',
|
||||
'--name',
|
||||
'"Jeff the horse"',
|
||||
'--colour',
|
||||
'Extreme\\ Blanc',
|
||||
"'foo \" bar'",
|
||||
'',
|
||||
),
|
||||
$context->getRawWords()
|
||||
);
|
||||
|
||||
$context = new CompletionContext();
|
||||
$context->setCommandLine('console --tag=');
|
||||
|
||||
// Cursor after equals symbol on option argument
|
||||
$context->setCharIndex(14);
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'console',
|
||||
'--tag',
|
||||
''
|
||||
),
|
||||
$context->getWords()
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetRawCurrentWord()
|
||||
{
|
||||
$context = new CompletionContext();
|
||||
|
||||
$context->setCommandLine('cmd "double quoted" --option \'value\'');
|
||||
$context->setCharIndex(13);
|
||||
$this->assertEquals(1, $context->getWordIndex());
|
||||
|
||||
$this->assertEquals(array('cmd', '"double q', '--option', "'value'"), $context->getRawWords());
|
||||
$this->assertEquals('"double q', $context->getRawCurrentWord());
|
||||
}
|
||||
|
||||
public function testConfigureFromEnvironment()
|
||||
{
|
||||
putenv("CMDLINE_CONTENTS=beam up li");
|
||||
putenv('CMDLINE_CURSOR_INDEX=10');
|
||||
|
||||
$context = new EnvironmentCompletionContext();
|
||||
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'beam',
|
||||
'up',
|
||||
'li'
|
||||
),
|
||||
$context->getWords()
|
||||
);
|
||||
|
||||
$this->assertEquals('li', $context->getCurrentWord());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace Stecman\Component\Symfony\Console\BashCompletion\Tests;
|
||||
|
||||
require_once __DIR__ . '/Common/CompletionHandlerTestCase.php';
|
||||
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\Completion;
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\Tests\Common\CompletionHandlerTestCase;
|
||||
|
||||
class CompletionHandlerTest extends CompletionHandlerTestCase
|
||||
{
|
||||
public function testCompleteAppName()
|
||||
{
|
||||
$handler = $this->createHandler('app');
|
||||
|
||||
// It's not valid to complete the application name, so this should return nothing
|
||||
$this->assertEmpty($handler->runCompletion());
|
||||
}
|
||||
|
||||
public function testCompleteCommandNames()
|
||||
{
|
||||
$handler = $this->createHandler('app ');
|
||||
$this->assertEquals(
|
||||
array('help', 'list', 'completion-aware', 'wave', 'walk:north'),
|
||||
$this->getTerms($handler->runCompletion())
|
||||
);
|
||||
}
|
||||
|
||||
public function testCompleteCommandNameNonMatch()
|
||||
{
|
||||
$handler = $this->createHandler('app br');
|
||||
$this->assertEmpty($handler->runCompletion());
|
||||
}
|
||||
|
||||
public function testCompleteCommandNamePartialTwoMatches()
|
||||
{
|
||||
$handler = $this->createHandler('app wa');
|
||||
$this->assertEquals(array('wave', 'walk:north'), $this->getTerms($handler->runCompletion()));
|
||||
}
|
||||
|
||||
public function testCompleteCommandNamePartialOneMatch()
|
||||
{
|
||||
$handler = $this->createHandler('app wav');
|
||||
$this->assertEquals(array('wave'), $this->getTerms($handler->runCompletion()));
|
||||
}
|
||||
|
||||
public function testCompleteCommandNameFull()
|
||||
{
|
||||
$handler = $this->createHandler('app wave');
|
||||
|
||||
// Completing on a matching word should return that word so that completion can continue
|
||||
$this->assertEquals(array('wave'), $this->getTerms($handler->runCompletion()));
|
||||
}
|
||||
|
||||
public function testCompleteSingleDash()
|
||||
{
|
||||
$handler = $this->createHandler('app wave -');
|
||||
|
||||
// Short options are not given as suggestions
|
||||
$this->assertEmpty($handler->runCompletion());
|
||||
}
|
||||
|
||||
public function testCompleteOptionShortcut()
|
||||
{
|
||||
$handler = $this->createHandler('app wave -j');
|
||||
|
||||
// If a valid option shortcut is completed on, the shortcut is returned so that completion can continue
|
||||
$this->assertEquals(array('-j'), $this->getTerms($handler->runCompletion()));
|
||||
}
|
||||
|
||||
public function testCompleteOptionShortcutFirst()
|
||||
{
|
||||
// Check command options complete
|
||||
$handler = $this->createHandler('app -v wave --');
|
||||
$this->assertArraySubset(array('--vigorous', '--jazz-hands'), $this->getTerms($handler->runCompletion()));
|
||||
|
||||
// Check unambiguous command name still completes
|
||||
$handler = $this->createHandler('app --quiet wav');
|
||||
$this->assertEquals(array('wave'), $this->getTerms($handler->runCompletion()));
|
||||
}
|
||||
|
||||
public function testCompleteDoubleDash()
|
||||
{
|
||||
$handler = $this->createHandler('app wave --');
|
||||
$this->assertArraySubset(array('--vigorous', '--jazz-hands'), $this->getTerms($handler->runCompletion()));
|
||||
}
|
||||
|
||||
public function testCompleteOptionFull()
|
||||
{
|
||||
$handler = $this->createHandler('app wave --jazz');
|
||||
$this->assertArraySubset(array('--jazz-hands'), $this->getTerms($handler->runCompletion()));
|
||||
}
|
||||
|
||||
public function testCompleteOptionEqualsValue()
|
||||
{
|
||||
// Cursor at the "=" sign
|
||||
$handler = $this->createHandler('app completion-aware --option-with-suggestions=');
|
||||
$this->assertEquals(array('one-opt', 'two-opt'), $this->getTerms($handler->runCompletion()));
|
||||
|
||||
// Cursor at an opening quote
|
||||
$handler = $this->createHandler('app completion-aware --option-with-suggestions="');
|
||||
$this->assertEquals(array('one-opt', 'two-opt'), $this->getTerms($handler->runCompletion()));
|
||||
|
||||
// Cursor inside a quote with value
|
||||
$handler = $this->createHandler('app completion-aware --option-with-suggestions="two');
|
||||
$this->assertEquals(array('two-opt'), $this->getTerms($handler->runCompletion()));
|
||||
}
|
||||
|
||||
public function testCompleteOptionOrder()
|
||||
{
|
||||
// Completion of options should be able to happen anywhere after the command name
|
||||
$handler = $this->createHandler('app wave bruce --vi');
|
||||
$this->assertEquals(array('--vigorous'), $this->getTerms($handler->runCompletion()));
|
||||
|
||||
// Completing an option mid-commandline should work as normal
|
||||
$handler = $this->createHandler('app wave --vi --jazz-hands bruce', 13);
|
||||
$this->assertEquals(array('--vigorous'), $this->getTerms($handler->runCompletion()));
|
||||
}
|
||||
|
||||
public function testCompleteColonCommand()
|
||||
{
|
||||
// Normal bash behaviour is to count the colon character as a word break
|
||||
// Since a colon is used to namespace Symfony Framework console commands the
|
||||
// character in a command name should not be taken as a word break
|
||||
//
|
||||
// @see https://github.com/stecman/symfony-console-completion/pull/1
|
||||
$handler = $this->createHandler('app walk');
|
||||
$this->assertEquals(array('walk:north'), $this->getTerms($handler->runCompletion()));
|
||||
|
||||
$handler = $this->createHandler('app walk:north');
|
||||
$this->assertEquals(array('walk:north'), $this->getTerms($handler->runCompletion()));
|
||||
|
||||
$handler = $this->createHandler('app walk:north --deploy');
|
||||
$this->assertEquals(array('--deploy:jazz-hands'), $this->getTerms($handler->runCompletion()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider completionAwareCommandDataProvider
|
||||
*/
|
||||
public function testCompletionAwareCommand($commandLine, array $suggestions)
|
||||
{
|
||||
$handler = $this->createHandler($commandLine);
|
||||
$this->assertSame($suggestions, $this->getTerms($handler->runCompletion()));
|
||||
}
|
||||
|
||||
public function completionAwareCommandDataProvider()
|
||||
{
|
||||
return array(
|
||||
'not complete aware command' => array('app wave --vigorous ', array()),
|
||||
'argument suggestions' => array('app completion-aware any-arg ', array('one-arg', 'two-arg')),
|
||||
'argument no suggestions' => array('app completion-aware ', array()),
|
||||
'argument suggestions + context' => array('app completion-aware any-arg one', array('one-arg', 'one-arg-context')),
|
||||
'array argument suggestions' => array('app completion-aware any-arg one-arg array-arg1 ', array('one-arg', 'two-arg')),
|
||||
'array argument suggestions + context' => array('app completion-aware any-arg one-arg array-arg1 one', array('one-arg', 'one-arg-context')),
|
||||
'option suggestions' => array('app completion-aware --option-with-suggestions ', array('one-opt', 'two-opt')),
|
||||
'option no suggestions' => array('app completion-aware --option-without-suggestions ', array()),
|
||||
'option suggestions + context' => array(
|
||||
'app completion-aware --option-with-suggestions one', array('one-opt', 'one-opt-context')
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function testShortCommandMatched()
|
||||
{
|
||||
$handler = $this->createHandler('app w:n --deploy');
|
||||
$this->assertEquals(array('--deploy:jazz-hands'), $this->getTerms($handler->runCompletion()));
|
||||
}
|
||||
|
||||
public function testShortCommandNotMatched()
|
||||
{
|
||||
$handler = $this->createHandler('app w --deploy');
|
||||
$this->assertEquals(array(), $this->getTerms($handler->runCompletion()));
|
||||
}
|
||||
|
||||
public function testHelpCommandCompletion()
|
||||
{
|
||||
$handler = $this->createHandler('app help ');
|
||||
$this->assertEquals(
|
||||
array('help', 'list', 'completion-aware', 'wave', 'walk:north'),
|
||||
$this->getTerms($handler->runCompletion())
|
||||
);
|
||||
}
|
||||
|
||||
public function testListCommandCompletion()
|
||||
{
|
||||
$handler = $this->createHandler('app list ');
|
||||
$this->assertEquals(
|
||||
array('walk'),
|
||||
$this->getTerms($handler->runCompletion())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace Stecman\Component\Symfony\Console\BashCompletion\Tests;
|
||||
|
||||
require_once __DIR__ . '/Common/CompletionHandlerTestCase.php';
|
||||
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\Completion;
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\Tests\Common\CompletionHandlerTestCase;
|
||||
|
||||
class CompletionTest extends CompletionHandlerTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider getCompletionTestInput
|
||||
*/
|
||||
public function testCompletionResults($completions, $commandlineResultMap)
|
||||
{
|
||||
if (!is_array($completions)) {
|
||||
$completions = array($completions);
|
||||
}
|
||||
|
||||
foreach ($commandlineResultMap as $commandLine => $result) {
|
||||
$handler = $this->createHandler($commandLine);
|
||||
$handler->addHandlers($completions);
|
||||
$this->assertEquals($result, $this->getTerms($handler->runCompletion()));
|
||||
}
|
||||
}
|
||||
|
||||
public function getCompletionTestInput()
|
||||
{
|
||||
$options = array('smooth', 'latin', 'moody');
|
||||
|
||||
return array(
|
||||
'command match' => array(
|
||||
new Completion(
|
||||
'wave',
|
||||
'target',
|
||||
Completion::ALL_TYPES,
|
||||
$options
|
||||
),
|
||||
array(
|
||||
'app walk:north --target ' => array(),
|
||||
'app wave ' => $options
|
||||
)
|
||||
),
|
||||
|
||||
'type restriction option' => array(
|
||||
new Completion(
|
||||
Completion::ALL_COMMANDS,
|
||||
'target',
|
||||
Completion::TYPE_OPTION,
|
||||
$options
|
||||
),
|
||||
array(
|
||||
'app walk:north --target ' => $options,
|
||||
'app wave ' => array()
|
||||
)
|
||||
),
|
||||
|
||||
'type restriction argument' => array(
|
||||
new Completion(
|
||||
Completion::ALL_COMMANDS,
|
||||
'target',
|
||||
Completion::TYPE_ARGUMENT,
|
||||
$options
|
||||
),
|
||||
array(
|
||||
'app walk:north --target ' => array(),
|
||||
'app wave ' => $options
|
||||
)
|
||||
),
|
||||
|
||||
'makeGlobalHandler static' => array(
|
||||
Completion::makeGlobalHandler(
|
||||
'target',
|
||||
Completion::ALL_TYPES,
|
||||
$options
|
||||
),
|
||||
array(
|
||||
'app walk:north --target ' => $options,
|
||||
'app wave ' => $options
|
||||
)
|
||||
),
|
||||
|
||||
'with anonymous function' => array(
|
||||
new Completion(
|
||||
'wave',
|
||||
'style',
|
||||
Completion::TYPE_OPTION,
|
||||
function() {
|
||||
return range(1, 5);
|
||||
}
|
||||
),
|
||||
array(
|
||||
'app walk:north --target ' => array(),
|
||||
'app wave ' => array(),
|
||||
'app wave --style ' => array(1, 2,3, 4, 5)
|
||||
)
|
||||
),
|
||||
|
||||
'with callable array' => array(
|
||||
new Completion(
|
||||
Completion::ALL_COMMANDS,
|
||||
'target',
|
||||
Completion::ALL_TYPES,
|
||||
array($this, 'instanceMethodForCallableCheck')
|
||||
),
|
||||
array(
|
||||
'app walk:north --target ' => array('hello', 'world'),
|
||||
'app wave ' => array('hello', 'world')
|
||||
)
|
||||
),
|
||||
|
||||
'multiple handlers' => array(
|
||||
array(
|
||||
new Completion(
|
||||
Completion::ALL_COMMANDS,
|
||||
'target',
|
||||
Completion::TYPE_OPTION,
|
||||
array('all:option:target')
|
||||
),
|
||||
new Completion(
|
||||
Completion::ALL_COMMANDS,
|
||||
'target',
|
||||
Completion::ALL_TYPES,
|
||||
array('all:all:target')
|
||||
),
|
||||
new Completion(
|
||||
Completion::ALL_COMMANDS,
|
||||
'style',
|
||||
Completion::TYPE_OPTION,
|
||||
array('all:option:style')
|
||||
),
|
||||
),
|
||||
array(
|
||||
'app walk:north ' => array(),
|
||||
'app walk:north -t ' => array('all:option:target'),
|
||||
'app wave ' => array('all:all:target'),
|
||||
'app wave bruce -s ' => array('all:option:style'),
|
||||
'app walk:north --style ' => array('all:option:style'),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in the test "with callable array"
|
||||
* @return array
|
||||
*/
|
||||
public function instanceMethodForCallableCheck()
|
||||
{
|
||||
return array('hello', 'world');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface;
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class CompletionAwareCommand extends Command implements CompletionAwareInterface
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('completion-aware')
|
||||
->addOption('option-with-suggestions', null, InputOption::VALUE_REQUIRED)
|
||||
->addOption('option-without-suggestions', null, InputOption::VALUE_REQUIRED)
|
||||
->addArgument('argument-without-suggestions')
|
||||
->addArgument('argument-with-suggestions')
|
||||
->addArgument('array-argument-with-suggestions', InputArgument::IS_ARRAY)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns possible option values.
|
||||
*
|
||||
* @param string $optionName Option name.
|
||||
* @param CompletionContext $context Completion context.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function completeOptionValues($optionName, CompletionContext $context)
|
||||
{
|
||||
if ($optionName === 'option-with-suggestions') {
|
||||
$suggestions = array('one-opt', 'two-opt');
|
||||
|
||||
if ('one' === $context->getCurrentWord()) {
|
||||
$suggestions[] = 'one-opt-context';
|
||||
}
|
||||
|
||||
return $suggestions;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns possible argument values.
|
||||
*
|
||||
* @param string $argumentName Argument name.
|
||||
* @param CompletionContext $context Completion context.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function completeArgumentValues($argumentName, CompletionContext $context)
|
||||
{
|
||||
if (in_array($argumentName, array('argument-with-suggestions', 'array-argument-with-suggestions'))) {
|
||||
$suggestions = array('one-arg', 'two-arg');
|
||||
|
||||
if ('one' === $context->getCurrentWord()) {
|
||||
$suggestions[] = 'one-arg-context';
|
||||
}
|
||||
|
||||
return $suggestions;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
|
||||
class HiddenCommand extends \Symfony\Component\Console\Command\Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('internals')
|
||||
->setHidden(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
class TestBasicCommand extends \Symfony\Component\Console\Command\Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('wave')
|
||||
->addOption(
|
||||
'vigorous'
|
||||
)
|
||||
->addOption(
|
||||
'jazz-hands',
|
||||
'j'
|
||||
)
|
||||
->addOption(
|
||||
'style',
|
||||
's',
|
||||
\Symfony\Component\Console\Input\InputOption::VALUE_REQUIRED
|
||||
)
|
||||
->addArgument(
|
||||
'target',
|
||||
\Symfony\Component\Console\Input\InputArgument::REQUIRED
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
class TestSymfonyStyleCommand extends \Symfony\Component\Console\Command\Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('walk:north')
|
||||
->addOption(
|
||||
'power',
|
||||
'p'
|
||||
)
|
||||
->addOption(
|
||||
'deploy:jazz-hands',
|
||||
'j'
|
||||
)
|
||||
->addOption(
|
||||
'style',
|
||||
's',
|
||||
\Symfony\Component\Console\Input\InputOption::VALUE_REQUIRED
|
||||
)
|
||||
->addOption(
|
||||
'target',
|
||||
't',
|
||||
\Symfony\Component\Console\Input\InputOption::VALUE_REQUIRED
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace Stecman\Component\Symfony\Console\BashCompletion\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\HookFactory;
|
||||
|
||||
class HookFactoryTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var HookFactory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->factory = new HookFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateHookDataProvider
|
||||
*/
|
||||
public function testBashSyntax($programPath, $programName, $multiple)
|
||||
{
|
||||
if ($this->hasProgram('bash')) {
|
||||
$script = $this->factory->generateHook('bash', $programPath, $programName, $multiple);
|
||||
$this->assertSyntaxIsValid($script, 'bash -n', 'BASH hook');
|
||||
} else {
|
||||
$this->markTestSkipped("Couldn't detect BASH program to run hook syntax check");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateHookDataProvider
|
||||
*/
|
||||
public function testZshSyntax($programPath, $programName, $multiple)
|
||||
{
|
||||
if ($this->hasProgram('zsh')) {
|
||||
$script = $this->factory->generateHook('zsh', $programPath, $programName, $multiple);
|
||||
$this->assertSyntaxIsValid($script, 'zsh -n', 'ZSH hook');
|
||||
} else {
|
||||
$this->markTestSkipped("Couldn't detect ZSH program to run hook syntax check");
|
||||
}
|
||||
}
|
||||
|
||||
public function generateHookDataProvider()
|
||||
{
|
||||
return array(
|
||||
array('/path/to/myprogram', null, false),
|
||||
array('/path/to/myprogram', null, true),
|
||||
array('/path/to/myprogram', 'myprogram', false),
|
||||
array('/path/to/myprogram', 'myprogram', true),
|
||||
array('/path/to/my-program', 'my-program', false)
|
||||
);
|
||||
}
|
||||
|
||||
public function testForMissingSemiColons()
|
||||
{
|
||||
$class = new \ReflectionClass('Stecman\Component\Symfony\Console\BashCompletion\HookFactory');
|
||||
$properties = $class->getStaticProperties();
|
||||
$hooks = $properties['hooks'];
|
||||
|
||||
// Check each line is commented or closed correctly to be collapsed for eval
|
||||
foreach ($hooks as $shellType => $hook) {
|
||||
$line = strtok($hook, "\n");
|
||||
$lineNumber = 0;
|
||||
|
||||
while ($line !== false) {
|
||||
$lineNumber++;
|
||||
|
||||
if (!$this->isScriptLineValid($line)) {
|
||||
$this->fail("$shellType hook appears to be missing a semicolon on line $lineNumber:\n> $line");
|
||||
}
|
||||
|
||||
$line = strtok("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a line of shell script is safe to be collapsed to one line for eval
|
||||
*/
|
||||
protected function isScriptLineValid($line)
|
||||
{
|
||||
if (preg_match('/^\s*#/', $line)) {
|
||||
// Line is commented out
|
||||
return true;
|
||||
}
|
||||
|
||||
if (preg_match('/[;\{\}]\s*$/', $line)) {
|
||||
// Line correctly ends with a semicolon or syntax
|
||||
return true;
|
||||
}
|
||||
|
||||
if (preg_match('
|
||||
/(
|
||||
;\s*then |
|
||||
\s*else
|
||||
)
|
||||
\s*$
|
||||
/x', $line)
|
||||
) {
|
||||
// Line ends with another permitted sequence
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function hasProgram($programName)
|
||||
{
|
||||
exec(sprintf(
|
||||
'command -v %s',
|
||||
escapeshellarg($programName)
|
||||
), $output, $return);
|
||||
|
||||
return $return === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code - code to pipe to the syntax checking command
|
||||
* @param string $syntaxCheckCommand - equivalent to `bash -n`.
|
||||
* @param string $context - what the syntax check is for
|
||||
*/
|
||||
protected function assertSyntaxIsValid($code, $syntaxCheckCommand, $context)
|
||||
{
|
||||
$process = proc_open(
|
||||
escapeshellcmd($syntaxCheckCommand),
|
||||
array(
|
||||
0 => array('pipe', 'r'),
|
||||
1 => array('pipe', 'w'),
|
||||
2 => array('pipe', 'w')
|
||||
),
|
||||
$pipes
|
||||
);
|
||||
|
||||
if (is_resource($process)) {
|
||||
// Push code into STDIN for the syntax checking process
|
||||
fwrite($pipes[0], $code);
|
||||
fclose($pipes[0]);
|
||||
|
||||
$output = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]);
|
||||
fclose($pipes[1]);
|
||||
fclose($pipes[2]);
|
||||
|
||||
$status = proc_close($process);
|
||||
|
||||
$this->assertSame(0, $status, "Syntax check for $context failed:\n$output");
|
||||
} else {
|
||||
throw new \RuntimeException("Failed to start process with command '$syntaxCheckCommand'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
$filename = __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (!file_exists($filename)) {
|
||||
echo 'You must first install the vendors using composer.' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require_once $filename;
|
||||
Reference in New Issue
Block a user