Skip to content

Commit 68031cb

Browse files
committed
commands implementation
1 parent 46bd0e5 commit 68031cb

File tree

8 files changed

+205
-206
lines changed

8 files changed

+205
-206
lines changed

composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"name": "tflori/commands",
3-
"description": "Extension of ulrichsg/getopt-php to support multiple commands",
3+
"description": "Extension of tflori/getopt-php to support multiple commands",
44
"license": "MIT",
55
"require": {
66
"php": ">= 5.6",
7-
"ulrichsg/getopt-php": "^2.3"
7+
"tflori/getopt-php": "^2.4"
88
},
99
"require-dev": {
1010
"phpunit/phpunit": "^5.6",

resources/helpTemplate.php

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Usage: <?= $scriptName; ?><?= $hasCommands ? ' <command>' : ($command ? ' ' . $command : '') ?><?= $hasOptions ? ' [options]' : '' ?> [operands]
2+
<?php if ($description) : ?>
3+
<?= $description ?>
4+
5+
<?php endif; ?><?= $options; ?><?= $commands ?>

src/Command.php

+9-14
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,21 @@ class Command
2525
* @param string $name
2626
* @param string $shortDescription
2727
* @param callable $handler
28-
* @param string $longDescription
2928
* @param array $options
29+
* @param string $longDescription
3030
*/
3131
public function __construct(
3232
$name,
3333
$shortDescription,
3434
callable $handler,
35-
$longDescription = '',
36-
array $options = array()
35+
array $options = array(),
36+
$longDescription = ''
3737
) {
3838
$this->name = $name;
3939
$this->shortDescription = $shortDescription;
4040
$this->handler = $handler;
41-
$this->longDescription = $longDescription ?: $shortDescription;
4241
$this->options = $options;
42+
$this->longDescription = $longDescription ?: $shortDescription;
4343
}
4444

4545
/**
@@ -61,11 +61,14 @@ public function getHandler()
6161
}
6262

6363
/**
64+
* Get description
65+
*
66+
* @param bool $short
6467
* @return string
6568
*/
66-
public function getDescription()
69+
public function getDescription($short = false)
6770
{
68-
return $this->longDescription;
71+
return $short ? $this->shortDescription : $this->longDescription;
6972
}
7073

7174
/**
@@ -83,12 +86,4 @@ public function getName()
8386
{
8487
return $this->name;
8588
}
86-
87-
/**
88-
* @return string
89-
*/
90-
public function getShortDescription()
91-
{
92-
return $this->shortDescription;
93-
}
9489
}

src/Commands.php

+67-73
Original file line numberDiff line numberDiff line change
@@ -2,103 +2,97 @@
22

33
namespace tflori\Commands;
44

5-
use Ulrichsg\Getopt\Getopt;
5+
use tflori\Getopt\Getopt;
66

