4 个稳定版本

1.3.0 2023年6月14日
1.2.0 2023年6月5日
1.1.0 2023年6月1日
1.0.0 2023年5月23日

#261 in 构建工具

MIT 许可证

165KB
3.5K SLoC

mom-task

build codecov License: MIT

适用于团队和个人的任务运行器。使用 Rust 编写。

索引

灵感来源

灵感来自不同的工具,如 cargo-makego-taskdoskeybashdocker-compose

此项目是我之前的项目 yamis 的分支,该项目最初是一个简单的用于工作和个人项目的任务运行器,也是学习 Rust 的方式。我决定将其分支出来,以去除一些不必要的复杂性并改进 YAML 文件结构,即更接近现有工具。这也使用了更熟悉的二进制名称,寓意双关。

安装

Homebrew

如果您已安装 homebrew,可以使用以下命令安装 mom

$ brew tap adrianmrit/mom
$ brew install mom

使用 cargo

如果您已安装 cargo,可以使用以下命令安装 mom

$ cargo install mom-task

提示:请确保 ~/.cargo/bin 目录已添加到您的 PATH 环境变量中。

二进制发布

Windows、Linux和macOS平台下也有可用的二进制文件,请访问发布页面。安装方法:下载适合您系统的zip文件,解压,然后将二进制文件复制到所需位置。请确保包含二进制文件的文件夹已添加到PATH环境变量中。

JSON 架构

任务文件的JSON模式可在此处找到:链接

您可以将此模式配置到您的IDE中,以便自动完成和验证。

VSCode

要将模式与VSCode集成,您可以使用YAML扩展。安装后,您可以将以下内容添加到您的settings.json文件中

"yaml.schemas": {
  "https://raw.githubusercontent.com/adrianmrit/mom/main/json-schema/mom.json": [
    "mom.*.yml",
    "mom.*.yaml",
    "mom.yml",
    "mom.yaml",
  ]
}

您还可以通过在文件顶部添加以下内容将模式添加到特定文件中

# yaml-language-server: $schema=https://raw.githubusercontent.com/adrianmrit/mom/main/json-schema/mom.json
version: 1

快速开始

在项目的根目录下创建一个名为mom.root.yml的文件。

以下是一个非常基本的任务文件示例

# mom.root.yml
version: 1

vars:
  greeting: Hello World

tasks:
  hi:
    cmds:
      - echo {{ vars.greeting }}
  
  hi.windows:
    cmds:
      - echo {{ vars.greeting }} from Windows
  
  sum:
    cmds:
      - echo "{{ args.0 }} + {{ args.1 }} = {{ args.0 | int + args.1 | int }}"
  
  swear:
    condition: |
      {{ input(label="Are you sure you want to say that? (yes/no)") | lower == 'yes' }}
    cmds:
      - echo "!@#$%^&*()"

拥有mom文件后,您可以通过调用mom(任务名称)、任何参数来运行任务,例如:mom hi。参数可以直接在任务名称后传递,无论是按名称还是按位置,例如:mom sum 1 2

用法

命令行选项

如果您想将命令行选项传递给mom本身,则必须在任务名称之前传递,任何任务名称之后的参数都将被视为任务的参数。例如,如果您想运行全局任务,您需要在任务名称之前传递-g--global标志,例如:mom -g say_hi,而不是mom say_hi -g

某些命令行选项可以组合使用,例如:mom -gt(或mom -g -t)将列出全局任务,而mom -gl将给出全局任务文件的路径。

如果您想使用非标准任务文件,可以使用-f--file选项,例如:mom -f my_tasks.yml say_hi

要在干运行模式下运行任务,即不执行任何命令,可以使用--dry标志,例如:mom --dry say_hi

运行mom -hmom --help可以查看一些额外的命令行选项。

任务文件

任务使用YAML格式定义。

在调用任务时,程序将从工作目录开始,一直继续到根目录,按照一定顺序查找配置文件,直到找到任务、找到mom.root.{yml,yaml}文件或没有更多的父文件夹(到达根目录)。在区分大小写的系统中,这些文件的名称是大小写敏感的,例如,在Linux中mom.root.yml将无法工作。

