simple-cli/simple-cli

一个简单的命令行框架

2.0.0 2021-12-29 23:41 UTC

README

Latest stable version GitHub Actions StyleCI codecov.io PHPStan Codacy grade

一个面向对象和无需依赖的简单 CLI 框架。

Example

特性

  • 自动文档。使用可用的命令、参数和选项自动生成 --help
  • 使用 PHP 8 属性的清洁语法。
  • 检测可能的输入错误并自动建议。
  • 基于文档注释以保持代码的超清洁。
  • 支持颜色。
  • 支持交互式命令和 CLI 输入的自动完成。
  • 提供预定义命令: usageversion
  • 提供预定义选项: --help--quiet--verbose
  • 提供用于创建程序和命令引导的 CLI。
  • 将您的程序构建为 PHAR 文件。

以下文档是为 simple-cli 2 编写的,它需要 PHP 8 并支持属性和注释。

simple-cli 1 与 PHP >= 7.1 兼容,仅支持注释。

创建命令行程序

您可以将您的命令行程序添加到任何现有的 composer 应用程序中,或者使用 composer init 创建一个新的。

然后添加 simple-cli

composer require simple-cli/simple-cli

假设您的应用程序允许添加或乘以两个参数,并且您想在 CLI 中调用 easy-calc,那么您需要创建一个扩展 SimpleCli\SimpleCliEasyCalc 类。

因此,首先检查您的 composer.json 中是否有 PSR 自动加载设置

"autoload": {
    "psr-4": {
        "MyVendorName\\": "src/MyVendorName/"
    }
},

(您可能需要运行 composer updatecomposer dump-autoload 以使自动加载生效。)

然后创建该类,使其可以被自动加载,所以按照上面的例子,我们可以创建文件 src/MyVendorName/CliApp/EasyCalc.php

<?php

namespace MyVendorName\CliApp;

use SimpleCli\SimpleCli;

class EasyCalc extends SimpleCli
{
    public function getCommands() : array
    {
        return []; // Your class needs to implement the getCommands(), we'll see later what to put in here.
    }
}

默认情况下,程序名称将从类名计算得出,这里 EasyCalc 变为 easy-calc,但您可以在类中添加 protected ?string $name = 'my-custom-name'; 来选择任何名称。

现在您可以从控制台运行它

vendor/bin/simple-cli create "MyVendorName\CliApp\EasyCalc"

它将为 Unix 系统创建 bin/easy-calc,为 Windows OS 创建 bin/easy-calc.bat

您可以将它添加到 composer.json 中,这样用户就可以通过 composer 调用它

"bin": [
    "bin/easy-calc"
],

现在我们来测试您的 CLI 程序

bin/easy-calc

Usage

如你所见,simple-cli 默认提供了两个命令:list(这是当用户没有选择命令时的默认命令,列出可用的命令)和 version(将显示您的 composer 包的版本和您可能添加的版本详细信息)。

请注意,如果您不想发布它,您可以自定义要显示的版本,或者禁用任何默认命令

class EasyCalc extends SimpleCli
{
    public function getCommands() : array
    {
        return [];
    }

    public function getVersion(): string
    {
        return '1.0.0';
    }
}

或者您可以选择禁用任何默认命令

class EasyCalc extends SimpleCli
{
    public function getCommands() : array
    {
        return [
            'version' => false,
        ];
    }
}

构建为 PHAR

Simple-CLI 允许您将您的程序打包为单个 PHAR 文件。

它将自动打包您 src 文件夹中的所有文件,您的主类所在的不同根文件夹,以及 vendor 文件夹。

因此,为了使其轻量,请从这些文件夹中删除任何不需要的文件,例如使用

composer install --no-dev

然后简单地运行

./bin/simple-cli build-phar

在您的工作目录内。

您也可以指定包的版本

PHAR_PACKAGE_VERSION=2.0.0 ./bin/simple-cli build-phar

因此,使用 GitHub Actions,在推送时自动构建您的项目为 PHAR 可以轻松完成

.github/workflows/phar.yml:

name: Generate phar

