Tem:样板文件复制器

2023-09-25, 星期一, 22:12

MAKEGolang

你可以通过 Tem - GitHub 获取项目的源代码。

唠嗑

我在 $HOME/Public/ 目录下专门设置了一个文件夹 /templates,里面放了一堆什么 *.gitignore*.json 之类的文件。每次从 Spring Initializr 弄下来什么工程,或是 yarn init 创了一个项目,就得 cp $HOME/Public/templates/ 然后 Tab Tab 一会儿选中对应的文件。如果需要访问私有的 Maven 仓库,再 cp 一个 settings.xml。如果是老项目,就得 ii(这好像是一个 Cmd / PowerShell 下的命令,可能是早年我 alias 来的)一下,然后拷贝一份修改了镜像地址的 Maven Wrapper 来。如果是个打算内部推广的 Golang CLI 工具(最近常借着 copilot 学习写工具,这就是其中一个),那就 cp 一份 go-cross-compile.sh

Tab Tab 的问题在于它没法选到隐藏文件(至少默认配置是这样),而好多配置文件都是 . 开头的,这样久一点不用就想不起来文件叫什么名字了。而且像 Maven Wrapper 这样又是文件又是目录的,有时候感觉 Finder 还会快一些。

我这个需求既简单又杂乱,因此用 yeoman 这样的脚手架生成器就有些杀鸡用牛刀。差不多 2020 年的时候研究了下 Zsh,写了个支持自动补全的脚本,大概长这样:

#!/bin/zsh
function tem() {
    GITIGNORE_JAVA="$CT_TEMPLATES_PATH/git/gitignore-java"
    UML_CLASS="$CT_TEMPLATES_PATH/plantuml/class"
# ...
    case $1 {
        # gitignore
        (gitignore-java)
        cp $GITIGNORE_JAVA .gitignore
        ;;
# ...
        (uml-class);&
        (class)
        TARGET_FILE=$2
        cp $UML_CLASS ${TARGET_FILE:-class.wsd}
        ;;
# ...
    }
}
compdef _autocomplate_template tem
function _autocomplate_template() {
    local -a templates
    # 获取 misc/ 下的所有文件,文件名作为提示符
    for i ($CT_TEMPLATES_PATH/misc/*) {
        templates+=($i:t)
    }
    templates+=(gitignore-java uml-class class)
    # ...
    _describe 'command' templates
}
  • Tab Tab 能触发 Zsh 的补全动作
  • $HOME/Public/templates/misc/ 下的同名文件能自动注册
  • 部分样板接受额外参数来构造目标文件名
  • 支持样板别名
  • 支持拷贝一组样板文件

不过我的 Zsh 水平止步于此,很难再做什么改进了。

所以搞了个 CLI 工具

首次安装或升级后需要执行 tem init。tem 会检查 $HOME/.tem/ 目录,并替换 $HOME/.tem/default/ 的内容,最终效果就是:

$ tree /Users/yufan/.tem
├── custom
│   └── custom.toml
└── default
    ├── default.toml
    ├── git
    │   ├── java.gitignore
    ...

default/ 目录预置了一些样板文件,default/default.toml 描述了复制这些样板文件需要输入的命令。

如果用户还没有编写自定义配置,tem init 也会创建一个 custom/custom.toml 文件作为示例。虽然 tem 不会碰用户创建的 custom/ 配置,还是备份一下以防万一比较好。

用户自定义配置和基础配置遵循相同的语法。以tem init 生成的示例 custom/custom.toml 为例:

[[template]]
key = "maven-wrapper"
assets = [
    ["custom/maven/mvnw", "mvnw"],
    ["custom/maven/mvnw.cmd", "mvnw.cmd"],
    ["custom/maven/maven-wrapper.jar", ".mvn/wrapper/maven-wrapper.jar"],
    ["custom/maven/maven-wrapper.properties", ".mvn/wrapper/maven-wrapper.properties"],
]

[[template]]
key = "golang"
assets = [
    ["https://www.toptal.com/developers/gitignore/api/goland+all,macos", ".gitignore"]
]

[config]
imports = [
    "custom/some-custom-config-pull-from-repos/custom.toml",
    "custom/like-github/custom.toml"
]

每一个 [[template]] 代表一组样板文件配置(我也不是很喜欢这种 List of Object 的语法),key 是这组样板文件的名称。assets 中的每一项都是一个数组,第一个元素是源,如果是文件的话,使用相对 $HOME/.tem/ 的路径,如果是个 URL,tem 会请求这个 URL 并把响应写入目标文件。第二个元素是相对当前目录的目标文件路径,如果路径上的文件夹还不存在,tem 会创建。

[config] 是用来加载子配置的,imports 字段指示了其他需要载入的配置文件相对 $TEM_HOME 的路径。模版配置的加载使用深度优先的方案,即读取到子配置文件就先加载子配置文件中的内容,因此需要将可能重名且优先度较高的的模版 key 放在加载队列的后方。

输入 tem add maven-wrapper -y,tem 就会在当前目录下准备好 Maven Wrapper 需要的文件。输入 tem add golang -y,tem 就会从 Toptal 的服务下载融合了 goland + macos +linux + ... 需要的 .gitignore 文件。

为了防止时间久远后记不起执行命令的效果,试试不要键入 -y,tem 会告诉你接下来准备干什么。

custom/custom.toml 中编写相同的 key 可以覆盖默认行为,不过如果用到了 default/ 中的内容,还是建议复制一份到 custom/,免得将来 tem 更新了默认配置。

tem 通过 github.com/spf13/cobra 实现对命令行参数的解析,顺便得到了自动补全功能,支持 bash、Zsh、fish、PowerShell 等 shell。

以 Zsh 为例,执行如下命令,在新的会话中即可使用 Tab 补全。

$ tem completion zsh > "$ZSH_CACHE_DIR/completions/_tem"

后面的打算

  • 读取环境变量填充模版

再后边就应该差不多了,毕竟我可没打算做什么项目脚手架工具,那种事情还是交给 yeoman 和 cobra 比较好。