优先顺序如下

  • mom.private.yml:应包含私有任务,且不应提交到仓库中。
  • mom.private.yaml:与上面相同,但为yaml格式。
  • mom.yml:应在项目的子文件夹中使用,用于特定文件夹和子文件夹的任务。
  • mom.yaml:与上面相同,但为yaml格式。
  • mom.root.yml:应包含整个项目的任务。
  • mom.root.yaml:与上面相同,但为yaml格式。

可以在~/mom/mom.global.yml~/mom/mom.global.yaml定义一个特殊任务文件,用于全局任务。要运行全局任务,需要传递--global-g标志,例如mom -g say_hi。这对于与特定项目无关的个人任务很有用。

还可以通过传递--file-f标志在不同的文件中定义任务,例如mom -f my_tasks.yml say_hi

虽然可以添加两种格式中的任何一种,即mom.root.ymlmom.root.yaml,但建议只使用一种格式,以确保一致性和避免混淆。

常用属性

可以在任务文件或任务本身中定义以下属性。任务的定义值优先于文件中的定义值。

  • wd:工作目录。
  • env:环境变量。
  • dotenv:包含环境变量的文件或文件列表。
  • vars:变量。
  • incl:可以包含或导入到Tera模板引擎中的模板。

wd

wd属性用于定义工作目录。默认为当前工作目录。可以在文件或任务中定义,任务的定义值优先于文件中的定义值。

路径可以是绝对路径或相对于文件位置的相对路径。要将工作目录设置为文件位置,使用wd: "."。或者,要将它设置为命令执行的目录,使用wd: ""。请注意,虽然在任务中可以设置wd: null,但它将被视为未定义wd,因此从父级继承。

env

env属性用于定义所有任务都可以访问的环境变量。属性的值是键值对的映射,其中键是环境变量的名称,值是环境变量的值。

执行的任务中定义的值优先于文件中定义的值。

通常情况下,可以通过三种方式访问环境变量,即使用tera标签,例如:{{ env.VAR }},只要它们被支持,使用tera函数get_env,例如:{{ get_env(name="VAR", default="default") }},或者通过shell展开,例如:$VAR${VAR}。但是请注意,在tera标签内部不支持shell展开。

注意,当使用tera标签访问环境变量时(即{{ env.VAR }}),系统环境变量不可用,只有文件或任务中定义的变量可用。要访问系统环境变量,请使用get_env函数或使用shell展开

参见

dotenv

dotenv属性用于定义文件中所有任务可用的环境变量。该属性的值是一个字符串,或包含包含环境变量的文件路径的字符串列表。路径可以是绝对路径,也可以是相对于文件位置的相对路径。

env属性中定义的值优先于使用dotenv属性定义的值。

vars

vars属性用于定义文件中所有任务可用的变量。这与env属性的行为类似,但变量不会被导出到环境变量中,并且可以比字符串更复杂。

例如,您可以定义一个变量如下

vars:
  user:
    age: 20
    name: John

然后像这样在任务中使用它

tasks:
  say_hi:
    cmd: echo "Hi, {{ vars.user.name }}!"

incl

incl属性用于定义文件中所有任务可用的Tera包括/模板。该属性的值是键值对映射,其中键是模板的名称,值是模板本身。然后可以在任务中使用具有名称incl.<name>的模板。

模板可以包含其他模板,但它们的定义顺序很重要。

例如,您可以定义一个模板如下

incl:
  say_hi: "Hi from {{ TASK.name }}!"
  say_bye: "Bye from {% include "incl.say_hi" %}!"

但是以下将不会工作

incl:
  say_bye: "Bye from {% include "incl.say_hi" %}!"
  say_hi: "Hi from {{ TASK.name }}!"

模板也可以在任务中定义,并且它们将优先于文件中定义的模板。

模板也可以用来定义。请参阅Tera的包含文档。

任务文件属性

除了通用属性外,任务文件中还可以定义以下属性

  • 任务:文件中定义的任务。
  • version:文件的较大版本。虽然目前没有使用,但它对于未来的兼容性是必需的。版本可以是数字或字符串。在撰写本文时,版本应该是1
  • extend:继承自的mom文件。

tasks

使用tasks属性来定义文件中的任务。属性的值是键值对映射,其中键是任务的名称,值是任务定义。

任务名称必须以ASCII字母字符或下划线开头,后跟任意数量的字母、数字、-_。名称也可以以.windows.linux.macos结尾,以定义一个操作系统特定的任务。您可以按照惯例,使用前导下划线命名私有任务,例如_private_task