on:
  push:
    branches: [ '**' ]
  pull_request:
    branches: [ '**' ]
  create:
    tags:
      - "*"
  release:
    types:
      - created

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    env:
      COMPOSER_NO_INTERACTION: 1

    name: Build PHAR

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.0'
          coverage: none
          tools: composer:v2

      - name: Cache Composer packages
        id: composer-cache
        uses: actions/cache@v2
        with:
          path: vendor
          key: phar-${{ runner.os }}-php${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}

      - name: Install dependencies
        run: composer install --prefer-dist --no-progress --no-dev

      - name: Build
        run: |
          chmod +x ./bin/simple-cli
          ./bin/simple-cli build-phar
        env:
          PHAR_PACKAGE_VERSION: ${{ secrets.GITHUB_REF }}

      - name: Upload my-cli.phar
        uses: actions/upload-artifact@v2
        with:
          name: my-cli.phar
          path: my-cli.phar

      - name: Release my-cli.phar
        if: github.event_name == 'release'
        uses: skx/github-action-publish-binaries@master
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          args: 'my-cli.phar'

my-cli 替换为您的程序名称。

添加命令

现在您的 CLI 需要实际的命令。例如,要创建一个 add 命令,您可以创建一个 MyVendorName\CliApp\Command\Add

<?php

namespace MyVendorName\CliApp\Command;

use SimpleCli\Command;
use SimpleCli\Options\Help;
use SimpleCli\SimpleCli;

/**
 * Sum arguments.
 */
class Add implements Command
{
    use Help;

    public function run(SimpleCli $cli): bool
    {
    }
}

然后在您的CLI中添加这个带有名称的命令

class EasyCalc extends SimpleCli
{
    public function getCommands() : array
    {
        return [
            'add' => \MyVendorName\CliApp\Command\Add::class,
        ];
    }
}

如果您再次运行 bin/easy-calc(或 bin/easy-calc list),现在您将看到可用的命令 add。您在 /** */ 中放置的注释将显示在描述中。

如果您运行 bin/easy-calc add --help(或 bin/easy-calc add -h),您将看到基于定义的选项和参数的命令文档。如您所见,只有由特质 SimpleCli\Options\Help(我们强烈建议在您的命令中始终使用此特质)提供的 --help 选项(或 -h 别名)。

添加参数

现在让我们添加一些参数,以便您的命令实际上可以做一些事情。

<?php

namespace MyVendorName\CliApp\Command;

use SimpleCli\Attribute\Argument;
use SimpleCli\Command;
use SimpleCli\Options\Help;
use SimpleCli\SimpleCli;

/**
 * Sum arguments.
 */
class Add implements Command
{
    use Help;

    #[Argument('The first number')]
    public float $number1 = 0;

    #[Argument('The second number')]
    public float $number2 = 0;

    public function run(SimpleCli $cli): bool
    {
        $cli->write($this->number1 + $this->number2);

        return true;
    }
}

#[Argument] 属性允许 simple-cli 知道它是一个参数。

参数类型将从属性类型提示中获取,如果您需要比简单的 array 更精确的类型,例如 string[],而不是简单地使用 array,您可以使用 PHPDoc 中的 @var 注释来指定它,例如 /** @var string[] */ 将被使用,否则类型将根据默认值推断。因此,public float $number2; 将被视为 float,而 public $number2 = false; 将被视为 bool

如果您运行 bin/easy-calc add --help,您将看到它们在帮助中显示,包括它们的描述、类型和默认值。

现在是执行您的命令的时候了

bin/easy-calc add 2 3

它输出 5 🚀

请注意,run() 必须返回一个布尔值

  • 对于成功的命令(退出代码 0)返回 true
  • 对于错误(退出代码 1)返回 false

您还可以使用属性 #[Rest] 允许无限数量的参数。rest arguments 变量将是一个包含所有其他参数的数组。

因此,如果您有 2 个 #[Argument] 和一个 #[Rest],那么如果用户使用 5 个参数调用您的命令,第一个参数将传递到第一个 #[Argument],第二个参数传递到第二个 #[Argument],而其他 3 个参数则作为一个数组传递到 #[Rest] 参数。

当然,您也可以将 #[Rest] 与任何其他参数一起使用,因此对于我们的 add 命令,它可以是

<?php

namespace MyVendorName\CliApp\Command;

use SimpleCli\Attribute\Rest;
use SimpleCli\Command;
use SimpleCli\Options\Help;
use SimpleCli\SimpleCli;

/**
 * Sum arguments.
 */
