[Hugo] index関数でconfigやdataの深いネストをドット区切りで辿る



Nプログラマ(@Nprog128)です。

Hugoでconfigやdataを使ってデータを設定していると、深いネストになることがあるはず。。。

ネストが深くなれば、index関数を使って値を取得するのも一苦労です。

こんな感じにかっこ!カッコ!括弧!とindexとカッコだらけになってきます。

1index (index $value1 (index $value2 (index $value3)))
なプ

自分はもっぱらshortcodeで使っています。

Hugoのショートコードにはオーバーロードのような実装はありません

なので愚直に作って行くと、同じ処理なのに取得するconfigが違うために専用ショートコードが増えて複雑になってしまいます。

Hugoに移行してからしばらくはゴリ押しでやっていたのですが、ショートコードが増えて複雑になりツラくなってきました。

ようやく重い腰を上げて、ちょっと工夫してこんな感じのショートコード呼び出しでネストしたconfigからデータを取得できるようにしました。

01 {{< show-param key="system.common.actor.palyer.job.hero" >}}

idex関数はドット区切りで取得できないので、内部でゴニョゴニョした実装になっています。。。

今回はそんな内容になります。

出来上がったコードを先に見たいかたは下のボタンからページ内ジャンプ!

こちらからページ内ジャンプ!

コードが出来上がるまでに至った道のりを見たい方は、最初から気長に読んでいってください。

もしかすると今回作った機能と同じようなhugoの関数にあるかもしれません。。。

なプ

あったらコッソリ教えてください!(twitter)

まずは素直に書いてみる

それでは素直に深いネストの設定からデータを取得してみます。

configもdataも取得方法は似ています。

configとdataの取得 コードを開く
configとdataの取得
1{{ $config := .Site.Params.person.name }}
2{{ $data   := .Site.Data.person.name }}

なので、今回はconfigを例にしていきます。

準備するconfig.tomlはこんな感じです。

config.toml コードを開く
config.toml
1[system.common.actor.player.job.hero.param]
2  name = "勇者"
3  hp   = 100
4  mp   = 20
なプ

なんかRPGゲーム風味、、、

このような設定を使うかは分かりませんが、今回は例ということで。。。

ネストは深いですがindexを使ってきちんと取得できます。

勇者を表示するショートコードshow-param.htmlはこんな感じになりました。

show-param.html コードを開く
show-param.html
1{{ $data := index .Site.Params.system.common.actor.palyer.job.hero "param" }}
2{{ $data.name }} # 勇者
3{{ $data.hp }}   # 100
4{{ $data.mp }}   # 20

特に問題はありませんが、ちょっと凝ったショートコードでデータを取得しようとすると 少し面倒になってきます。

戦士を増やす

最初は勇者だけを表示させるだけでしたが、戦士のパラメータも表示させたくなりました。

戦士の設定を追加したconfig.tomlはこんな感じ。

config.toml コードを開く
config.toml
1[system.common.actor.player.job.hero.param]
2  name = "勇者"
3  hp   = 100
4  mp   = 20
5# 追加した戦士の設定
6[system.common.actor.player.job.soldier.param]
7  name = "戦士"
8  hp   = 200 
9  mp   = 0

さて、これを表示させるにはどうするか。。。?

今の所思いついたのは2つです。

  1. 戦士用のショートコードshow-param2.htmlを作る
  2. ショートコードの引数で分岐させる

1.の方法もアリです。 この機能の使いみちがはっきりと決まっているのであれば、コピペする方が早いラクですから。

なプ

ケースバイケースってやつッス。

しかし、今後も設定で登場人物が増えるかもしれない。。。

そういう場合なら2.の方法を取れば、同じショートコードを引数を変えることで使い回せそうです。

ショートコードに引数を追加する

ということで、show-param.htmlのショートコードに登場人物を指定する引数:job_personを追加してみました。

コードはこんな感じです。

引数を追加 コードを開く
引数を追加
1{{ $arg := .Get `job_person` }}
2{{ $param_job := index .Site.Params.system.common.actor.palyer.job }}
3# .Site.Params.system.common.actor.palyer.jobからheroかsoldierを取得できる
4{{ $job_person := index $param_job $arg }}
5# heroかsoldierのparamを取得
6{{ $data := index $job_person "param" }}
7{{ $data.name }} # 勇者 or 戦士
8{{ $data.hp }}   # 100 or 200
9{{ $data.mp }}   # 20 or 0

最初に.Site.Params.system.common.actor.palyer.jobまでを取得して、渡された引数からheroかsoliderを取得する、という実装になります。

これで一つのショートコードで、勇者と戦士の情報を表示することができるようになりました。

// 勇者を表示
{{< show-param job_person="hero" >}}

// 戦士を表示
{{< show-param job_person="soldier" >}}

さて、これで僧侶や魔法使いなども使いたくなったら、configに追記してショートコードの引数:job_personを指定すればうまく動きそうです。

僧侶や魔法使い コードを開く
僧侶や魔法使い
 1# 僧侶
 2[system.common.actor.player.job.priest.param]
 3  name = "僧侶"
 4  hp   = 50 
 5  mp   = 20
 6# 魔法使い
 7[system.common.actor.player.job.magician.param]
 8  name = "魔法使い"
 9  hp   = 20 
10  mp   = 50

しかし、これにもちょっとした壁が立ちはだかります。。。

敵とかのパラメータも表示したいんだよね、、、

楽しくなって設定値を増やしていたら、ある時こういう風に思いました。

敵のパラメータもconfigに書いて表示させてみよう!!