文件扩展

在文件中,使用extend属性来定义要继承的mom文件。它可能是一个相对于文件位置的路径或路径列表,或是一个绝对路径。例如

version: 1

# Extend from a file in the same directory
extend: mom.base.yml

# Extend from a file in a subdirectory
extend: base/mom.base.yml

# Extend from multiple files
extend:
  - mom.base.yml
  - base/mom.base.yml

继承的值是

合并的值(文件值优先)是

dotenv在从文件继承或合并到父文件之前加载并与同一文件中的env合并。这意味着它被视为env的一部分。

任务属性

除了通用属性外,任务还可以具有以下属性

  • help:帮助消息。
  • script:要执行的脚本。
  • script_runner:用于解析脚本程序和参数的模板。
  • script_extension:脚本文件的扩展名。
  • script_extscript_extension的别名。
  • cmds:要执行的命令。
  • program:要执行的程序。
  • args:传递给程序的参数。
  • args_extend:传递给程序的参数,如果存在基本任务,则追加到这些参数。
  • args+args_extend的别名。
  • linux:在linux中执行的任务版本。
  • windows:在windows中执行的任务版本。
  • mac:在mac中执行的任务版本。
  • private:任务是否为私有。
  • 继承: 要继承的任务。

help

help 属性用于定义任务的帮助信息。属性的值是一个包含帮助信息的字符串。

与注释不同,当运行 mom -i <TASK> 时,会打印帮助信息。

condition

condition 属性用于定义执行任务的条件。属性的值是一个包含 Tera 模板的字符串。如果模板计算结果为 true,则执行任务,否则跳过。

只有 true(不区分大小写)值被视为真,所有其他值被视为假。这是因为 Tera 条件将返回 truefalse

示例

tasks:
  say_hi:
    condition: "{{ env.ENVIRONMENT == 'production' }}"  # will evaluate to true if ENVIRONMENT is production, false otherwise
    script: echo "Hi!"

这也可以根据某些条件选择不同的任务,例如。

tasks:
  greet:
    cmds:
      - task:
          condition: "{{ env.ENVIRONMENT == 'production' }}"
          script: echo "Hi!"
      - task:
          condition: "{{ env.ENVIRONMENT != 'production' }}"
          script: echo "Bye!"

脚本

⚠️警告:不要在脚本中将敏感信息作为参数传递。脚本存储在系统的临时目录中的文件中,由操作系统负责删除,但无法保证何时以及是否删除。因此,任何传递的敏感参数都可能无限期地保留。

任务内的 script 值将在命令行中执行(在 Windows 上默认为 cmd,在 Unix 上默认为 bash)。脚本可以包含多行,并包含 shell 内置程序和程序。

生成的脚本存储在临时目录中,文件名将是哈希值,这样如果脚本以前曾使用相同的参数调用,我们可以重用以前的文件,本质上作为缓存。

script_runner

