需求

目前很多文档项目都是用 Markdown 编写的,一个 .md 文件为一个文章放在目录中,在阅读时或许需要手动翻看目录按文件名挑选文章或查看作者手动编辑的目录。

GitHub 的文件目录界面

那么利用 GitHub Actions 这样一个持续集成服务,就可以完成当主分支有更新时自动运行一个脚本将指定目录中的所有文件更新到根目录 README.md 的目录位置中。本文所介绍的功能可以见 minoic/stackoverflow-top-cpp 项目,目前可以正常工作。

编写脚本

既然是定期运行的脚本,那么肯定要有能完成任务的脚本才行,在 PythonGo 之间还是选择了后者,因为用的更熟练且跑起来都是一样的。

package main

import (
    "fmt"
    "io"
    "io/fs"
    "os"
    "path/filepath"
    "strings"
)

func main() {
    readme, err := os.OpenFile("README.md", os.O_RDWR, os.ModePerm)
    if err != nil {
        panic(err)
    }
    readmeB, err := io.ReadAll(readme)
    if err != nil {
        panic(err)
    }
    readmeS := string(readmeB)
    flag := "<!-- catalog -->"
    splits := strings.Split(readmeS, flag)
    ctl := strings.Builder{}
    ctl.WriteString("\n\n")
    err = filepath.Walk("question", func(path string, info fs.FileInfo, err error) error {
        if info.IsDir() || !strings.Contains(info.Name(), ".md") {
            return nil
        }
        ctl.WriteString(fmt.Sprintf("- [%s](%s)\n", strings.Trim(info.Name(), "[]"), strings.ReplaceAll(path, " ", "%20")))
        return nil
    })
    readmeS = splits[0] + flag + ctl.String() + "\n" + flag + splits[2]
    if err != nil {
        panic(err)
    }
    err = readme.Truncate(0)
    if err != nil {
        panic(err)
    }
    _, err = readme.Seek(0, 0)
    if err != nil {
        panic(err)
    }
    _, err = readme.WriteString(readmeS)
    if err != nil {
        panic(err)
    }
}

这段代码的目的是将 question 文件夹中的所有文件,建立一个相对地址链接到根目录的 README.md 中。程序会将两个 <!-- catalog --> 注释标志之间的内容删除并将所生成的目录以无序列表的方式置于其中。该脚本文件可以保存在任意位置,我这里保存在 script/catalog.go

文件目录结构如图所示

文件目录结构

question 文件夹内容

运行效果图

相对目录链接经过测试本地 Goland 和 GitHub 的渲染器都可以成功索引,GitHub 会自动将链接点击操作跳转到对应的文章所在地址。

编写 Workflow

有了这个脚本,虽然我们可以每次写完文章,手动 go run 一次,然后再提交代码,但这样做毕竟不够优雅,可以借助 GitHub Actions,在每次主分支更新时自动运行这个脚本并提交更改。

可以点击 Repo 页面下方的 Actions -> New workflow 来创建,也可以在 .github/workflows/ 目录下创建 catalog.yml 文件,文件内容如下:

name: minoic-auto-catalog

on:
  push:
    branches: [ "master" ]
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Set up Go
      uses: actions/setup-go@v3
      with:
        go-version: 1.18

    - name: Run
      run: go run -v ./scripts/catalog.go

    - name: Commit files
      run: |
        git config --local user.name "minoic-auto-catalog[bot]"
        git config --local user.email "minoic-auto-catalog[bot]@users.noreply.github.com"
        git commit -a -m 'Automated catalog update'
      continue-on-error: true

    - name: Create Pull Request
      uses: peter-evans/create-pull-request@v3
      with:
        title: '[Bot] Automated catalog update'
        committer: GitHub <noreply@github.com>
        author: minoic-auto-catalog[bot] <minoic-auto-catalog[bot]@users.noreply.github.com>
        # 对应的上传分支
        branch: t/bot/minoic-auto-catalog

首先 on 中的内容意味着发生哪些事件会运行该 workflow,这里我设置了 pushworkflow_dispatch,分别指主分支修改和手动启动按钮。不过这里有两个问题:1. pr is merged 事件怎么办?push 是包含合并事件的,并且一次 push 多个 commit 也只会触发一次,并没有特定的 pr is merged 事件,详见 Trigger Github Actions only when PR is merged 2. push 之后,bot 再向主分支自动发起 push 来更新目录,不会反复操作无限套娃吗?答案是会运行 workflow 但由于没有更改,所以不会产生新的 push 事件或发起 PR。

下面定义了一个包含多个步骤的 job,其中 go run -v ./scripts/catalog.go 可根据需要更改脚本地址,后面两步是 git 的提交和向 master 分支发起 PR 的操作,当你成功修改了文档并 push 到主分支后不久就会收到一个来自 bot 的 PR,简单 review 一下点击合并即可更新目录。

当然也可以将最后一个 step 更改为直接 push 到 master 分支(跳过需要人工 merge 的步骤),这将需要用到项目管理员的 GitHub Token,详见 dotnet 在 GitHub 的 Action 上部署自动代码编码规范机器人

由Bot发起的PR

结语

本功能和文章为刚接触 GitHub actions 的新手作品,市面上没有搜索到类似的功能,只有上面的自动代码编码规范机器人流程类似,如需多级目录或其它更高级的操作可以自行修改脚本实现,仅供参考。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注