Mask: 用 Markdown 革新命令行任务自动化

文摘   2024-06-27 00:47   江苏  

Mask: 用 Markdown 革新命令行任务自动化

mask 是一个命令行任务运行器,它通过一个简单的 Markdown 文件来定义。它会在当前目录中搜索 maskfile.md,然后解析该文件中的命令和参数。

一个 maskfile.md 不仅是一份人类可读的文档,同时也是一份命令定义!以文档为中心的特性使得其他人可以通过简单地阅读你的 maskfile.md 来轻松开始你的项目开发设置。使用 Markdown 的一个好处是,许多编辑器和渲染器(比如 GitHub 本身)都内置了代码块的语法高亮。

以下是 mask 自身使用的 maskfile.md[1] 作为示例!

任务

mask 的开发任务。

运行开发模式 (maskfile_command)

构建并运行 mask 的开发模式

注意: 使用 cargo run 构建并运行 mask 的开发模式。当前目录中必须存在一个 maskfile(此文件),并且必须提供该 maskfile 的有效命令 (maskfile_command) 以测试你对 mask 所做的更改。由于目前只能针对此 maskfile 进行测试,你可以在底部添加子命令并运行这些子命令,而不是运行现有命令。

示例: mask run "test -h" - 输出此 test 命令的帮助信息

选项

  • watch
    • 标志:-w --watch
    • 描述:文件变更时重新构建
if [[ $watch == "true" ]]; then
    watchexec --exts rs --restart "cargo run -- $maskfile_command"
else
    cargo run -- $maskfile_command
fi

注意: 在 Windows 平台上,mask 默认回退到运行 powershell 代码块。

param (
$maskfile_command = $env:maskfile_command,
$watch = $env:watch
)

$cargo_cmd = "cargo run -- $maskfile_command"
$extra_args = "--exts rs --restart $cargo_cmd"

if ($watch) {
Start-Process watchexec -ArgumentList $extra_args -NoNewWindow -PassThru
} else {
cargo run -- $maskfile_command
}

构建

构建 mask 的发布版本

cargo build --release
cargo build --release

链接

构建 mask 并用它替换全局安装的版本以进行测试

cargo install --force --path ./mask
[Diagnostics.Process]::Start("cargo", "install --force --path ./mask").WaitForExit()

测试

运行所有测试

选项

  • file
    • 标志:-f --file
    • 类型:string
    • 描述:只从特定文件名运行测试
extra_args=""

if [[ "$verbose" == "true" ]]; then
    # 线性运行测试并使日志在输出中可见
    extra_args="-- --nocapture --test-threads=1"
fi

echo "Running tests..."
if [[ -z "$file" ]]; then
    # 默认运行所有测试
    cargo test $extra_args
else
    # 测试特定的集成文件名
    cargo test --test $file $extra_args
fi
echo "Tests passed!"
param (
$file = $env:file
)

$extra_args = ""
$verbose = $env:verbose

if ($verbose) {
$extra_args = "-- --nocapture --test-threads=1"
}

Write-Output "Running tests..."
if (!$file) {
cargo test $extra_args
} else {
cargo test --test $file $extra_args
}
Write-Output "Tests passed!"

格式化

格式化所有源文件

选项

  • check
    • 标志:-c --check
    • 描述:显示哪些文件格式不正确
if [[ $check == "true" ]]; then
    cargo fmt --all -- --check
else
    cargo fmt
fi
param (
$check = $env:check
)

if ($check) {
cargo fmt --all -- --check
} else {
cargo fmt
}

检查

使用 clippy 对项目进行静态代码检查

cargo clippy

要开始使用,请按照下面的指南操作,或者查看 mask 拥有的更多高级特性[2],比如位置参数可选标志子命令、其他脚本运行时等!

安装

预编译的二进制文件