class Add implements Command
{
    use Help;

    /** @var float[] */
    #[Rest('The numbers to sum')]
    public array $numbers = [];

    public function run(SimpleCli $cli): bool
    {
        $cli->write(array_sum($this->numbers));

        return true;
    }
}

现在您可以以任意数量的参数调用

bin/easy-calc build 2 3 1.5

输出:6.5

SimpleCli\Annotation\* 导入,如 use SimpleCli\Annotation\argument; 可用于帮助 IDE 自动完成,但不是必需的。

添加选项

simple-cli 提供 3 个标准选项。您已经了解 --help -h 作为 SimpleCli\Options\Help 特质,您可以在您的命令中简单地使用 use

但还有 --quiet -q 作为 SimpleCli\Options\Quiet,允许用户静音输出。如果您在命令中使用此特质,并且用户传递选项 --quiet-q,则方法 $cli->write()$cli->writeLine()(以及所有输出方法)将不再输出任何内容。

您还可以使用 --verbose -v 使用 SimpleCli\Options\Verbose

<?php

namespace MyVendorName\CliApp\Command;

use SimpleCli\Command;
use SimpleCli\Options\Verbose;
use SimpleCli\SimpleCli;

/**
 * Sum arguments.
 */
class Add implements Command
{
    use Verbose;

    public function run(SimpleCli $cli): bool
    {
        // ...

        if ($this->verbose) {
            $cli->writeLine('Log some additional info', 'light_cyan');
        }

        // ...
    }
}

您还可以使用 #[Option] 属性创建自己的选项

<?php

namespace MyVendorName\CliApp\Command;

use SimpleCli\Attribute\Option;
use SimpleCli\Command;
use SimpleCli\SimpleCli;

/**
 * Sum arguments.
 */
class Add implements Command
{
    #[Option('Something the command can use.')]
    public $foo = 'default';

    #[Option(
        name: 'show-foo',
        description: 'Whether foo should be displayed or not.',
    )]
    public $showFoo = false;

    public function run(SimpleCli $cli): bool
    {
        if ($this->showFoo) {
            $cli->write($this->foo, 'red');
        }

        return true;
    }
}
bin/easy-calc --show-foo --foo=bar

输出:bar(红色)。

请注意,您可以在属性中传递选项的名称和别名:#[Option(name: ['some-name', 'other-name'], alias: ['s', 'o'])] 这意味着 --some-name--other-name-s-o 都将存储在同一个选项变量中。

此外,请注意,如果选项是布尔类型(显式类型为 bool@var bool 或布尔默认值)并且具有别名,则它们可以合并。如果您有 #[Option(name: 'show-foo', alias: 's')#[Option(name: 'verbose', alias: 'v'),并且通过 CLI 传递 -vs,则两个选项都将为 true

对于非布尔选项,可以使用 --foo bar--foo=bar 设置值,两者都有效。并且选项可以放在任何位置(在参数之前、之后或之间)。

最后,如果您没有设置名称,仅使用 #[Option] 属性,选项将与其变量具有相同的名称,如果可用,则将首字母用作别名。

注解

可以一行内编写 @option@argument@rest 的注解。

class Add implements Command
{
    #[Argument('First argument.')]
    public $first = 'main';

    #[Rest('Other arguments.')]
    public $others = [];

    #[Option('Something the command can use.')]
    public $foo = 'default';

    #[Option(
        name: 'show-foo',
        alias: 'f',
        description: 'Whether foo should be displayed or not.',
    )]
    public $showFoo = false;

    // run(...)
}

使用此语法,无法进行 @var 类型指定,因此类型将自动使用属性类型提示或从默认值推断。

进度条小部件

Usage

use SimpleCli\Command;
use SimpleCli\SimpleCli;
use SimpleCli\Widget\ProgressBar;

class SomeCommand implements Command
{
    public function run(SimpleCli $cli): bool
    {
        $bar = new ProgressBar($cli);
        $bar->start();
        $bar->setValue(0.3);
        $bar->setValue(0.7);
        $bar->setValue(1);
        $bar->end();

        return true;
    }
}

这将显示 30% 和一个 30% 填充的条,然后替换为 70% 的条,最后为满条。看起来如下:

|  70% [===================================>               ]

