github.com/pelletier/go-toml で遊ぶ
Hugo v0.87.0 で TOML の操作に github.com/pelletier/go-toml パッケージを採用することにしたらしい。
we have switched to using go-toml for all things TOML in Hugo. A big thanks to @pelletier for his work on the v2 version. It's both faster than what we had and TOML v1.0.0 compliant.
(via "Release v0.87.0 · gohugoio/hugo")
ちなみに,このパッケージは v2 系がベータ版公開されていて github.com/BurntSushi/toml パッケージより2倍以上速いとのこと(via "Release v2.0.0-beta.3 · pelletier/go-toml")。
Benchmark | go-toml v1 | BurntSushi/toml |
---|---|---|
Marshal/HugoFrontMatter | 2.0x | 2.0x |
Marshal/ReferenceFile/map | 1.8x | 2.0x |
Marshal/ReferenceFile/struct | 2.7x | 2.7x |
Unmarshal/HugoFrontMatter | 3.0x | 2.6x |
Unmarshal/ReferenceFile/map | 3.0x | 3.1x |
Unmarshal/ReferenceFile/struct | 5.9x | 6.6x |
というわけで pelletier/go-toml を使って軽く遊んでみよう。
gpgpdump 解析結果を TOML で出力する
拙作の github.com/spiegel-im-spiegel/gpgpdump パッケージは OpenPGP パケットの解析結果を TOML 形式で出力することができる[1]。たとえば BurntSushi/toml パッケージと組み合わせて
// +build run
package main
import (
"bytes"
"fmt"
"io"
"os"
"strings"
"github.com/BurntSushi/toml"
"github.com/spiegel-im-spiegel/gpgpdump/parse"
"github.com/spiegel-im-spiegel/gpgpdump/parse/context"
)
const openpgpStr = `
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2
iF4EARMIAAYFAlTDCN8ACgkQMfv9qV+7+hg2HwEA6h2iFFuCBv3VrsSf2BREQaT1
T1ZprZqwRPOjiLJg9AwA/ArTwCPz7c2vmxlv7sRlRLUI6CdsOqhuO1KfYXrq7idI
=ZOTN
-----END PGP SIGNATURE-----
`
func main() {
p, err := parse.New(
context.New(
context.Set(context.ARMOR, true),
context.Set(context.UTC, true),
),
strings.NewReader(openpgpStr),
)
if err != nil {
fmt.Fprintf(os.Stderr, "%+v", err)
return
}
res, err := p.Parse()
if err != nil {
fmt.Fprintf(os.Stderr, "%+v", err)
return
}
buf := &bytes.Buffer{}
if err := toml.NewEncoder(buf).Encode(res); err != nil {
fmt.Fprintf(os.Stderr, "%+v", err)
return
}
if _, err = io.Copy(os.Stdout, buf); err != nil {
fmt.Fprintf(os.Stderr, "%+v", err)
return
}
}
てな感じに書ける。出力結果は
$ go run sample0.go
[[Packet]]
name = "Signature Packet (tag 2)"
note = "94 bytes"
[[Packet.Item]]
name = "Version"
value = "4"
note = "current"
[[Packet.Item]]
name = "Signiture Type"
value = "Signature of a canonical text document (0x01)"
[[Packet.Item]]
name = "Public-key Algorithm"
value = "ECDSA public key algorithm (pub 19)"
[[Packet.Item]]
name = "Hash Algorithm"
value = "SHA2-256 (hash 8)"
[[Packet.Item]]
name = "Hashed Subpacket"
note = "6 bytes"
[[Packet.Item.Item]]
name = "Signature Creation Time (sub 2)"
value = "2015-01-24T02:52:15Z"
[[Packet.Item]]
name = "Unhashed Subpacket"
note = "10 bytes"
[[Packet.Item.Item]]
name = "Issuer (sub 16)"
value = "0x31fbfda95fbbfa18"
[[Packet.Item]]
name = "Hash left 2 bytes"
dump = "36 1f"
[[Packet.Item]]
name = "ECDSA value r"
note = "256 bits"
[[Packet.Item]]
name = "ECDSA value s"
note = "252 bits"
という感じ。これを pelletier/go-toml パッケージに置き換えてみよう。インポート・パスで
import "github.com/pelletier/go-toml/v2"
と v2 系を指定して BurntSushi/toml パッケージを使った部分を書き換える。
b, err := toml.Marshal(res)
if err != nil {
fmt.Fprintf(os.Stderr, "%+v", err)
return
}
if _, err = bytes.NewReader(b).WriteTo(os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "%+v", err)
return
}
これを実行してみよう。
$ go run sample1.go
[['Packet,omitempty']]
name = 'Signature Packet (tag 2)'
'value,omitempty' = ''
'dump,omitempty' = ''
'note,omitempty' = '94 bytes'
[['Packet,omitempty'.'Item,omitempty']]
name = 'Version'
'value,omitempty' = '4'
'dump,omitempty' = ''
'note,omitempty' = 'current'
'Item,omitempty' = []
[['Packet,omitempty'.'Item,omitempty']]
name = 'Signiture Type'
'value,omitempty' = 'Signature of a canonical text document (0x01)'
'dump,omitempty' = ''
'note,omitempty' = ''
'Item,omitempty' = []
[['Packet,omitempty'.'Item,omitempty']]
name = 'Public-key Algorithm'
'value,omitempty' = 'ECDSA public key algorithm (pub 19)'
'dump,omitempty' = ''
'note,omitempty' = ''
'Item,omitempty' = []
[['Packet,omitempty'.'Item,omitempty']]
name = 'Hash Algorithm'
'value,omitempty' = 'SHA2-256 (hash 8)'
'dump,omitempty' = ''
'note,omitempty' = ''
'Item,omitempty' = []
[['Packet,omitempty'.'Item,omitempty']]
name = 'Hashed Subpacket'
'value,omitempty' = ''
'dump,omitempty' = ''
'note,omitempty' = '6 bytes'
[['Packet,omitempty'.'Item,omitempty'.'Item,omitempty']]
name = 'Signature Creation Time (sub 2)'
'value,omitempty' = '2015-01-24T02:52:15Z'
'dump,omitempty' = ''
'note,omitempty' = ''
'Item,omitempty' = []
[['Packet,omitempty'.'Item,omitempty']]
name = 'Unhashed Subpacket'
'value,omitempty' = ''
'dump,omitempty' = ''
'note,omitempty' = '10 bytes'
[['Packet,omitempty'.'Item,omitempty'.'Item,omitempty']]
name = 'Issuer (sub 16)'
'value,omitempty' = '0x31fbfda95fbbfa18'
'dump,omitempty' = ''
'note,omitempty' = ''
'Item,omitempty' = []
[['Packet,omitempty'.'Item,omitempty']]
name = 'Hash left 2 bytes'
'value,omitempty' = ''
'dump,omitempty' = '36 1f'
'note,omitempty' = ''
'Item,omitempty' = []
[['Packet,omitempty'.'Item,omitempty']]
name = 'ECDSA value r'
'value,omitempty' = ''
'dump,omitempty' = ''
'note,omitempty' = '256 bits'
'Item,omitempty' = []
[['Packet,omitempty'.'Item,omitempty']]
name = 'ECDSA value s'
'value,omitempty' = ''
'dump,omitempty' = ''
'note,omitempty' = '252 bits'
'Item,omitempty' = []
ありゃりゃーん。 Struct タグの omitempty
を解釈してくれないんだな[2] orz
ま,まぁ,特定の構造体ではなく map[string]interface{} を使う手もあるのだが... ちなみに import 宣言を
import "github.com/pelletier/go-toml"
と v1 系にすれば BurntSushi/toml と同等の出力になる(でも遅い,多分)。
jq 風のクエリ
【おまけ】pelletier/go-toml v1 系には jq 風のクエリを使って TOML の内容をフィルタリングする機能がある。これを使って以下のようなコードを書いてみた。
// +build run
package main
import (
"flag"
"fmt"
"os"
"github.com/pelletier/go-toml"
"github.com/pelletier/go-toml/query"
)
func main() {
flag.Parse()
args := flag.Args()
if len(args) != 1 {
fmt.Fprintln(os.Stderr, os.ErrInvalid)
return
}
info, err := toml.LoadReader(os.Stdin)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
results, err := query.CompileAndExecute(args[0], info)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
for _, item := range results.Values() {
fmt.Println(item)
}
}
たとえば,最初に書いた sample0.go の実行結果をこの sample2.go にパイプで繋げば
$ go run sample0.go | go run sample2.go "$.Packet[0].Item[0]"
name = "Version"
note = "current"
value = "4"
てな感じで評価できる。
参考
Discussion