前往 [Releases 页面][releases] 查看最新发布的版本。在 Assets 下,你会看到可供下载的 Linux、macOS 和 Windows 的 zip 文件。下载后,你可以解压它们,然后将 mask 二进制文件移动到 $PATH 中的某个可访问位置,如 mv mask /usr/local/bin

Homebrew

mask 可在 [Homebrew][homebrew] 中获取,你可以通过 brew install mask 来安装它。

Cargo

mask 发布在 [crates.io][crate] 上,你可以通过 cargo install mask 来安装它。

从源代码构建

如果你更喜欢从源代码构建,克隆这个仓库,然后运行 cargo build --release

开始使用

首先,在你的项目中定义一个简单的 maskfile.md

# 我的项目任务

## build

> 构建我的项目

```sh
echo "正在构建项目..."

test

测试我的项目

你可以在任何地方编写文档。只有特定类型的 Markdown 模式 被解析以确定命令结构。

下面定义为 js 的代码块意味着它将使用 node 运行。Mask 还 支持其他脚本运行时,包括 python、ruby 和 php!

console.log("正在运行测试...")

然后,尝试运行你的命令!

```sh
mask build
mask test

特性

位置参数

这些参数定义在命令名旁边的(圆括号)内。它们是必需的参数,必须提供这些参数命令才能运行。[可选参数][2]即将推出。参数名称作为环境变量注入到脚本的作用域中。

示例:

## test (file) (test_case)

> 运行测试

```bash
echo "正在 $file 中测试 $test_case"

### 命名标志

你可以为你的命令定义一系列命名标志。标志名称作为环境变量注入到脚本的作用域中。

**示例:**

```markdown
## serve

> 服务这个目录

<!-- 你必须在标志列表之前定义 OPTIONS -->

**OPTIONS**

- port
  - 标志:-p --port
  - 类型:string
  - 描述:在哪个端口上服务

```sh
PORT=${port:-8080} # 如果没有提供,则设置回退端口

if [[ "$verbose" == "true" ]]; then
    echo "正在 PORT: $PORT 上启动 http 服务器"
fi
python -m SimpleHTTPServer $PORT

你也可以通过将标志的 `type` 设置为 `number` 来让你的标志期望一个数值。这意味着 `mask` 将自动为你验证它是否为数字。如果验证失败,`mask` 将以有用的错误消息退出。

**示例:**

```markdown
## purchase (price)

> 计算某物的总价格。

**OPTIONS**

- tax
  - 标志:-t --tax
  - 类型:number
  - 描述:税是多少?

```sh
TAX=${tax:-1} # 如果没有提供,则回退到 1
echo "总计:$(($price * $TAX))"

如果你省略了 `type` 字段,`mask` 将把它当作一个 `boolean` 标志。如果传递了标志,其环境变量将是 `"true"`,否则它将不被设置/不存在。

重要的是要注意,`mask` 会自动注入一个非常常见的 `boolean` 标志 `verbose` 到每一个命令中,即使你没有使用它,这也为你节省了一些打字的工作。这意味着每个命令隐含地已经有一个 `-v` 和 `--verbose` 标志了。

**示例:**

```markdown
## test

> 运行测试套件

**OPTIONS**

- watch
  - 标志:-w --watch
  - 描述:文件变更时运行测试

```bash
[[ "$watch" == "true" ]] && echo "以监视模式启动..."
[[ "$verbose" == "true" ]] && echo "使用额外日志运行..."

标志默认是可选的。如果你在标志定义中添加了 `required`,`mask` 会在用户没有提供它时出错。

**示例:**

```markdown
## ping

**OPTIONS**

- domain
  - 标志:-d --domain
  - 类型:string
  - 描述:要 ping 的域名
  - 必需

```sh
ping $domain

### 子命令

由于它们简单地通过 Markdown 标题的级别来定义,所以可以轻松创建嵌套的命令结构。H2 (`##`) 是你定义顶级命令的地方。之后的每一级都是一个子命令。

**示例:**

