spsostrov/app-console

SPŠ Ostrov 应用控制台框架

v0.4.1 2024-05-01 17:36 UTC

README

应用控制台是一个多用途的简单工具,允许定义与任何Composer管理的项目相关联的自定义命令。

目标

  • 提供一种轻松维护不同类型管理脚本的方法。
  • 可以在应用控制台框架内部创建脚本,同时支持Unix风格的选项,但无需与getopt实现纠缠。
  • 支持广泛的脚本语言。支持所有特定于操作系统的选项。
  • 插件架构,其中应用控制台的脚本不仅可以定义在项目本身中,也可以定义在其他Composer依赖项中。
  • 不提供任何特定功能,但提供一个 平台,任何人都可以定义自己的自定义功能。

入门

如果您想使用应用控制台,只需将其用作依赖项

composer require spsostrov/app-console

应用控制台将在供应商的二进制文件中提供一个命令,您可以调用此命令

vendor/bin/app <command> [command-arguments]

此二进制文件完全支持Unix风格的选项,是一个PHP文件。因此,您可以在任何存在PHP解释器的位置运行它。整个应用控制台有趣的是,所有控制台命令的加载位置和方式。实际上,它们是从Composer包中加载的。命令是从这些包加载的

  • 从根包
  • 从所有类型为 spsostrov-app-console 的包
  • spsostrov/app-console 本身。(目前此处没有定义任何命令,但将来可能会有所改变。)

如果在多个包中定义了同名的命令,则使用第一个找到的命令。包的顺序与上述模式中的顺序完全匹配。目前 spsostrov-app-console 包的顺序是由Composer的排序方式决定的,这并不太可靠。也许在应用控制台的将来版本中可以实现清晰的排序规则。这意味着,任何命令首先在根包中查找,然后是在类型为 spsostrov-app-console 的包中,最后是在 spsostrov/app-console 中。

除非另有说明,否则将在包的 scripts/ 子目录中搜索命令。如果您想更改默认的搜索目录,可以在 composer.json 中明确设置它。

{
    "extra": {
        "spsostrov-app-console": {
            "scripts-dir": "some/other/directory/specified/relatively"
        }
    }
}

如果您想创建一个应用控制台命令,只需在 scripts/ 目录中创建一个可执行二进制文件。在这种情况下,控制台将按顺序传递参数,不进行任何解析。只有当传递 --help 和/或 --version 时,它才在应用控制台中识别并内部处理,不传递给应用控制台命令。

控制台将只运行命令作为常规的全球可执行文件。目前Windows平台不受支持,但它在WSL子系统下运行良好。

命令清单

应用控制台的强大之处在于,您可以提供每个命令的清单文件来扩展可用信息。清单文件与可执行文件的名称相同,但具有 .json 扩展名。例如,命令 run 的清单文件名为 run.json

清单文件只是一个JSON文件,可能看起来像这样

{
    "description": "Short description of the command",
    "help": "Long description of the command.",
    "options": [],
    "args": [],
    "hidden": false,
    "invoker-params": []
}

配置记录的含义

  • description - 命令的简短描述
  • help - 命令的详细描述
  • options - 选项解析规则(详情见spsostrov/getopt 包)
  • args - 解析后的选项如何转换回可执行脚本参数的规则。这允许在脚本消耗参数的同时,以更简单的方式传递给应用控制台,而无需使用 getopt。稍后将会展示这些规则的示例。
  • hidden - 如果设置为 true,命令将是可用的,但它不会显示在帮助信息中。此外,所有以点开头的命令默认都是隐藏的。
  • invoker-params - invoker 命令的参数(稍后介绍)

命令参数处理

应用控制台提供了一个参数处理模型,设置起来非常简单且功能强大。主要思想是,将参数从应用控制台传递到命令的可执行二进制文件的过程分为以下步骤

  1. 应用控制台解析其全局选项以及命令名称本身。根据这些信息,它将决定应该调用哪个命令等。命令名称之后的参数将被确定,并稍后传递给可执行二进制文件。
  2. 这些参数使用清单文件中的 options 配置进行处理(见上文)。该处理的结果始终是一个常规的 PHP 关联数组。详情见spsostrov/getopt 包。
  3. 从这个解析选项的关联数组中,可以重新构建参数,但它们可以完全重新排列,以便更容易被脚本处理。