なプ

そんなことしねーよ!

ということはさておき、これも例ですよ、例(笑)。

とりあえず、こんな風に敵の設定を追加しました。

config.toml コードを開く
config.toml
 1[system.common.actor.player.job.hero.param]
 2  name = "勇者"
 3  hp   = 100
 4  mp   = 20
 5[system.common.actor.player.job.soldier.param]
 6  name = "戦士"
 7  hp   = 200 
 8  mp   = 0
 9[system.common.actor.enemy.attribute.water.slime]
10  name = "スライム"
11  hp   = 3
12  mp   = 0

敵(enemy)の属性(attribute)が水(water)であるスライム(slime)を追加しました。

さてこれを先程のshow-param.htmlのショートコードで取得しようとすると、すんなりと取得することができません。

コード再掲 コードを開く
コード再掲
1{{ $arg := .Get `job_person` }}
2{{ $param_job := index .Site.Params.system.common.actor.palyer.job }}
3# .Site.Params.system.common.actor.palyer.jobからheroかsoldierを取得できる
4{{ $job_person := index $param_job $arg }}
5# heroかsoldierのparamを取得
6{{ $data := index $job_person "param" }}
7{{ $data.name }} # 勇者 or 戦士
8{{ $data.hp }}   # 100 or 200
9{{ $data.mp }}   # 20 or 0

すんなりといかない理由は、$param_jobをindexで取得している部分です。

indexを使って取得するconfigの値が固定になっているので、ここを引数によって変更する必要が出てきます。

actorまでは共通なので、actor_typeのような引数を追加すれば実装できます。

引数の追加 コードを開く
引数の追加
1{{ $config := index .Site.Params.system.common.actor $actor_type }}
2{{ if eq $actor_type "player" }}
3  {{ $config = index (index $config "job") $job_person }}
4{{ else if eq $actor_type "enemy" }}
5  {{ $config = index (index (index $config "attribute") "water") $job_person }}
6{{ end }}

playerとenemyではmap構造が違うため、これを繰り返していくとコードが肥大していきそうです。

configに追加したい設定がモリモリと増えてきて、最終的にちょっと分かりにくいコードになってしまうと思います。

で、、、長い長ーい前置きがあって、こう思うわけです。

なプ

Laravelのarray_getみたいに、ドット区切りでネストしたmapからピンポイントで値を取得できねーかな。。。?

array_getとは?

Laravelのヘルパ関数の一つ。

ネストされた配列に対してドット記法で要素を指定するとその配列から要素を取得することができる。

できればこんな風にシンプルにショートコードを書きたいのです。

01 # 勇者を表示
02 {{< show-param key="system.common.actor.palyer.job.hero" >}}
03 # 戦士を表示
04 {{< show-param key="system.common.actor.palyer.job.hero" >}}
05 # スライムを表示<br>
06 {{< show-param key="system.common.actor.enemy.attribute.slime" >}}

ということで、これを実現するショートコードを作ってみます。

という感じで完成したコード

なプ

はい!できました!

作ったコード コードを開く
作ったコード
1{{ $p  := index .Site.Params }}
2{{ $key := .Get `key` }}
3
4{{ range split $key "." }}
5  {{ $p = index $p . }}
6{{ end }}
7
8# これ以降に勇者や戦士僧侶魔法使いスライムの表示処理

勇者、戦士、スライムのconfigも再掲します。

config(再掲) コードを開く
config(再掲)
 1[system.common.actor.player.job.hero.param]
 2  name = "勇者"
 3  hp   = 100
 4  mp   = 20
 5[system.common.actor.player.job.soldier.param]
 6  name = "戦士"
 7  hp   = 200 
 8  mp   = 0
 9[system.common.actor.enemy.attribute.water.slime]
10  name = "スライム"
11  hp   = 3
12  mp   = 0

これで先程の実現したいショートコードを実行すれば、mapのどこの階層の要素でも取得できるようになります。

name、hp、mpのように最終的な構造が同じでないと表示周りの分岐は必要ですが、configの取得がかなりスッキリしたと思います。

01 # 勇者
02 {{< show-param key="system.common.actor.palyer.job.hero" >}}
03 # 戦士
04 {{< show-param key="system.common.actor.palyer.job.hero" >}}
05 # スライム
06 {{< show-param key="system.common.actor.enemy.attribute.slime" >}}

ドットの分だけrangeの処理で重くなるかもしれませんが、許容範囲だと思います。

自分も使っていますが、今のブログの規模で全ビルドは3秒くらいで終わるのでしばらく使ってみようと思います。

もし重くなってきたら。。。?

おそらくこのショートコードの使用箇所が増えてくると、処理が重くなってくると思います。

1ショートコードで5回のループがあるとして、10000ヶ所でショートコードを使えば、50000回のループが回ることになります。(hugoの内部処理を見ていないのでどうとも言えませんが、、、)

このブログの規模が大きくなり、ビルド時間がこのショートコードにより遅くなってきたらどうしましょうか。

一応それも考えてあって、一つはドット区切りをやめて全てをハイフン区切りにして、一つのconfigファイル、一つのdataファイルにまとめてしまおうと考えています。

また実験してみてうまくいきそうなら記事にでもしておきます。

おわりに

今回は、configやdataからmapのドット区切りの深いネストを辿る、という内容でした。

hugoの関数に追加されるまでは、この実装を使っていこうと思います。

なプ

index_recurみたいな関数が追加されないかなー。。。

それでは、このへんで。
バイナリー!

\ ちょっとお買い物 /


関連した記事