前言

  代码开发的过程中可能遇到一些情况想要通过 代码 来自动执行命令行生成一些东西的情况。
  如果不使用框架进行管理,这些代码脚本就很零碎地散落在各个地方。
  因此就找到这个框架可以很方便管理多个任务,实现

Github 地址
官方说明文档

doit 的基本用法

  在 doit 执行命令的地方添加一个 dodo.py 的脚本
  doit 会去读取 dodo.py 里面命名开头为 task_ 的方法作为执行的命令。

1
2
3
4
5
6
7
8
9
10
11
def task_hello():
"""hello"""

def python_hello(targets):
with open(targets[0], "a") as output:
output.write("Python says Hello World!!!\n")

return {
'actions': [python_hello],
'targets': ["hello.txt"],
}

  比如添加上面的方法到 dodo.py 里面
  执行 doit list 可以罗列出当前的可执行的命令

1
2
3
4
F:\thm_git\adam_pose_editor>doit list
hello hello
F:\thm_git\adam_pose_editor>doit hello
. hello

  执行 doit hello 就会在 dodo.py 缩在目录下输出一个 hello.txt 的文件。
  这个就是 doit 的基本用法。

dodo.py 配置

https://pydoit.org/configuration.html

  可以使用 doit -f xxx/dodo.py 配置 dodo.py 的路径
  也可以使用 pyproject.toml 进行配置

1
2
[tool.doit]
dodoFile = "scripts/dodo.py"

task 配置

  dodo.py 的 task 支持导入
  只要是 task_ 前缀的方法就会自动识别。
  也可以给函数添加 create_doit_tasks 属性,这样就可以自动生成了。 文档链接

  利用这些机制,我搞了一个装饰器可以给 task 添加一个短名的方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def add_short_name(short_name):
"""Doit for short decorator.

Args:
short_name (str): short alias name.

Returns:
callable: decoartor function.
"""

def decorator(func):
globals()["task_{0}".format(short_name)] = func # noqa: WPS421
return func

return decorator

@add_short_name("pf")
def task_preflight():
"""Run pre commit for all files.

Returns:
dict: doit config.
"""
command = ["poetry", "run", "pre-commit", "run", "-a"]
return {"actions": [command], "verbosity": 2}

  这样运行 doit 会识别到两个 task ,可以分别通过 doit pf 或者 doit preflight 触发指令

1
2
3
>doit list 
pf Run pre commit for all files.
preflight Run pre commit for all files.

  但是默认排序是按命名来的,如果命令很多就会混在一起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>doit list 
b Run black format all python files.
black Run black format all python files.
d Run mkdocs serve.
dd Run mike to deploy docs.
docs Run mkdocs serve.
docs_deploy Run mike to deploy docs.
f Run `black` `isort`.
format Run `black` `isort`.
i Run isort format all python files.
isort Run isort format all python files.
l Run flakehell lint for all python files.
lint Run flakehell lint for all python files.
m Run mike serve.
mike Run mike serve.
pf Run pre commit for all files.
preflight Run pre commit for all files.
pt Run pytest.
pytest Run pytest.

  可以使用 doit list –sort=definition 的方式让排序变成创建顺序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>doit list --sort=definition
f Run `black` `isort`.
format Run `black` `isort`.
pf Run pre commit for all files.
preflight Run pre commit for all files.
b Run black format all python files.
black Run black format all python files.
i Run isort format all python files.
isort Run isort format all python files.
l Run flakehell lint for all python files.
lint Run flakehell lint for all python files.
pt Run pytest.
pytest Run pytest.
d Run mkdocs serve.
docs Run mkdocs serve.
m Run mike serve.
mike Run mike serve.
dd Run mike to deploy docs.
docs_deploy Run mike to deploy docs.

  但是每次使用都要加一个参数配置,那是相当的麻烦。
  我们可以利用 DOIT_CONFIG 进行配置 文档链接

1
2
3
DOIT_CONFIG = {
"sort": "definition",
}

task group

  可以使用 task_dep 的方式执行多个定义好的 task 文档链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import glob
DIR = os.path.dirname(__file__)
PY_FILES = glob.glob(os.path.join(DIR, "**/*.py"), recursive=True)
@add_short_name("f")
def task_format():
"""Run `black` `isort`.

Returns:
dict: doit config.
"""
return {"actions": None, "task_dep": ["black", "isort"]}

@add_short_name("b")
def task_black():
"""Run black format all python files.

Returns:
dict: doit config.
"""
command = ["poetry", "run", "black"] + PY_FILES
return {"actions": [command], "verbosity": 2}


@add_short_name("i")
def task_isort():
"""Run isort format all python files.

Returns:
dict: doit config.
"""
command = ["poetry", "run", "isort"] + PY_FILES
return {"actions": [command], "verbosity": 2}

  通过上面的配置就可以快速给所有的 python 脚本运行 black 和 isort

task 传参

文档链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def gen_api(api):
"""Generate API docs.

Args:
api (bool): flag to generate docs

Returns:
str: running command
"""
# NOTES(timmyliang): remove reference api
rmtree(os.path.join(DIR, "docs", "reference"), ignore_errors=True)
script_path = os.path.join(DIR, "docs", "gen_api_nav.py")
api_command = " ".join(["poetry", "run", "python", script_path])
serve_command = " ".join(["poetry", "run", "mkdocs", "serve"])
return f"{api_command} & {serve_command}" if api else serve_command


@add_short_name("d")
def task_docs():
"""Run mkdocs serve.

Returns:
dict: doit config.
"""
return {
"actions": [CmdAction(gen_api)],
"params": [
{
"name": "api",
"short": "a",
"type": bool,
"default": False,
"inverse": "flagoff",
"help": "generate api docs",
},
],
"verbosity": 2,
}

  通过 params 定义传入的参数,就可以控制 mkdocs 是否自动生成 api 的 markdown 脚本。

总结

  目前我使用上面的写法已经很够用了,其实它还有很多其他的配置可以用来做 C 编译。
  还可以定义 task 依赖 和 文件依赖,确保 task 的执行顺序。
  整体而言,doit 是个非常简单而是用的框架,配置 tox 等工具可谓是锦上添花。