```markdown
## services

> 与启动和停止服务相关的命令

### services start (service_name)

> 启动一个服务。

```bash
echo "正在启动服务 $service_name"

services stop (service_name)

停止一个服务。

echo "正在停止服务 $service_name"

你可能注意到上面的 `start` 和 `stop` 命令都带有它们的父命令 `services` 的前缀。在某些情况下,用祖先命令来前缀子命令可能有助于可读性,但这是完全可选的。下面的示例与上面的相同,但没有前缀。

**示例:**

```markdown
## services

> 与启动和停止服务相关的命令

### start (service_name)

> 启动一个服务。

```bash
echo "正在启动服务 $service_name"

stop (service_name)

停止一个服务。

echo "正在停止服务 $service_name"

### 支持其他脚本运行时

除了 shell/bash 脚本,`mask` 还支持使用 node、python、ruby 和 php 作为脚本运行时。这让你有自由选择适合手头特定任务的正确工具。例如,假设你有一个 `serve` 命令和一个 `snapshot` 命令。你可以选择 python 来 `serve` 一个简单的目录,也许选择 node 来运行一个 puppeteer 脚本,为每个页面生成一个 png `snapshot`。

**示例:**

```markdown
## shell (name)

> 一个示例 shell 脚本

有效的语言代码:sh, bash, zsh, fish... 任何支持 -c 的 shell

```zsh
echo "你好,$name!"

node (name)

一个示例 node 脚本

有效的语言代码:js, javascript

const { name } = process.env;
console.log(`你好,${name}!`);

python (name)

一个示例 python 脚本

有效的语言代码:py, python

import os
name = os.getenv("name""世界")
print("你好," + name + "!")

ruby (name)

一个示例 ruby 脚本

有效的语言代码:rb, ruby

