Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14203] ファイルの概要

このコミットは、Go言語のコマンドラインツール golist サブコマンドに、テンプレート関数 join を追加するものです。これにより、go list -f オプションでパッケージ情報を整形して出力する際に、文字列スライス(例: 依存関係のリスト)をシェルで扱いやすい形式で結合できるようになります。具体的には、strings.Join 関数をテンプレート内で直接呼び出せるようにすることで、出力の柔軟性と利便性を向上させています。

コミット

  • コミットハッシュ: 9714691a3f1862f09cd8d8536131c01c15ab32c3
  • Author: Roger Peppe rogpeppe@gmail.com
  • Date: Mon Oct 22 08:58:27 2012 +0100

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/9714691a3f1862f09cd8d8536131c01c15ab32c3

元コミット内容

    cmd/go: add join template function.
    
    It's common to use the go list command in shell scripts, but
    currently it's awkward to print a string slice from the Package
    type in a way that's easily parseable by the shell.  For example:
    
            go list -f '{{range .Deps}}{{.}}
            {{end}}'
    
    (and even that prints an unwanted new line at the end|).
    
    To make this easier, this CL adds a "join" function to the
    format template.
    
            go list -f '{{join .Deps "\n"}}'
    
    R=rsc, dsymonds, minux.ma, remyoudompheng, r
    CC=golang-dev
    https://golang.org/cl/6680044

変更の背景

go list コマンドは、Goパッケージに関する情報を表示するために使用されます。-f フラグを使用すると、Goの text/template パッケージの構文を用いて出力フォーマットをカスタマイズできます。しかし、既存のテンプレート機能では、Package 型に含まれる文字列スライス(例えば、パッケージの依存関係を示す .Deps)を、シェルスクリプトで簡単にパースできる形式で出力するのが困難でした。

具体的には、文字列スライスを改行区切りで出力しようとすると、{{range .Deps}}{{.}}\n{{end}} のように記述する必要がありましたが、これでは最後の要素の後に余分な改行が出力されてしまうという問題がありました。このような出力は、シェルスクリプトで xargsfor ループなどを使って処理する際に不便でした。

この問題を解決し、go list の出力をよりシェルスクリプトフレンドリーにするために、文字列スライスを任意の区切り文字で結合する join テンプレート関数が求められました。

前提知識の解説

go list コマンド

go list は、Goのワークスペース内のパッケージに関する情報を表示するためのコマンドです。パッケージのインポートパス、ディレクトリ、依存関係、ソースファイルなど、さまざまな詳細を取得できます。 -f フラグを使用すると、Goの text/template パッケージの構文を使って出力形式を柔軟に指定できます。これにより、特定の情報だけを抽出したり、カスタムフォーマットで表示したりすることが可能です。

Goの text/template パッケージ

text/template パッケージは、Go言語でテキストベースのテンプレートを生成するための機能を提供します。HTML、XML、プレーンテキストなど、さまざまな形式の出力を生成するのに使用されます。テンプレートは、プレースホルダー(例: {{.Field}})や制御構造(例: {{range .Slice}}...{{end}})を含めることができ、データ構造をテンプレートに渡してレンダリングすることで、動的なテキストを生成します。

text/template は、カスタム関数を登録する機能も持っています。これにより、テンプレート内でGoの関数を呼び出し、より複雑なデータ処理や整形を行うことができます。カスタム関数は template.FuncMap を使用して登録されます。

strings.Join 関数

Goの標準ライブラリ strings パッケージに含まれる Join 関数は、文字列スライス([]string)の要素を、指定された区切り文字で結合して一つの文字列として返す関数です。

関数シグネチャ:

func Join(s []string, sep string) string
  • s: 結合する文字列スライス
  • sep: 要素間に挿入する区切り文字

例:

package main

import (
	"fmt"
	"strings"
)

func main() {
	s := []string{"apple", "banana", "cherry"}
	result := strings.Join(s, ", ")
	fmt.Println(result) // 出力: apple, banana, cherry
}

技術的詳細

この変更の核心は、text/template パッケージの Funcs メソッドを使用して、Goの標準ライブラリ関数 strings.Joingo list コマンドのテンプレートエンジンにカスタム関数として登録することです。