77
class Commands extends Getopt
88
{
9+
/** @var Command[] */
10+
protected $commands = [];
11+
12+
/** @var Command */
13+
protected $command;
14+
915
/** @var string */
10-
protected $scriptName = '';
16+
protected $banner = ''; // we don't use banner
1117

12-
public function __construct($options = null, $defaultType = Getopt::NO_ARGUMENT)
13-
{
14-
parent::__construct($options, $defaultType);
18+
/** @var string */
19+
protected $template = __DIR__ . '/../resources/helpTemplate.php';
1520

16-
if (isset($_SERVER['argv'][0])) {
17-
$this->setScriptName($_SERVER['argv'][0]);
18-
}
19-
}
21+
/** @var string */
22+
protected $description = '';
2023

21-
/**
22-
* Set the scriptName manually.
23-
*
24-
* @param $scriptName
25-
* @return self
26-
*/
27-
public function setScriptName($scriptName)
24+
public function addCommand(Command $command)
2825
{
29-
$this->scriptName = $scriptName;
26+
$this->commands[$command->getName()] = $command;
3027
}
3128

3229
public function parse($arguments = null)
3330
{
34-
if ($arguments === null && isset($_SERVER['argv'])) {
35-
$arguments = array_slice($_SERVER['argv'], 1);
36-
} elseif (is_string($arguments)) {
37-
$arguments = $this->parseArgumentString($arguments);
38-
}
39-
4031
parent::parse($arguments);
4132

42-
// if (count($this->getOperands()) > 0) {
43-
// $cmd = reset($this->getOperands());
44-
// // set the command and parse again...
45-
// }
33+
if (count($this->getOperands()) > 0) {
34+
$cmd = $this->getOperand(0);
35+
if (isset($this->commands[$cmd])) {
36+
$arguments = array_slice($this->operands, 1);
37+
$this->command = $this->commands[$cmd];
38+
39+
$commandOptions = $this->command->getOptions();
40+
if (!empty($commandOptions)) {
41+
$this->addOptions($commandOptions);
42+
}
43+
44+
$commonOptions = $this->options;
45+
parent::parse($arguments);
46+
$this->options += $commonOptions;
47+
}
48+
}
4649
}
4750

4851
public function getHelpText($padding = 25)
4952
{
50-
$this->setBanner('Usage: ' . $this->scriptName . " [options] [operands]\n");
53+
$this->banner = '';
54+
55+
$scriptName = $this->scriptName;
56+
$options = parent::getHelpText($padding);
57+
$hasOptions = !empty($this->optionList);
58+
if (!$this->command) {
59+
$description = $this->description;
60+
$commands = $this->getCommandsHelp($padding);
61+
$hasCommands = !empty($this->commands);
62+
$command = '';
63+
} else {
64+
$description = $this->command->getDescription();
65+
$commands = '';
66+
$hasCommands = false;
67+
$command = $this->command->getName();
68+
}
5169

52-
return parent::getHelpText($padding);
70+
ob_start();
71+
include $this->template;
72+
return ob_get_clean();
5373
}
5474

55-
/**
56-
* Prase the command line string and returns an array.
57-
*
58-
* @param string $argsString
59-
* @return array
60-
*/
61-
protected function parseArgumentString($argsString)
75+
protected function getCommandsHelp($padding)
6276
{
63-
$argv = [''];
64-
$argsString = trim($argsString);
65-
$argc = 0;
66-
67-
$state = 'n'; // states: n (normal), d (double quoted), s(single quoted)
68-
for ($i = 0; $i < strlen($argsString); $i++) {
69-
$char = $argsString{$i};
70-
switch ($state) {
71-
case 'n':
72-
if ($char === '\'') {
73-
$state = 's';
74-
} elseif ($char === '"') {
75-
$state = 'd';
76-
} elseif (in_array($char, ["\n", "\t", ' '])) {
77-
$argc++;
78-
$argv[$argc] = '';
79-
} else {
80-
$argv[$argc] .= $char;
81-
}
82-
break;
83-
84-
case 's':
85-
if ($char === '\'') {
86-
$state = 'n';
87-
} else {
88-
$argv[$argc] .= $char;
89-
}
90-
break;
91-
92-
case 'd':
93-
if ($char === '"') {
94-
$state = 'n';
95-
} else {
96-
$argv[$argc] .= $char;
97-
}
98-
break;
77+
$help = '';
78+
if (!empty($this->commands)) {
79+
$help .= "Commands:\n";
80+
foreach ($this->commands as $command) {
81+
$help .= sprintf(
82+
"%s %s\n",
83+
str_pad(sprintf(' %s', $command->getName()), $padding),
84+
$command->getDescription(true)
85+
);
9986
}
10087
}
88+
return $help;
89+
}
10190

102-
return array_filter($argv);
91+
/**
92+
* @param string $description
93+
*/
94+
public function setDescription($description)
95+
{
96+
$this->description = $description;
10397
}
10498
}

tests/CommandTest.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use PHPUnit\Framework\TestCase;
66
use tflori\Commands\Command;
7-
use Ulrichsg\Getopt\Option;
7+
use tflori\Getopt\Option;
88

99
class CommandTest extends TestCase
1010
{
@@ -24,8 +24,8 @@ protected function setUp()
2424
'the-name',
2525
'a short description',
2626
array('\PDO', 'getAvailableDrivers'),
27-
'a long description might be longer',
28-
$this->options
27+
$this->options,
28+
'a long description might be longer'
2929
);
3030
}
3131

@@ -36,7 +36,7 @@ public function testConstructorSavesName()
3636

3737
public function testConstructorSavesDescription()
3838
{
39-
self::assertSame('a short description', $this->command->getShortDescription());
39+
self::assertSame('a short description', $this->command->getDescription(true));
4040
}
4141

4242
public function testConstructorSavesLongDescription()

tests/Commands/ConstructorTest.php

-18
This file was deleted.

tests/Commands/HelpTextTest.php

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
namespace tflori\Commands\Test\Commands;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use tflori\Commands\Command;
7+
use tflori\Commands\Commands;
8+
use tflori\Getopt\Option;
9+
10+
class HelpTextTest extends TestCase
11+
{
12+
public function testContainsNoOptionAndNoCommand()
13+
{
14+
$commands = new Commands();
15+
$commands->setScriptName('test');
16+
17+
$helpText = $commands->getHelpText();
18+
19+
$expected = "Usage: test [operands]\n";
20+
self::assertSame($expected, $helpText);
21+
}
22+
23+
public function testContainsOptions()
24+
{
25+
$commands = new Commands();
26+
$commands->setScriptName('test');
27+
$commands->addOptions([Option::create('a', 'optionA')->setDescription('The description for optionA')]);
28+
29+
$helpText = $commands->getHelpText();
30+
31+
$expected = "Usage: test [options] [operands]\n" .
32+
"Options:\n" .
33+
" -a, --optionA The description for optionA\n";
34+
self::assertSame($expected, $helpText);
35+
}
36+
37+
public function testContainsCommand()
38+
{
39+
$commands = new Commands();
40+
$commands->setScriptName('test');
41+
$commands->addCommand(new Command('help', 'Show the help for command', 'var_dump'));
42+
43+
$helpText = $commands->getHelpText();
44+
45+
$expected = "Usage: test <command> [operands]\n" .
46+
"Commands:\n" .
47+
" help Show the help for command\n";
48+
self::assertSame($expected, $helpText);
49+
}
50+
51+
public function testContainsDescription()
52+
{
53+
$commands = new Commands();
54+
$commands->setScriptName('copy');
55+
$commands->setDescription('copy files');
56+
57+
$helpText = $commands->getHelpText();
58+
59+
$expected = "Usage: copy [operands]\n" .
60+
"copy files\n";
61+
self::assertSame($expected, $helpText);
62+
}
63+
64+
public function testShowsExecutedCommand()
65+
{
66+
$commands = new Commands();
67+
$commands->setScriptName('test');
68+
$commands->addCommand(new Command('help', 'Show the help for command', 'var_dump'));
69+
70+
$commands->parse('help');
71+
$helpText = $commands->getHelpText();
72+
73+
$expected = "Usage: test help [operands]\n" .
74+
"Show the help for command\n";
75+
self::assertSame($expected, $helpText);
76+
}
77+
78+
public function testWithCommonAndComandOptions()
79+
{
80+
$commands = new Commands([
81+
Option::create('a', 'optionA')->setDescription('The description for optionA')
82+
]);
83+
$commands->setScriptName('test');
84+
$commands->addCommand(new Command('help', 'Show the help for command', 'var_dump', [
85+
Option::create('b', 'optionB')->setDescription('The description for optionB')
86+
]));
87+
88+
$commands->parse('help');
89+
$helpText = $commands->getHelpText();
90+
91+
$expected = "Usage: test help [options] [operands]\n" .
92+
"Show the help for command\n" .
93+
"Options:\n" .
94+
" -a, --optionA The description for optionA\n" .
95+
" -b, --optionB The description for optionB\n";
96+
self::assertSame($expected, $helpText);
97+
}
98+
}

0 commit comments

Comments
 (0)