name = ENV["name"|| "世界"
puts "你好,#{name}!"

php (name)

一个示例 php 脚本

$name = getenv("name") ?: "世界";
echo "你好," . $name . "!\n";

#### Windows 支持

你甚至可以添加 powershell 或批处理代码块与 Linux/macOS 代码块一起。这取决于运行的平台,将执行正确的代码块。

**示例:**

```markdown
## link

> 构建并全局链接二进制文件

```bash
cargo install --force --path .
[Diagnostics.Process]::Start("cargo", "install --force --path .").WaitForExit()

### 自动帮助和使用输出

你不必花时间手动编写帮助信息。`mask` 使用你的命令描述和选项自动生成帮助输出。对于每个命令,它添加了 `-h, --help` 标志和一个替代的 `help <name>` 命令。

\*\*示例:

\*\*

```sh
mask services start -h
mask services start --help
mask services help start
mask help services start

所有输出相同的帮助信息:

mask-services-start
启动或重启服务。

用法:
mask services start [标志] <service_name>

标志:
-h, --help 打印帮助信息
-V, --version 打印版本信息
-v, --verbose 设置详细程度
-r, --restart 如果服务已在运行,则重启
-w, --watch 文件变更时重启服务

参数:
<service_name>

在脚本中运行 mask

如果你需要将命令链接在一起,可以很容易地在脚本中调用 mask。但是,如果你计划使用不同的 maskfile 运行 mask[3],你应该考虑使用 $MASK 实用程序,它允许你的脚本不依赖于位置。

示例:

## bootstrap

> 安装依赖项,构建,链接,迁移数据库,然后启动应用程序

```sh
mask install
mask build
mask link
# $MASK 也可以工作。它是 `mask --maskfile <path_to_maskfile>` 的别名变量
# 它保证你的脚本即使从另一个目录调用也能正常工作。
$MASK db migrate
$MASK start

### 继承脚本的退出码

如果你的命令以错误退出,`mask` 将以其状态码退出。这允许你链接命令,它们将在第一个错误上退出。

**示例:**

```markdown
## ci

> 运行测试并检查 lint 和格式化错误

```sh
mask test \
    && mask lint \
    && mask format --check

### 使用不同的 maskfile 运行 mask

如果你在一个没有 `maskfile.md` 的目录中,但你想要引用其他地方的一个,你可以使用 `--maskfile <path_to_maskfile>` 选项。

**示例:**

```sh
mask --maskfile ~/maskfile.md <subcommand>

提示: 为此创建一个 bash 别名,以便你可以轻松地从任何地方调用

# 给它起个有趣的名字
alias wask="mask --maskfile ~/maskfile.md"

# 你可以从任何地方运行这个
wask <subcommand>

环境变量实用工具

在每个脚本的执行环境中,mask 注入了一些可能有用的环境变量助手。

$MASK

这在在脚本中运行 mask[4]时很有用。这个变量允许我们使用 $MASK command 而不是 mask --maskfile <path> command 在脚本中调用,这样它们就可以不依赖于位置(不在乎它们被调用的地方)。这对于你可能从任何地方调用的全局 maskfiles 特别方便。

$MASKFILE_DIR

这个变量是 maskfile 父目录的绝对路径。拥有父目录的可用性允许我们相对于 maskfile 本身加载文件,这在你有依赖其他外部文件的命令时可能很有用。

文档部分

如果一个标题没有代码块,它将被视为文档并被完全忽略。

示例:

## 这是一个没有脚本的标题

它作为记录像设置指南或命令可能依赖的所需依赖项
或工具的地方非常有用。

使用案例

这里有一些 mask 可能很有用的场景示例。

项目特定任务

你有一个项目,有很多随机的构建和开发脚本,或者一个笨重的 Makefile。你想通过拥有一个单一的、可读性强的文件来简化它,让你的团队成员可以添加和修改现有任务。

全局系统实用工具

你想要一个全局的 CLI 实用工具,用于各种系统任务,例如备份目录或重命名大量文件。通过为 mask --maskfile ~/my-global-maskfile.md 制作一个 bash 别名,这是很容易实现的。

FAQ

mask 是否作为库可用?

[mask-parser][mask_parser] 包是可用的。然而,它尚未文档化,也不被认为是稳定的。

灵感来自哪里?

我绝对不是第一个想到使用 Markdown 作为 CLI 结构定义的人。

我对 make 语法的挫败感是我寻找其他选项的原因。我有一段时间使用了 [just][just],这是一个相当好的改进。我最喜欢的 just 的特性是它支持其他语言运行时,这也是为什么 mask 也有这个能力!然而,它仍然没有一些我想要的功能,比如嵌套子命令和多个可选标志。

在寻找过程中,我偶然发现了 [maid][maid],这是 mask 大部分灵感的来源。我认为使用 Markdown 作为命令定义格式,同时仍然如此易于阅读,这是非常巧妙的。

那么,为什么我选择重新造轮子而不是使用 maid 呢?首先,我更喜欢安装一个单一的二进制文件,就像 just 那样,而不是安装一个带有数百个依赖项的 npm 包。我还有一些改进 maid 的想法,这就是为什么 mask 支持多级嵌套子命令以及可选标志和位置参数。另外...我真的很想用 Rust 再构建一些东西 :)

我还需要提到 [clap][clap] 和 [pulldown-cmark][cmark],它们是 mask 的核心部分,使得创建它变得如此容易。

附录

https://github.com/jacobdeichert/mask/tree/master

参考资料
[1]

maskfile.md: /maskfile.md

[2]

高级特性: #features

[3]

使用不同的 maskfile 运行 mask: #running-mask-with-a-different-maskfile

[4]

在脚本中运行 mask: #running-mask-from-within-a-script

编程悟道
自制软件研发、软件商店,全栈,ARTS 、架构,模型,原生系统,后端(Node、React)以及跨平台技术(Flutter、RN).vue.js react.js next.js express koa hapi uniapp Astro
 最新文章