调用任务

本页将从解析器机制(如何将任务的参数公开为命令行选项)和执行策略(哪些任务实际运行,以及按什么顺序)两方面解释如何在cli上调用任务。

(有关Invoke的核心标志和选项的详细信息,请参见 inv[oke] 核心用途

基本命令行布局

调用可以执行为 invoke (或) inv 简而言之)它的命令行布局如下:

$ inv [--core-opts] task1 [--task1-opts] ... taskN [--taskN-opts]

坦白地说,invoke's CLI parser 将命令行拆分为多个“parser contexts”,允许它对将接受的参数和选项进行推理:

  • 在给出任何任务名称之前,解析器处于“core”解析上下文中,并查找核心选项和标志,如 --echo--list--help .

  • 任何非参数类型的标记(例如 mytask )导致切换到每个任务上下文(如果在 loaded collection

  • 此时,像标记这样的参数应该与先前命名任务的参数相对应(请参见 任务命令行参数

  • 然后这个循环无限重复,允许链式执行任意数量的任务。(实际上,大多数用户一次只执行一个或两个。)

有关核心参数和标志,请参见 inv[oke] 核心用途 ;有关任务如何影响cli的详细信息,请继续阅读。

备注

对于解析上下文的行为有一个小小的例外:核心选项 may 也可以在每个任务上下文中给出, 当且仅当 与正在分析的任务的类似命名/前缀参数没有冲突。

例如, invoke mytask --echo 会表现得和 invoke --echo mytask除非 mytask 有它自己的 echo 标志(在这种情况下,该标志会像正常情况一样传递给任务上下文)。

同样地, invoke mytask -e 也将启用命令回显,除非 mytask 有自己的参数,其shortflag设置为 -e (例如) def mytask(env)

任务命令行参数

最简单的任务调用,对于不需要参数化的任务:

$ inv mytask

任务可以采用标志参数形式的参数:

$ inv build --format=html
$ inv build --format html
$ inv build -f pdf
$ inv build -f=pdf

请注意,长样式标志和短样式标志都受支持,而等号在这两种情况下都是可选的。

布尔选项是没有参数的简单标志:

$ inv build --progress-bar

当然,一次可以给出多个标志:

$ inv build --progress-bar -f pdf

型铸造

从本机上讲,命令行字符串就是——一个字符串——需要一些逻辑跳跃才能在python端得到任何非字符串值。invoke已经掌握了许多这样的技巧,将来还会实现更多的技巧。目前:

  • 具有默认值的参数使用这些默认值作为类型提示,因此 def mytask(c, count=1) 将看到 inv mytask --count=5 并得到python整数值 5 而不是字符串 "5" .

    • 默认值 None 实际上与完全没有默认值是一样的-不进行类型转换,只留下一个字符串。

  • 前一个规则的主要异常是booleans: TrueFalse 使这些参数显示为实际的非值接受标志 (--argname 将值设置为 True 如果默认为 False--no-argment 在相反的情况下)。见 自动布尔逆标志 更多示例。

  • 列表值(无论如何,您都不想将其设置为参数的默认值——这是python的一个常见错误)由一个特殊的 @task 旗见 Iterable标志值 下面。

  • 目前没有办法在命令行上设置其他复合值(如dict);解决这个更复杂的问题留给读者作为练习(尽管我们将来可能会为此类事情添加帮助程序)。

按任务帮助/打印可用标志

若要获得特定任务的帮助,可以将任务名称作为参数传递给核心。 --help/-h 选择,或给予 --help/-h 在任务之后(这将触发自定义的-help``行为,其中任务名称本身被赋予 ``--help 作为其参数值)。

当请求帮助时,您将看到任务的docstring(如果有的话)和每个参数/标志的帮助输出:

$ inv --help build  # or invoke build --help

Docstring:
  none

Options for 'build':
  -f STRING, --format=STRING  Which build format type to use
  -p, --progress-bar          Display progress bar

全球短旗

布尔短标志可以组合成一个标志表达式,例如:

$ inv build -qv

相当于(并在解析期间扩展为):

$ inv build -q -v

如果globbed short flag标记中的第一个标志不是布尔值,而是接受一个值,则将glob的其余部分改为该值。例如。::

$ inv build -fpdf

扩展为:

$ inv build -f pdf

not ::

$ inv build -f -p -d -f

可选标志值

你看到这个暗示 --help 特别是,如果声明为 optional . 例如,假设您的任务有 --log 激活日志记录的标志:

$ inv compile --log

但你也希望它是可配置的 在哪里? 登录::

$ inv compile --log=foo.log

你可以用一个附加的参数(例如 --log--log-location )但有时简洁的api更有用。

若要启用此功能,请在内部指定此“混合”可选值类型的参数 @task ::

@task(optional=['log'])
def compile(c, log=None):
    if log:
        log_file = '/var/log/my.log'
        # Value was given, vs just-True
        if isinstance(log, str):
            log_file = log
        # Replace w/ your actual log setup...
        set_log_destination(log_file)
    # Do things that might log here...

使用可选标志值时,分析后看到的值遵循以下规则:

  • 如果旗子根本不给的话 (invoke compile )默认值按正常值填写。

  • 如果给它一个值 (invoke compile --log=foo.log )然后正常存储值。

  • 如果给定的标志没有值 (invoke compile --log ,它被视为 bool 并设置为 True .

解决歧义

对于采用可选值的标志,有许多情况下可能会产生歧义:

  • 当一个任务接受位置参数时,当解析器到达可选值标志时,它们还没有全部被填充;

  • 当这些标志中的一个后面的标记看起来本身就是一个标志时;或者

  • 当该令牌与另一个任务同名时。

在大多数情况下,invoke的解析器将 refuse the temptation to guess 并提出一个错误。

但是,如果二义性标记类似于标志,则会检查当前解析上下文以解决二义性:

  • 如果令牌是合法的参数,则假定用户打算在当前参数之后立即给出该参数,并且不设置可选值。

    • 例如在 invoke compile --log --verbose (假设) --verbose 是另一个合法的理由 compile )解析器决定用户打算给 --log 没有值,然后 --verbose 旗帜。

  • 否则,将逐字解释令牌并将其存储为当前标志的值。

    • 例如如果 --verbosenot 正当理由 compile 然后 invoke compile --log --verbose 使分析器指定 "--verbose" 作为给定的值 --log . (这可能会在我们设计的用例中引起其他问题,但它说明了我们的观点。)

Iterable标志值

cli程序的一个常见用例是希望为给定的选项构建一个值列表,而不是单个值。虽然这 can 通过子字符串解析完成——例如让用户使用 --mylist item1,item2,item3 在逗号上拆分——通常最好多次指定选项并将值存储在列表中(而不是重写或出错)。

在invoke中,通过向解析器暗示一个或多个任务参数是 iterable 本质上(类似于 optionalpositional ):

@task(iterable=['my_list'])
def mytask(c, my_list):
    print(my_list)

如果根本没有给定,则 my_list 将是一个空列表;否则,结果是一个列表,按顺序追加看到的每个值,而不进行任何其他操作(因此无重复数据消除等):

$ inv mytask
[]
$ inv mytask --my-list foo
['foo']
$ inv mytask --my-list foo --my-list bar
['foo', 'bar']
$ inv mytask --my-list foo --my-list bar --my-list foo
['foo', 'bar', 'foo']

递增标志值

这可以说是 iterable flag values (如上所示)-它具有相同的核心接口“多次给出一个cli参数,并使其执行除错误或覆盖单个值以外的操作”。但是,“incrementables”(如您可能猜到的)会增加一个整数,而不是生成一个字符串列表。这通常出现在详细标志和类似功能中。

举个例子:

@task(incrementable=['verbose'])
def mytask(c, verbose=0):
    print(verbose)

它的用途:

$ inv mytask
0
$ inv mytask --verbose
1
$ inv mytask -v
1
$inv mytask -vvv
3

很高兴,因为在python中 0 是假的 1 (或任何其他数字)是“truthy”,它的功能也很像布尔标志,至少有一个默认值是 0 .

备注

您可以为此类参数提供任何整数默认值(它只是作为起始值),但请注意,参数的使用者在编写时应理解,除非 0 你说什么?

标志名称中的破折号与下划线

在python中,使用 underscored_names 对于关键字参数,例如:

@task
def mytask(c, my_option=False):
    pass

但是,命令行标志的典型约定是破折号,它在python标识符中无效:

$ inv mytask --my-option

invoke通过自动生成带下划线名称的虚线版本来解决这个问题,当它将任务函数签名转换为命令行解析器标志时。

因此,上面的两个例子实际上很好地结合在一起-- my_option 最终映射到 --my-option .

此外,领先 (_myopt )和拖尾 (myopt_ )下划线被忽略,因为 invoke ---myoptinvoke --myopt- 没什么意义。

自动布尔逆标志

当设置通常是 False ,至 True ::

$ inv mytask --yes-please-do-x

但是,在某些情况下,您希望相反的结果是 True ,它可以很容易地被禁用。例如,彩色输出:

@task
def run_tests(c, color=True):
    # ...

在这里,我们真正想要的是命令行 --no-color 设置的标志 color=False . invoke为您处理这个问题:设置cli标志时,boolean默认为 True 生成一个 --no-<name> 改为打旗子。

任务如何运行

基本情况

在最简单的情况下,没有前置或后置任务的任务只运行一次。例子::

@task
def hello(c):
    print("Hello, world!")

执行::

$ inv hello
Hello, world!

任务前和任务后

应该在任务之前或之后执行另一个任务的任务,可以使用 @task 分离器 pre 和/或 post 夸格斯,就像这样:

@task
def clean(c):
    print("Cleaning")

@task
def publish(c):
    print("Publishing")

@task(pre=[clean], post=[publish])
def build(c):
    print("Building")

执行::

$ inv build
Cleaning
Building
Publishing

这些关键字参数总是带iterables。为了方便起见,pre tasks(仅限pre tasks)可以作为位置参数给出,其方式类似于 make . 我们可以将上述示例的一部分表示为:

@task
def clean(c):
    print("Cleaning")

@task(clean)
def build(c):
    print("Building")

像以前一样, invoke build 会导致 clean 然后跑 build .

递归/链式前/后任务

前置任务的前置任务也将以深度优先的方式递归调用(前置任务的后置任务、后置任务的前置任务等也将如此)。下面是一个更复杂(如果稍微做作的话)的任务文件:

@task
def clean_html(c):
    print("Cleaning HTML")

@task
def clean_tgz(c):
    print("Cleaning .tar.gz files")

@task(clean_html, clean_tgz)
def clean(c):
    print("Cleaned everything")

@task
def makedirs(c):
    print("Making directories")

@task(clean, makedirs)
def build(c):
    print("Building")

@task(build)
def deploy(c):
    print("Deploying")

有了深度优先的行为,下面希望对大多数用户来说是直观的:

$ inv deploy
Cleaning HTML
Cleaning .tar.gz files
Cleaned everything
Making directories
Building
Deploying

参数化前/后任务

默认情况下,执行前任务和后任务没有参数,即使触发其执行的任务被赋予了一些。当这不合适时,您可以用 call 允许您指定调用签名的对象:

@task
def clean(c, which=None):
    which = which or 'pyc'
    print("Cleaning {}".format(which))

@task(pre=[call(clean, which='all')]) # or call(clean, 'all')
def build(c):
    print("Building")

示例输出:

$ inv build
Cleaning all
Building

任务重复数据消除

默认情况下,在会话期间运行多次的任何任务(例如,由于包含在前/后任务中)将只运行一次。任务文件示例:

@task
def clean(c):
    print("Cleaning")

@task(clean)
def build(c):
    print("Building")

@task(build)
def package(c):
    print("Packaging")

关闭重复数据消除后(见下文),将执行上述操作 clean > build > build 再次> package . 通过重复数据消除, build 不会发生:

$ inv build package
Cleaning
Building
Packaging

备注

参数化预任务(使用 call )根据参数列表进行重复数据消除。例如,如果 clean 以两种不同的方式被参数化并作为一个前置任务连接起来。 call(clean, 'html')call(clean, 'all') -如果它们都在同一会话中运行,则不会被重复数据消除。

However, two separate references to call(clean, 'html') would become deduplicated.

禁用重复数据消除

如果你喜欢你的任务,无论何时运行,不管怎样,你可以给 --no-dedupe 在运行时设置core cli选项,或设置 tasks.dedupe config settingFalse . 虽然这在现实世界中毫无意义,但让我们想象一下我们想申请 --no-dedupe 对于上面的示例;我们将看到以下输出:

$ inv --no-dedupe build package
Cleaning
Building
Building
Packaging

构建步骤现在运行两次。