Skip to content

Commit

Permalink
Ensure additional flags & options are forwarded (#106)
Browse files Browse the repository at this point in the history

Closes #65
  • Loading branch information
theofidry authored Jul 10, 2022
1 parent 468af9d commit a522fc2
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 60 deletions.
1 change: 1 addition & 0 deletions e2e/scenario7/expected.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
Cannot update only a partial set of packages without a lock file present. Run `composer update` to generate a lock file.
[bamarni-bin] Changed current directory to /path/to/project/e2e/scenario7.
–––––––––––––––––––––
[bamarni-bin] Checking namespace vendor-bin/ns1
No dependencies installed. Try running composer install or update.
31 changes: 22 additions & 9 deletions e2e/scenario8/expected.txt
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
Loading composer repositories with package information
Updating dependencies
Dependency resolution completed in 0.000 seconds
Analyzed 90 packages to resolve dependencies
Analyzed 90 rules to resolve dependencies
Dependency resolution completed in 0.000 seconds
Lock file operations: 1 install, 0 updates, 0 removals
Installs: bamarni/composer-bin-plugin:dev-hash
- Locking bamarni/composer-bin-plugin (dev-hash)
Writing lock file
Installing dependencies from lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
Installs: bamarni/composer-bin-plugin:dev-hash
- Installing bamarni/composer-bin-plugin (dev-hash): Symlinking from ../..
Generating autoload files
> post-autoload-dump: Bamarni\Composer\Bin\Plugin->onPostAutoloadDump
[bamarni-bin] Calling onPostAutoloadDump().
[bamarni-bin] The command is being forwarded.
[bamarni-bin] Original input: update --prefer-lowest --verbose.
[bamarni-bin] Current working directory: /path/to/project/e2e/scenario8
[bamarni-bin] Configuring bin directory to /path/to/project/e2e/scenario8/vendor/bin.
[bamarni-bin] Checking namespace vendor-bin/ns1
[bamarni-bin] Changed current directory to vendor-bin/ns1.
[bamarni-bin] Running `@composer update --prefer-lowest --verbose --working-dir='.'`.
Loading composer repositories with package information
Updating dependencies
Lock file operations: 2 installs, 0 updates, 0 removals
- Locking nikic/iter (v1.6.0)
- Locking phpstan/phpstan (1.8.0)
Dependency resolution completed in 0.000 seconds
Analyzed 90 packages to resolve dependencies
Analyzed 90 rules to resolve dependencies
Nothing to modify in lock file
Writing lock file
Installing dependencies from lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing nikic/iter (v1.6.0): Extracting archive
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating autoload files
––––––––––––––
nikic/iter
[bamarni-bin] Changed current directory to /path/to/project/e2e/scenario8.
4 changes: 1 addition & 3 deletions e2e/scenario8/script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,4 @@ rm -rf vendor-bin/*/composer.lock || true
rm -rf vendor-bin/*/vendor || true

# Actual command to execute the test itself
composer update --no-dev 2>&1 | tee > actual.txt || true
echo "––––––––––––––" >> actual.txt
composer bin ns1 show --direct --name-only 2>&1 | tee >> actual.txt || true
composer update --prefer-lowest --verbose 2>&1 | tee >> actual.txt || true
9 changes: 1 addition & 8 deletions e2e/scenario8/vendor-bin/ns1/composer.json
Original file line number Diff line number Diff line change
@@ -1,8 +1 @@
{
"require": {
"nikic/iter": "v1.6.0"
},
"require-dev": {
"phpstan/phpstan": "1.8.0"
}
}
{}
8 changes: 4 additions & 4 deletions src/BinCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ protected function configure(): void
{
$this
->setDescription('Run a command inside a bin namespace')
->setDefinition([
new InputArgument(self::NAMESPACE_ARG, InputArgument::REQUIRED),
new InputArgument('args', InputArgument::REQUIRED | InputArgument::IS_ARRAY),
])
->addArgument(
self::NAMESPACE_ARG,
InputArgument::REQUIRED
)
->ignoreValidationErrors();
}

Expand Down
71 changes: 63 additions & 8 deletions src/BinInputFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,84 @@

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StringInput;
use function array_filter;
use function array_map;
use function implode;
use function preg_match;
use function preg_quote;
use function preg_replace;
use function sprintf;

final class BinInputFactory
{
/**
* Extracts the input to execute in the bin namespace.
*
* For example: `bin namespace-name update --prefer-lowest` => `update --prefer-lowest`
*
* Note that no input definition is bound in the resulting input.
*/
public static function createInput(
string $namespace,
InputInterface $previousInput
): InputInterface {
return new StringInput(
preg_replace(
sprintf('/bin\s+(--ansi\s)?%s(\s.+)/', preg_quote($namespace, '/')),
'$1$2',
(string) $previousInput,
1
$matchResult = preg_match(
sprintf(
'/^(?<preBinOptions>.+)?bin (?:(?<preBinOptions2>.+?) )?(?:%1$s|\'%1$s\') (?<binCommand>.+?)(?<extraInput> -- .*)?$/',
preg_quote($namespace, '/')
),
(string) $previousInput,
$matches
);

if (1 !== $matchResult) {
throw InvalidBinInput::forBinInput($previousInput);
}

$inputParts = array_filter(
array_map(
'trim',
[
$matches['binCommand'],
$matches['preBinOptions2'] ?? '',
$matches['preBinOptions'] ?? '',
$matches['extraInput'] ?? '',
]
)
);

// Move the options present _before_ bin namespaceName to after, but
// before the end of option marker (--) if present.
$reorderedInput = implode(' ', $inputParts);

return new StringInput($reorderedInput);
}

public static function createNamespaceInput(InputInterface $previousInput): InputInterface
{
return new StringInput((string) $previousInput . ' --working-dir=.');
$matchResult = preg_match(
'/^(.+?\s?)(--(?: .+)?)?$/',
(string) $previousInput,
$matches
);

if (1 !== $matchResult) {
throw InvalidBinInput::forNamespaceInput($previousInput);
}

$inputParts = array_filter(
array_map(
'trim',
[
$matches[1],
'--working-dir=.',
$matches[2] ?? '',
]
)
);

$newInput = implode(' ', $inputParts);

return new StringInput($newInput);
}

public static function createForwardedCommandInput(InputInterface $input): InputInterface
Expand Down
32 changes: 32 additions & 0 deletions src/InvalidBinInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Bamarni\Composer\Bin;

use RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use function sprintf;

final class InvalidBinInput extends RuntimeException
{
public static function forBinInput(InputInterface $input): self
{
return new self(
sprintf(
'Could not parse the input "%s". Expected the input to be in the format "bin <namespaceName> <commandToExecuteInBinNamespace>", for example "bin all update --prefer-lowest".',
$input
)
);
}

public static function forNamespaceInput(InputInterface $input): self
{
return new self(
sprintf(
'Could not parse the input (executed within the namespace) "%s".',
$input
)
);
}
}
154 changes: 131 additions & 23 deletions tests/BinInputFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StringInput;
use function sprintf;

/**
* @covers \Bamarni\Composer\Bin\BinInputFactory
Expand All @@ -29,30 +30,110 @@ public function test_it_can_create_a_new_input(

public static function inputProvider(): iterable
{
yield [
'foo-namespace',
new StringInput('bin foo-namespace flex:update --prefer-lowest'),
new StringInput('flex:update --prefer-lowest'),
$namespaceNames = [
'simpleNamespaceName',
'composed-namespaceName',
'regexLimiter/namespaceName',
'all',
];

yield [
'foo-namespace',
new StringInput('bin foo-namespace flex:update --prefer-lowest --ansi'),
new StringInput('flex:update --prefer-lowest --ansi'),
];
foreach ($namespaceNames as $namespaceName) {
$labelPrefix = sprintf('[%s]', $namespaceName);

yield $labelPrefix.'simple command' => [
$namespaceName,
new StringInput(
sprintf(
'bin %s show',
$namespaceName
)
),
new StringInput('show'),
];

yield $labelPrefix.' namespaced command' => [
$namespaceName,
new StringInput(
sprintf(
'bin %s check:platform',
$namespaceName
)
),
new StringInput('check:platform'),
];

yield $labelPrefix.'command with options' => [
$namespaceName,
new StringInput(
sprintf(
'bin %s show --tree -i',
$namespaceName
)
),
new StringInput('show --tree -i'),
];

yield $labelPrefix.'command with annoyingly placed options' => [
$namespaceName,
new StringInput(
sprintf(
'--ansi bin %s -o --quiet show --tree -i',
$namespaceName
)
),
new StringInput('-o --quiet show --tree -i --ansi'),
];

yield $labelPrefix.'command with options with option separator' => [
$namespaceName,
new StringInput(
sprintf(
'bin %s show --tree -i --',
$namespaceName
)
),
new StringInput('show --tree -i --'),
];

yield $labelPrefix.'command with options with option separator and follow up argument' => [
$namespaceName,
new StringInput(
sprintf(
'bin %s show --tree -i -- foo',
$namespaceName
)
),
new StringInput('show --tree -i -- foo'),
];

yield $labelPrefix.'command with options with option separator and follow up option' => [
$namespaceName,
new StringInput(
sprintf(
'bin %s show --tree -i -- --foo',
$namespaceName
)
),
new StringInput('show --tree -i -- --foo'),
];

yield $labelPrefix.'command with annoyingly placed options and option separator and follow up option' => [
$namespaceName,
new StringInput(
sprintf(
'--ansi bin %s -o --quiet show --tree -i -- --foo',
$namespaceName
)
),
new StringInput('-o --quiet show --tree -i --ansi -- --foo'),
];
}

// See https://github.com/bamarni/composer-bin-plugin/pull/23
yield [
'foo-namespace',
new StringInput('bin --ansi foo-namespace flex:update --prefer-lowest'),
new StringInput('--ansi flex:update --prefer-lowest'),
];

// See https://github.com/bamarni/composer-bin-plugin/pull/73
yield [
'irrelevant',
new StringInput('update --dry-run --no-plugins roave/security-advisories'),
new StringInput('update --dry-run --no-plugins roave/security-advisories'),
new StringInput('flex:update --prefer-lowest --ansi'),
];
}

Expand All @@ -70,14 +151,41 @@ public function test_it_can_create_a_new_input_for_a_namespace(

public static function namespaceInputProvider(): iterable
{
yield [
new StringInput('flex:update --prefer-lowest'),
new StringInput('flex:update --prefer-lowest --working-dir=.'),
$namespaceNames = [
'simpleNamespaceName',
'composed-namespaceName',
'regexLimiter/namespaceName',
'all',
];

yield [
new StringInput('flex:update --prefer-lowest --ansi'),
new StringInput('flex:update --prefer-lowest --ansi --working-dir=.'),
yield 'simple command' => [
new StringInput('flex:update'),
new StringInput('flex:update --working-dir=.'),
];

yield 'command with options' => [
new StringInput('flex:update --prefer-lowest -i'),
new StringInput('flex:update --prefer-lowest -i --working-dir=.'),
];

yield 'command with annoyingly placed options' => [
new StringInput('-o --quiet flex:update --prefer-lowest -i'),
new StringInput('-o --quiet flex:update --prefer-lowest -i --working-dir=.'),
];

yield 'command with options with option separator' => [
new StringInput('flex:update --prefer-lowest -i --'),
new StringInput('flex:update --prefer-lowest -i --working-dir=. --'),
];

yield 'command with options with option separator and follow up argument' => [
new StringInput('flex:update --prefer-lowest -i -- foo'),
new StringInput('flex:update --prefer-lowest -i --working-dir=. -- foo'),
];

yield 'command with annoyingly placed options and option separator and follow up option' => [
new StringInput('-o --quiet flex:update --prefer-lowest -i -- --foo'),
new StringInput('-o --quiet flex:update --prefer-lowest -i --working-dir=. -- --foo'),
];
}

Expand Down
Loading

0 comments on commit a522fc2

Please sign in to comment.