ProgressBar 将其设置和使用的字符作为公共属性暴露,因此您可以简单地设置以自定义条样式:[源代码](https://github.com/kylekatarnls/simple-cli/blob/master/src/SimpleCli/Widget/ProgressBar.php)

让我们看看一个具体的例子

public function run(SimpleCli $cli): bool
{
    $bar = new ProgressBar($cli);
    // Assuming we have a 214MB file being downloaded in a parallel process
    $bar->total = 214 * 1024 * 1024;
    // Let's customize a bit the style:
    $bar->width = 20; // inner bar size
    $dash = str_repeat('', $bar->width);
    // Let's draw a swaure around the bar
    $bar->start = "{$dash}\n";
    $bar->barStart = '';
    $bar->barEnd = '';
    $bar->after = "\n{$dash}";
    $bar->cursor = ''; // remove bar middle cursor
    // Colorize some characters for bar (left) and empty bar (right)
    $bar->bar = $cli->colorize('', 'cyan');
    $bar->emptyBar = $cli->colorize('', 'light_gray');
    // as ->after contains a new line, we have
    // to rewind 1 more line
    $bar->rewind = "\033[1A\r";
    $bar->start();

    while ($bar->isInProgress()) { // while value < total
        $bar->setValue(filesize('partially-downloaded-file.part'));
        usleep(250000); // Let's refresh every 250ms
    }

    $bar->end();

    return true;
}

表格小部件

use SimpleCli\Command;
use SimpleCli\SimpleCli;
use SimpleCli\Widget\Table;
use SimpleCli\Widget\Cell;

class SomeCommand implements Command
{
    public function run(SimpleCli $cli): bool
    {
        $data = [
            [
                new Cell('Artist', Cell::ALIGN_CENTER),
                new Cell('Song', Cell::ALIGN_CENTER),
                new Cell('Year', Cell::ALIGN_CENTER),
            ],
            [$cli->colorize('Nina Simone', 'cyan'), 'Feeling Good', 1965],
            ['The Marvelettes', 'Please Mr. Postman', 1961],
        ];
        $table = new Table($data);

        $cli->writeLine($table->format());

        return true;
    }
}

Usage

Table 仅接受数据并将它们格式化为文本表格,因此它基本上可以在 SimpleCli 上下文之外使用。

它提供了一个非常简单的方式来自定义模板。您只需要提供一个示例,使用 1234 作为 2x2 表格的内容,然后它将您的示例应用到任何大小的表格上。

$table = new Table([
    ['One', 'Two', 'Three'],
    ['*'],
    ['Hello', 'World', '!'],
]);
$table->template =
'╔═══╤═══╗
║ 1 │ 2 ║
╟───┼───╢
║ 3 │ 4 ║
╚═══╧═══╝';

输出

╔═══════╤═══════╤═══════╗
║ One   │ Two   │ Three ║
╟───────┼───────┼───────╢
║ *     │       │       ║
╟───────┼───────┼───────╢
║ Hello │ World │ !     ║
╚═══════╧═══════╧═══════╝

为了防止代码中的丑陋缩进,您可以使用 !template! 在给定位置开始模板

$table->template = '
    !template!
    ╔═══╤═══╗
    ║ 1 │ 2 ║
    ╟───┼───╢
    ║ 3 │ 4 ║
    ╚═══╧═══╝';

相当于上面的模板,因为在 ! 之前的所有缩进都将被忽略。

您还可以设置用作填充的字符串

$table->fill = '_-';

最后,您可以设置每列的默认对齐方式

$table->align = [Cell::ALIGN_LEFT, Cell::ALIGN_CENTER, Cell::ALIGN_RIGHT];

您仍然可以使用以下方式覆盖给定单元格的对齐方式

new Cell('Text', Cell::ALIGN_CENTER)

Cell 还允许您跨列

// 2 columns-wide
(new Cell('Text'))->cols(2)

// 3 columns-wide centered
(new Cell('Text', Cell::ALIGN_CENTER))->cols(3)

文本占用的空间将平均分配到各列中。

最后,您可以使用 $cli->colorize() 将颜色添加到模板或单元格内容中。考虑到了颜色特殊字符,因此无论是否有颜色,显示都将保持正确对齐。

API 参考

在上面的例子中,您可以看到您的命令 run(SimpleCli $cli) 方法获得了一个 SimpleCli 实例。 $cli 是您的程序对象,是扩展 SimpleCli 的类的实例,因此在上面的例子中,它是 EasyCalc 的实例,这意味着您可以通过 $cli 访问您在子类中定义的所有方法(或覆盖以自定义您的程序)以及从继承的 SimpleCli 类中可用的所有方法。

getVersionDetails(): string

获取要显示在版本命令中的详细信息。

getVersion(): string

获取处理 CLI 程序的包的 composer 版本。

displayVariable(int $length, string $variable, array $definition, mixed $defaultValue): void

输出标准命令变量(参数或选项)。

autocomplete(string $start): array

获取给定起始处的可能完成项。

read(string $prompt, Closure|callable|array|null $completion): string

提示用户 $prompt 并返回 CLI 输入。

getStandardInput(): string

获取命令通过:echo "foobar" | command 或:command < some-file.txt 接收的初始 stdin 内容。如果没有传递输入,则返回空字符串。

getColors(): array

每个文本颜色名称的代码列表。

getBackgrounds(): array

每个背景颜色名称的代码列表。

isMuted(): bool

如果 CLI 程序被静音(安静),则返回 true。

setMuted(bool $muted): void

设置静音状态。

mute(): void

静音程序(不再输出)。

unmute(): void

取消静音程序(启用输出)。

enableColors(): void

在命令行中启用颜色支持。

disableColors(): void

在命令行中禁用颜色支持。

setEscapeCharacter(string $escapeCharacter): void

在 CLI 字符串中设置用于转义命令的自定义字符串。

setColors(array $colors, array $backgrounds): void

设置颜色调色板。

colorize(string $text, string $color, string $background): string

返回具有给定颜色和背景颜色的 $text。

rewind(int $length): void

将 CLI 光标回退 $length 个字符,如果省略 $length,则使用最后写入的字符串长度。

write(string $text, string $color, string $background): void

以给定颜色和背景颜色输出 $text。

writeLine(string $text, string $color, string $background): void

以给定颜色和背景颜色输出 $text 并添加新行。

rewrite(string $text, string $color, string $background): void

用给定颜色和背景颜色的 $text 替换最后写入的行。

rewriteLine(string $text, string $color, string $background): void

用给定颜色和背景颜色的 $text 替换最后写入的行并重新添加新行。

getName(): string

获取 CLI 程序的名称。

getFile(): string

获取 CLI 中调用的当前程序文件。

getCommands(): array

获取命令列表,排除 SimpleCli 提供的命令。

getAvailableCommands(): array

获取包含 SimpleCli 提供的命令的命令列表。

getCommand(): string

获取选择的命令。

getParameters(): array

获取未经筛选的原始参数(选项和参数)。

getParameterValue(string $parameter, array $parameterDefinition): string|int|float|bool|null

根据定义中的类型转换参数/选项。

getArguments(): array

获取当前筛选参数列表。

getExpectedArguments(): array

获取预期参数的定义。

getRestArguments(): array

获取筛选参数的其余部分。

getExpectedRestArgument(): array

如果提供了 @rest 属性,则获取其余参数的定义。

getOptions(): array

获取当前筛选选项列表。

getExpectedOptions(): array

获取预期选项的定义。

getOptionDefinition(string $name): array

获取指定名称或别名的选项定义及其预期类型/值。

getPackageName(): string

获取处理 CLI 程序的 composer 包名。

setVendorDirectory(string $vendorDirectory): void

设置应包含 composer/installed.json 的包的供应商。

getVendorDirectory(): string

获取应包含 composer/installed.json 的包的供应商。

getInstalledPackages(): array<string|int, array<string, string>>

获取 composer 安装的包列表。

getInstalledPackage(string $name): SimpleCli\Composer\InstalledPackage

获取给定已安装包的数据。

getInstalledPackageVersion(string $name): string

获取给定已安装包的版本。

extractClassNameDescription(string $className): string

获取给定类的 PHP 注释文档块内容。

getAttributeOrAnnotation(string $source, string $annotation, ReflectionProperty $property, string $attributeClass): object|string|null

如果存在,则获取属性或从 PHP 注释文档块中提取注释内容。

extractAnnotation(string $source, string $annotation): string

从 PHP 注释文档块中提取注释内容。