MakefileのドキュメンテーションツールをGoで書いた

  • 13
    いいね
  • 1
    コメント

MakefileのドキュメンテーションツールをGoで書いた

この記事は Go (その2) Advent Calendar 2016 21日目の記事です。

開発時のタスクランナーがわりにMakefileをよく使います。
make build したらビルドするとか make fmt したら go fmt するとか、 make server したらサーバを起動するとかです。
中には make build target=linux のようにオプションも渡したくなったりします。
しかしMakefileのタスクとその使い方をすぐに忘れてしまってMakefileの中を確認するということがよくあるので、
ドキュメントを表示してくれるようなコマンドをGoで書いてみました。

以下のように記述されたMakefileがあったとします。

.PHONY: command
command:
    @## param1=value1 param2=value2
    @#
    @# document 1
    @# document 2
    @# document 3

    echo "EXECUTE COMMANDS..."

unmakeを実行すると次のように表示されます。

$ unmake Makefile
* command param1=value1 param2=value2

      document 1
      document 2
      document 3

動作を解説します。

makefilePath はMakefileへのPATHです。
ファイルオープンし、中身を取得しています。

    fp, err := os.OpenFile(makefilePath, os.O_RDONLY, 0600)
    if err != nil {
        panic(err)
    }
    defer fp.Close()

    buf, err := ioutil.ReadAll(fp)
    if err != nil {
        panic("")
    }

実際にドキュメントの記述がある正規表現はこちらです。

    regx, err := regexp.Compile(`(?m:^([^._][a-zA-Z0-9_\-]*):(.*)\n(^\t\@\##.*\n)?(^\t\@\#\s*\n)?((^\t\@\#.*\n)*))`)
    if err != nil {
        panic("")
    }

見にくいですねえ。(?m) はマルチラインモードです。
今回の場合複数行にわたってコメントを書くことが多かったので
コメントで続く塊を取得するために指定しています。

上記の正規表現を探し出して、出力のために整形しています。

    for _, match := range regx.FindAllSubmatch(buf, -1) {
        cmdName := strings.TrimSpace(string(match[1]))
        paramLine := strings.TrimSpace(strings.Trim(string(match[3]), "\t@#"))
        usage := strings.Replace(string(match[5]), "\t@#", "     ", -1)
        fmt.Printf("* %s %s\n\n%s\n", cmdName, paramLine, usage)
    }

そんなに難しいことはしてないですね。
実は最初は同じ処理をPerlで実装していましたw

    open (my $fp, $_[0]) or die "$!";
    my $buf =  do { local $/; <$fp> };
    close $fp;

    while ($buf =~ /^([^._][a-zA-Z0-9_\-]*):(.*)\n(^\t\@\##.*\n)?(^\t\@\#\s*\n)?((^\t\@\#.*\n)*)/gm ) {
        my $cmd = $1;
        my $synopsis = $3 || "";
        my $msg = $5 || "";

        $cmd =~ s/\A\s*(.*?)\s*\z/$1/;
        $synopsis =~ s/@##//g;
        $synopsis =~ s/\A\s*(.*?)\s*\z/$1/;
        $msg =~ s/\t\@\#/     /g;
        printf("* %-20s\n\n%s\n\n", $cmd . " " . $synopsis, $msg);
    }

ただバイナリにコンパイルできるのが魅力的だったので実装し直しました。

Makefileのhelpには $(MAKEFILE_LIST) を渡すことで
includeされたMakefileも引数に渡すことができます。

.DEFAULT_GOAL_NAME := help

.PHONY: help
help:
    @# Display usage
    unmake $(MAKEFILE_LIST)

リポジトリはこちら

この投稿は Go (その2) Advent Calendar 201621日目の記事です。