script_runner 属性用于定义解析脚本程序和参数的模板。必须包含一个程序和一个 {{ script_path }} 模板,例如 python {{ script_path }}。参数的分离方式与 args 相同。此参数支持展开环境变量,如 `$A

script_extension

script_extension 属性用于定义脚本文件的扩展名。例如,对于 Python 脚本,可以是 py.py

程序

任务内的 program 值将以单独的进程执行,如果传递了 args,则将传递这些参数。

参数

任务内的 args 值将作为参数传递给程序,如果有的话。值是一个包含空格分隔的参数的字符串。包含空格的值可以用引号引起来作为单个参数处理,例如 "hello world"。引号可以用反斜杠转义,例如 \"

参数扩展

args_extend 的值将追加到 args(之间有空格),如果有的话。值与 args 的形式相同。

命令

cmds的值是一个要执行的命令列表。每个命令可以是字符串,或者包含一个task键的映射。

如果命令是字符串,它将以程序的方式执行,第一个值是程序,其余的是参数。参数的分隔方式与args相同。为了方便,echo是mom中的一个内置命令,因此相同的命令可以在Windows和Unix中正常工作。

如果命令是映射,task的值可以是要执行的任务的名称,或者要执行的任务的定义。

示例

tasks:
  say_hi:
    script: echo "hi"

  say_bye:
    cmds:
      - echo "bye"
  
  greet:
    cmds:
      - python -c "print('hello')"
      - task: say_hi
      - task:
          extend: say_bye

私有

private的值是一个布尔值,表示任务是否为私有。私有任务不能直接执行,但可以从其他任务继承。

任务扩展

在任务中,extend属性用于定义要继承的任务。它可能是一个字符串或字符串列表。例如

tasks:
  base_task:
    program: echo
    args: "Hello, world!"
  task:
    extend: base_task

  other_task:
    extend:
      - base_task
      - task

任务会合并,父任务优先于基础任务。

继承的值是

合并的值(父值优先)包括

就像在文件中一样,在从任务扩展或合并到父任务之前,dotenv被加载并与同一任务中的env合并。这意味着它被视为任务中env的一部分。

没有继承的值是

特定于操作系统的任务

可以为每个任务设置不同的OS版本。如果没有找到当前OS的任务,如果存在非OS特定的任务,它将回退到该任务。即

tasks:
  ls:
    script: "ls {{ args.0 }}"

  ls.windows:
    script: "dir {{ args.0 }}"

可以在单个键中指定OS特定的任务,即以下示例与上面的示例等效。

tasks:
  ls: 
    script: "ls {{ args.0 }}"

  ls.windows:
    script: "dir {{ args.0 }}"

请注意,OS特定的任务不会隐式地从非OS特定的任务继承,如果您想这样做,您必须显式地定义extend,即

tasks:
  ls:
    env:
      DIR: "."
    script: "ls {{ env.DIR }}"

  ls.windows:
    extend: ls
    script: "dir {{ env.DIR }}"

传递参数

任务参数可以是键值对传递,例如--name "John Doe",或者作为位置参数传递,例如"John Doe"

命名参数必须以一个或两个连字符开头,后跟一个ASCII字母或下划线,后跟任意数量的字母、数字、-_。值将是下一个参数或等于号后面的值,即--name "John Doe"--name-person1="John Doe"-name_person1 John都是有效的。请注意,"--name John"不是命名参数,因为它被引号包围且包含空格,然而"--name=John"是一个有效的命名参数。

导出的变量是

  • args:传递给任务的参数。如果任务使用mom say_hi arg1 --name "John"调用,那么args将变为["arg1", "--name", "John"]
  • kwargs:传递给任务的键值参数。如果任务使用mom say_hi --name "John"调用,那么kwargs将变为{"name": "John"}。如果相同的参数名多次传递,则取最后一个值。
  • pkwargs:与kwargs相同,但值为相同参数名的所有传递值的列表。
  • env:任务中定义的环境变量。注意,这不包括系统中定义的环境变量。要访问这些变量,请使用{{ get_env(name=<value>, default=<default>) }}
  • vars:任务中定义的变量。
  • TASK:任务对象及其属性。
  • FILE:文件对象及其属性。

命名参数也被视为位置参数,即如果传递--name John --surname=Doe,那么{{ args.0 }}将是--name{{ args.1 }}将是John,而{{ args.2 }}将是--surname="Doe"。因此,建议首先传递位置参数。

如果您想传递所有命令行参数,可以使用 {{ args | join(sep=" ") }},或者如果您想引用它们,可以使用 {% for arg in args %} "{{ arg }}" {% %}

参见

环境变量和变量继承

当同一环境变量(env)和变量(vars)的值存在于多个位置时,最具体(最近的)值将具有优先权。例如,使用 env 定义的值优先于使用 dotenv 定义的值,以及定义在任务中的 varsenv 优先于文件中定义的值。

例如,如果您有以下文件

version: 1

# Default values. The tasks can override these values.
env:
  ENV1: "env1"
  ENV2: "env2"

vars:
  VAR1: "var1"
  VAR2: "var2"

tasks:
  test1:
    env:
      ENV2: "test1_env2"
      ENV3: "test1_env3"
    vars:
      VAR2: "test1_var2"
      VAR3: "test1_var3"
    cmds:
      - echo "{{ env.ENV1 }} {{ env.ENV2 }} {{ env.ENV3 }}"
      - echo "{{ vars.VAR1 }} {{ vars.VAR2 }} {{ vars.VAR3 }}"
      
      # env and vars from the parent will take precedence
      - task: test2
      
      # This subtask will inherit the env and vars from the parent
      # but its own bases, envs and vars will take precedence
      - task:
          # Bases will take precedence over the parent task
          extend: test2

          # env and vars take precedence over the parent task and the bases
          env:
            ENV2: "subtask_env2"
          vars:
            VAR2: "subtask_var2"
  
  test2:
    env:
      ENV2: "test2_env2"
      ENV3: "test2_env3"
    vars:
      VAR2: "test2_var2"
      VAR3: "test2_var3"
    cmds:
      - echo "{{ env.VAR1 }} {{ env.VAR2 }} {{ env.VAR3 }}"
      - echo "{{ vars.VAR1 }} {{ vars.VAR2 }} {{ vars.VAR3 }}"

输出将是(不包括调试输出)

$ mom test1
env1 test1_env2 test1_env3
var1 test1_var2 test1_var3
env1 test1_env2 test1_env3
var1 test1_var2 test1_var3
env1 subtask_env2 test2_env3
var1 subtask_var2 test2_var3

这可能会有些令人困惑,所以让我们解释一下输出

env1 test1_env2 test1_env3
var1 test1_var2 test1_var3

这是 test1 任务中前两条命令的输出。 ENV1VAR1 只在文件中定义,而任务覆盖了 ENV2ENV3VAR2VAR3

env1 test1_env2 test1_env3
var1 test1_var2 test1_var3

这是 test1 任务中的第三条命令的输出,它调用了 test2。同样,ENV1VAR1 只在文件中定义。虽然 test2 覆盖了 ENV2ENV3VAR2VAR3,但父任务 test1 中定义的值将具有优先权。

env1 subtask_env2 test2_env3
var1 subtask_var2 test2_var3

这是 test1 任务中的第四条命令的输出,它调用了一个子任务。 ENV1VAR1 只在文件中定义。虽然看起来我们正在调用 task2,但我们实际上定义了一个从 task2 继承并覆盖 ENV2VAR2 的新任务。因此,从 task2 继承的值将优先于父任务。

shell 扩展

一些任务属性支持类似于 shell 的扩展。以下字符将被扩展

  • ~:主目录。
  • $VAR:环境变量 VAR 的值。
  • ${VAR}:环境变量VAR的值。

注意,虽然环境变量可以这样展开,但在Tera模板中不可用。也就是说,{{ $VAR }}将引发错误。您可以使用{{ env.VAR }}。有关详细信息,请参阅[env] (#env)。

以下任务属性支持shell展开

Tera 模板引擎

使用的模板引擎是Tera。语法基于Jinja2和Django模板。文档包含所需的所有信息,非常直观,因此此处不再重复。只需忽略Rust特定的部分。

参见

Mom 过滤器

exclude

从列表或映射中排除一个值。该值可以是字符串,也可以是字符串列表。

示例

tasks:
  test:
    vars:
      var1: "value1"
      var2: "value2"
      var3: "value3"
    script: echo "{{ env | exclude(val='var2') | json_encode() }} {{ [1, 2, 3] | exclude(val=2) }}"

输出

$ mom test
{"var1": "value1", "var3": "value3"} [1, 3]

Mom 函数

input

请求用户输入。需要一个label和一个default参数。虽然label必须是字符串,但default可以是任何类型。

还可以提供一个if参数,它必须是一个布尔值,并且必须与一个default参数一起使用。如果iffalse,则将返回default参数而无需请求用户输入。这是模板中if语句的简写。

示例

tasks:
  test:
    script: echo "{{ input(label='What is your name?', default='John Doe') }}"

输出

$ mom test
What is your name? [John Doe]: 
tasks:
  test:
    script: echo "{{ input(label='What is your name?', default='John Doe', if=args is containing("--default")) }}"
$ mom test --default
John Doe

密码

⚠️警告:如果在脚本中使用,密码仍然会以明文形式添加到脚本文件中。请谨慎使用。

输入类似,但输入不会回显到终端。

get_env

我们覆盖了默认实现,以便此方法还可以返回在mom文件中定义的环境变量,这些变量优先于系统环境变量。需要一个name参数,它必须是字符串,并且可以有一个可选的default参数,它可以任何类型。

示例

tasks:
  test:
    script: echo "{{ get_env(name='VAR1', default='default') }}"

输出

$ mom test
default
$ VAR1="value1"
$ mom test
value1

贡献

欢迎贡献!请首先阅读贡献指南

依赖关系

~12–22MB
~319K SLoC