要充分发挥选项解析的潜力,命令必须在清单文件中定义 optionsargs 字段。

如果只定义了 options 但没有定义 args,此解决方案的唯一优势是,应用控制台将正确地寻找所有其他选项中自动定义的 --help--version 选项,因为 args 的默认值导致始终以原始方式传递参数。

定义 args

args 字段仅是一个包含规则的字符串数组,这些规则指定应该添加到参数列表中的内容。这些规则可以访问解析后的选项,因此可以轻松地将解析后的选项放入最终的参数列表中。

这些规则可用于以下情况:

  • $data - 将解析选项中的变量 data 作为单个参数(标量上下文)放入参数列表中
  • @data - 将解析选项中的数组变量 data 作为多个参数(数组上下文)放入参数列表中
  • #data - 放入数组 data 中的参数数量。如果它不是数组,则可能是 01,具体取决于值是否存在(计数上下文)
  • ?data - 与 $data 相同,但如果 data 不存在,则不放入任何参数。(可选标量上下文)
  • $data?"default-value" - 在参数列表中放入变量 data,但如果变量 data 未定义,则使用默认值 default-value

示例

假设我们想要创建一个命令,该命令接受任意数量的输入文件作为参数,并具有一个默认为 stdout 的输出文件参数。

处理命令的设计方式使得输出文件是第一个参数(如果没有指定输出,则默认为 -),而所有其他参数表示输入文件。

我们希望选项设计得可以调用

vendor/bin/app command --output <output> [input1 [input2 ...]

为了实现这个目标,你可以在清单文件中放入以下内容

{
    "options": [
        "o|output: Output file",
        "$input* Input file"
    ],
    "args": [
        "$output?\"-\"",
        "@input"
    ]
}

事件

应用控制台包含一种机制,不仅可以调用特定命令,还可以处理不同类型的事件。事件背后的思想几乎与命令相同,只有一个区别:事件总是对所有包进行调用。

任何命令都可以作为事件处理。不需要特殊要求。只需遵循一个约定

事件命令名称应始终以点开头,这样它们默认情况下是不可见的。如果您想将一个命令作为事件调用,只需调用

vendor/bin/app --all .configure

在这种情况下,所有包内部都会调用 .configure 命令。

从特定包调用命令

vendor/bin/app --package spsostrov/runtime dc

这将从 spsostrov/runtime 包调用 dc 命令。

使用自定义命令调用器

可以设置一个特殊场景,即命令不是通过直接执行来调用,而是通过使用“调用器”来执行。调用器获取要执行的二进制文件及其参数作为调用器的参数,并且它可以执行任何它想做的事情。

调用器是以这种方式定义的常规命令

  • 命令的名称始终是 .invoke
  • 它的清单文件中包含 "is-invoker": true 标志。

然后所有命令都使用这样的调用器执行。使用调用器的目的是根据需求而定,但原始目标是在调用任何命令之前自动进行一些权限提升。

调用器参数

调用器也可以参数化。参数的数量是固定的,并在调用器的清单文件中规定。要在调用器的清单文件中定义调用器参数的数量,使用以下方法

{
    "is-invoker": true,
    "invoker-accepted-params": {
        "user": "root",
        "container": "ubuntu"
    }
}

接受的参数列表定义为json对象,其中项的顺序很重要

  • 对象中的键只是命名调用器参数(要调用的命令本身可以引用它)
  • 值是给定调用器参数的默认值(可以是字符串或null)
  • 键值对的顺序描述了调用器参数的顺序。

即在这个特定的例子中,调用器的参数将是

.invoke root ubuntu <invoked-binary> <invoked-binary-args>

任何命令本身都可以使用其自己的清单文件覆盖调用器参数。例如

{
    "invoker-params": {
        "user": "john-doe",
        "container": "debian"
    }
}

重要:如果某些调用器参数没有值(因为默认值为null且命令没有覆盖它),则不使用调用器,直接调用命令。