gomarkdocを使ってGoのソースコードからpkg.go.dev風Markdownを生成する

## はじめに

設計とコードの整合性についての話は昔からよくある議題ですよね。  
実装コードは直したけど文書の修正を忘れていて、気付けばコードと異なる仕様が文書に残ってしまったことは一度や二度では無いでしょう。

Go言語にはとても親切な [Go Doc](https://tip.golang.org/doc/comment) が提供されており、Go言語に関わったことがあれば https://pkg.go.dev でサードパーティ製のドキュメントを目にしたことがあるはずです。  
しかし、プライベートなパッケージやhtmlではない方法でGo docと同様の内容が読みたいこともあります。  
今回は [gomarkdoc](https://github.com/princjef/gomarkdoc) を使ってGithub ActionsでコードからMarkdownを生成することで、Githubサイトでプレビューしやすい文書にしてみたいと思います。


## Install go 1.21.6 on Ubuntu 20.04

これは導入のためのサンプル手順です。  
お手元のGo言語開発環境がある場合は不要です。

```
$ sudo -i
# curl -LO https://go.dev/dl/go1.21.6.linux-amd64.tar.gz
# tar -C /usr/local -xzf go1.21.6.linux-amd64.tar.gz
# exit
$ mkdir ~/bin
$ ln -s -r /usr/local/go/bin/go ~/bin/go
$ go version
go version go1.21.6 linux/amd64
$ vi ~/.bashrc
# Add go/bin path
if [ -d "$HOME/go/bin" ] ; then
    PATH="$HOME/go/bin:$PATH"
fi
$ . ~/.bashrc
```


## Create sample project

適当なプロジェクトをGithubで作成し、ログメッセージを出力して終了するだけのサンプルコードを作成します。  
Githubで `gomarkdoc-sample` プロジェクトを作って、それを使います。

:::note warn
URLは削除済みのプライベートリポジトリなので一部伏せています。  
また、実際に外部から使うわけでは無いのでモジュールパスはダミーです。
:::

```
$ git clone git@github.com:<your-name>/gomarkdoc-sample.git
$ cd gomarkdoc-sample
$ go mod init example.com/qiita/gomarkdoc-sample
go: creating new go.mod: module example.com/qiita/gomarkdoc-sample
go: to add module requirements and sums:
        go mod tidy
$ cat main.go 
package main

import (
        "fmt"
        "log/slog"
)

func SomeInfo(message string) {
        slog.Info(fmt.Sprintf("Hello, %s", message))
}

func main() {
        SomeInfo("test")
}
$ go run main.go 
2024/01/14 11:06:04 INFO Hello, test
```


## gomarkdocによる文書生成

それではgomarkdocをインストールしましょう。  
インストール方法は https://github.com/princjef/gomarkdoc に書いてある通りです。

```
$ go install github.com/princjef/gomarkdoc/cmd/gomarkdoc@latest
$ gomarkdoc --version
v1.1.0
```

パスが通っていれば動くはずです。  
パスが通っていない場合は `~/go/bin/gomarkdoc` でも動きますね。

次に先ほどのサンプルコードを対象にMarkdownを生成します。

~~~
$ gomarkdoc --output doc/main.md example.com/qiita/gomarkdoc-sample
$ cat doc/main.md 
<!-- Code generated by gomarkdoc. DO NOT EDIT -->

# gomarkdoc\-sample

```go
import "example.com/qiita/gomarkdoc-sample"
```

## Index

- [func SomeInfo\(message string\)](<#SomeInfo>)


<a name="SomeInfo"></a>
## func [SomeInfo](<https://github.com/<your-name>/gomarkdoc-sample/blob/main/main.go#L8>)

```go
func SomeInfo(message string)
```



Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
~~~

今のところ公開している関数が無いので出力はほとんどありません。  
このファイルをリポジトリに追加して、Github上で見てみましょう。

Github上での見た目は次の画像のようになります。  
見慣れた https://pkg.go.dev と似た感じの出力で、コードへのリンクも機能します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613240/f5807027-341d-8456-044b-32fefef64d26.png)


## Github ActionsでMarkdownを生成&比較

Github Actionsでもgomarkdocを使ってMarkdownファイルを生成し、差分が発生するかどうかをチェックするようにしてみましょう。

以下のように `.github/workflows/gomarkdoc.yml` を作成して、git pushしてみます。

```yaml
name: gomarkdoc-sample

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  doc:
    runs-on: ubuntu-20.04
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-go@v4
      with:
        go-version: 1.21.6
    - name: Ensure doc update
      run: |
        go install github.com/princjef/gomarkdoc/cmd/gomarkdoc@latest
        gomarkdoc --output doc/main.md example.com/qiita/gomarkdoc-sample
        git diff --exit-code doc
```

さて、Github Actionsでの結果は...と言うと、以下のように差分が発生して失敗しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613240/c1a995b4-7eeb-bec4-1326-3b7a9e0a907b.png)

コードを変更したのに文書を変更し忘れたことを検出できそうです。  
でも、この差分のリンクは文書の更新忘れとは違うもののようです。

[gomarkdoc - Additional Options](https://github.com/princjef/gomarkdoc?tab=readme-ov-file#additional-options) によると、どうやらローカルでMarkdownファイルを生成した時はgitの設定を自動的に読み込んでいたようです。

そこで、以下のように `.github/workflows/gomarkdoc.yml` を変更して、再度pushしてみます。

```diff
diff --git a/.github/workflows/gomarkdoc.yml b/.github/workflows/gomarkdoc.yml
index a29bd30..30d80d8 100644
--- a/.github/workflows/gomarkdoc.yml
+++ b/.github/workflows/gomarkdoc.yml
@@ -19,6 +19,6 @@ jobs:
     - name: Ensure doc update
       run: |
         go install github.com/princjef/gomarkdoc/cmd/gomarkdoc@latest
-        gomarkdoc --output doc/main.md example.com/qiita/gomarkdoc-sample
+        gomarkdoc --repository.url "${{ github.server_url }}/${{ github.repository }}" --repository.default-branch "main" --repository.path "/" --output doc/main.md example.com/qiita/gomarkdoc-sample
         git diff --exit-code doc
 
```

今度は成功しました。  
ローカルで生成したものとGithub Actionsで生成したもので差分が無いので、コードと一緒に文書も正確に更新されていることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613240/8885b5a3-83fc-062e-d2ef-14dbf4d3a442.png)

Markdown生成時のオプションに `repository.url`, `repository.default-branch`, `repository.path` の3点を与えることでローカルgitの内容が不明な場合でも、文書のリンク生成に必要なパラメータを解決できます。


## おわり

今回は、gomarkdocを使ってGoコードからGithubの画面でも見やすいMarkdownを作成できることを確認しました。

コードの更新に合わせて文書が更新されるのは、DoxygenやJavaDocなど昔からよく使われる方法だと思います。  
今回使用したgomarkdocはGo言語の標準であるGo DocをベースにMarkdownが生成できるので、Go言語開発では比較的気軽に導入できると思います。

GithubでMermaid.jsを表示できるようにもなったので、コードコメントにMermaid.jsを書いてシーケンス図をGithub上で表示できないかと思ったのですが、やはりGo Docがベースと言うこともあり難しいようです。  
https://github.com/princjef/gomarkdoc/issues/78 で軽い提案はあったようですが、今のところ動きは無いようです。

開発チームに合わせた適切な文書管理が実現できるように、色々な方法を探りたいものです。それでは。