go.rice の使い方を調査した

Awesome Go から見繕って go.rice を使ってみることにした。

感想

「こう使いたい」という気持ちと go.rice の作法にずれがあって少しとまどった

失敗例

初期値では モジュールのルートディレクトリからの相対パス ではなく、FindBoxMustFindBox を呼び出しているコードが含まれるパッケージからの相対パス になる。

README を流し読みして失敗したけど、実装を読んだらなるほどそういうことかと感心させられた。

ソースコードと失敗する様子

$ find . -type f
./example.exe
./go.mod
./go.sum
./main.go
./pkg/example/example.go
./template/hello.tmpl
// ./main.go
package main

import (
    "log"

    "gist.github.com/yujiorama/gorice-invalid/pkg/example"
)

func main() {

    log.Println(example.A())
}

// ./pkg/example/example.go
package example

import (
    "fmt"
    "strings"
    "text/template"

    rice "github.com/GeertJohan/go.rice"
)

func A() string {
    box, err := rice.FindBox("template")
    if err != nil {
        return fmt.Sprintf("%v", err)
    }

    helloTemplate, err := box.String("hello.tmpl")
    if err != nil {
        return fmt.Sprintf("%v", err)
    }

    tmpl, err := template.New("message").Parse(helloTemplate)
    if err != nil {
        return fmt.Sprintf("%v", err)
    }

    var message strings.Builder
    err = tmpl.Execute(&message, "go.rice")
    if err != nil {
        return fmt.Sprintf("%v", err)
    }

    return message.String()
}

このファイル構成では初期設定のままファイルを埋め込むコマンド rice append が失敗する。

(エラーメッセージよりどうやらパッケージの相対パスに配置されていることを期待している雰囲気を感じた)

$ go build -o example.exe
$ rice append --import-path pkg/example/example.go --exec example.exe
Error: box "C:\Users\y_okazawa\work\gorice-tutorial\pkg\example\template" not found on disk

実装を確認

  • FindBox
    • findBoxdefaultLocateOrder を指定している
  • findBox
    • order に応じて探索先を変更している
  • defaultLocateOrder
    • LocateEmbedded
    • LocateAppended
      • 実行ファイルに埋め込まれた(append された)ディレクトリを探索
    • LocateFS
      • 実行ファイルパスからディレクトリを探索するけど、テスト用みたいだった

と、一通り読んでから orderConfig オブジェクトで制御できることがわかった。

Config

正しい使い方

  • パッケージの相対パスから探索させたい場合は初期設定のままでいい
  • 実行時ディレクトリから探索させたい場合は Config オブジェクトを用意する

パッケージの相対パスから探索させるときのファイル配置とソースコード

$ find . -type f
./example.exe
./go.mod
./go.sum
./main.go
./pkg/example/example.go
./pkg/example/template/hello.tmpl
// ./pkg/example/example.go
package example

import (
    "fmt"
    "strings"
    "text/template"

    rice "github.com/GeertJohan/go.rice"
)

func A() string {
    box, err := rice.FindBox("template")
    if err != nil {
        return fmt.Sprintf("%v", err)
    }

    helloTemplate, err := box.String("hello.tmpl")
    if err != nil {
        return fmt.Sprintf("%v", err)
    }

    tmpl, err := template.New("message").Parse(helloTemplate)
    if err != nil {
        return fmt.Sprintf("%v", err)
    }

    var message strings.Builder
    err = tmpl.Execute(&message, "go.rice")
    if err != nil {
        return fmt.Sprintf("%v", err)
    }

    return message.String()
}

初期設定では FindBox を呼び出している example.go のパッケージ example から相対パスtemplate を探索する。

(rice append がそういう動作をする。)

$ rm -f example.exe
$ go build -o example.exe
$ rice append --import-path pkg/example/example.go --exec example.exe
$ ./example.exe
2020/03/02 09:53:51 Hello go.rice World

実行時ディレクトリから探索させるときのファイル配置とソースコード

$ find . -type f
./example.exe
./go.mod
./go.sum
./main.go
./pkg/example/example.go
./pkg/example/template/hello.tmpl
./template/hello.tmpl

$ cat ./pkg/example/template/hello.tmpl
(inside) Hello {{.}} World

$ cat ./template/hello.tmpl
(outside) Hello {{.}} World
// ./pkg/example/example.go
package example

import (
    "fmt"
    "strings"
    "text/template"

    rice "github.com/GeertJohan/go.rice"
)

func A() string {
    config := rice.Config{
        LocateOrder: []rice.LocateMethod{
            rice.LocateWorkingDirectory,
            rice.LocateEmbedded,
            rice.LocateAppended,
            rice.LocateFS,
        },
    }

    box, err := config.FindBox("template")
    if err != nil {
        return fmt.Sprintf("%v", err)
    }

    helloTemplate, err := box.String("hello.tmpl")
    if err != nil {
        return fmt.Sprintf("%v", err)
    }

    tmpl, err := template.New("message").Parse(helloTemplate)
    if err != nil {
        return fmt.Sprintf("%v", err)
    }

    var message strings.Builder
    err = tmpl.Execute(&message, "go.rice")
    if err != nil {
        return fmt.Sprintf("%v", err)
    }

    return message.String()
}

実行時の作業ディレクトリから相対パスディレクトリを見つけたらそちらを利用する。

$ rm -f example.exe
$ go build -o example.exe
$ rice append --import-path pkg/example/example.go --exec example.exe
$ ./example.exe
2020/03/02 12:30:30 (outside) Hello go.rice World

実行時の作業ディレクトリから相対パスディレクトリを見つけられなかったら埋め込みしたリソースを利用する。

$ mv template template.1
$ ./example.exe
2020/03/02 12:31:26 (inside) Hello go.rice World