template.New("name").Funcs(template.FuncMap{...}).Parse(templateString) のように記述することで、テンプレートのパース時にカスタム関数マップをテンプレートエンジンに提供できます。template.FuncMapmap[string]interface{} 型であり、キーがテンプレート内で使用する関数名(例: "join")、値が対応するGoの関数(例: strings.Join)となります。

strings.Join[]stringstring を引数に取り、string を返す関数です。text/template は、Goの関数のシグネチャを自動的に解釈し、テンプレート内で適切な引数が渡された場合にその関数を呼び出すことができます。これにより、テンプレートユーザーは {{join .Deps "\n"}} のように簡潔な構文で strings.Join の機能を利用できるようになります。

コアとなるコードの変更箇所

変更は src/cmd/go/list.go ファイルに集中しています。

  1. strings パッケージのインポート追加:

    --- a/src/cmd/go/list.go
    +++ b/src/cmd/go/list.go
    @@ -9,6 +9,7 @@ import (
     	"encoding/json"
     	"io"
     	"os"
    +	"strings"
     	"text/template"
     )
    

    strings.Join 関数を使用するために、strings パッケージがインポートされました。

  2. go list -f の説明文の更新:

    --- a/src/cmd/go/list.go
    +++ b/src/cmd/go/list.go
    @@ -24,10 +25,10 @@ The default output shows the package import path:
         code.google.com/p/goauth2/oauth
         code.google.com/p/sqlite
     
    -The -f flag specifies an alternate format for the list,
    -using the syntax of package template.  The default output
    -is equivalent to -f '{{.ImportPath}}'.  The struct
    -being passed to the template is:
    +The -f flag specifies an alternate format for the list, using the
    +syntax of package template.  The default output is equivalent to -f
    +'{{.ImportPath}}'.  One extra template function is available, "join",
    +which calls strings.Join. The struct being passed to the template is:
     
          type Package struct {
              Dir        string // directory containing package sources
    

    go list -f オプションの説明に、join 関数が利用可能になったことが追記されました。

  3. テンプレートの初期化時に join 関数を登録:

    --- a/src/cmd/go/list.go
    +++ b/src/cmd/go/list.go
    @@ -113,7 +114,7 @@ func runList(cmd *Command, args []string) {
     		out.Write(nl)
     		}
     	} else {
    -		tmpl, err := template.New("main").Parse(*listFmt)
    +		tmpl, err := template.New("main").Funcs(template.FuncMap{"join": strings.Join}).Parse(*listFmt)
     		if err != nil {
     			fatalf("%s", err)
     		}
    

    runList 関数内でテンプレートをパースする際に、template.New("main").Funcs(template.FuncMap{"join": strings.Join}) を呼び出すことで、join という名前で strings.Join 関数がテンプレートエンジンに登録されています。

コアとなるコードの解説

このコミットの最も重要な変更は、src/cmd/go/list.gorunList 関数内で行われている以下の行です。

tmpl, err := template.New("main").Funcs(template.FuncMap{"join": strings.Join}).Parse(*listFmt)

このコードは、以下のステップでテンプレートを準備しています。

  1. template.New("main"): 新しいテンプレートインスタンスを作成し、名前を "main" とします。
  2. .Funcs(template.FuncMap{"join": strings.Join}): ここが肝となる部分です。Funcs メソッドは、テンプレートで使用できるカスタム関数を登録するために使用されます。
    • template.FuncMap は、map[string]interface{} 型のエイリアスであり、キーがテンプレート内で呼び出す関数名(この場合は "join")、値がその関数に対応するGoの関数(この場合は strings.Join)です。
    • strings.Joinfunc Join(s []string, sep string) string というシグネチャを持つため、テンプレート内で {{join .Slice "separator"}} のように呼び出されると、.Slice[]string として、"separator"string として strings.Join に渡され、その結果がテンプレートに挿入されます。
  3. .Parse(*listFmt): ユーザーが -f フラグで指定したフォーマット文字列(*listFmt)をパースし、テンプレートオブジェクトを構築します。

この変更により、go list -f '{{join .Deps "\n"}}' のようなコマンドが有効になり、パッケージの依存関係を改行区切りで、かつ余分な改行なしで出力できるようになりました。これは、シェルスクリプトでの go list の出力を処理する際に非常に役立ちます。

関連リンク

参考にした情報源リンク