mike-neckのブログ

Java or Groovy or Swift or Golang

github から Gradle のリリースのリストを取得して、 jq で RC 以外で各マイナーバージョンで最新のバージョンの値を取得する

単なる jq のメモ。

f:id:mike_neck:20200815231037p:plain


やりたいこと

github から release の一覧を取得して、 RC 以外のバージョンの最新バージョンの値を取得する

GitHub API

例えば Gradle のリリースは、 GitHubドキュメント によると次の URL で 40 件ほど取得できる

https://api.github.com/repos/gradle/gradle/releases?per_page=40

すると、このコマンドができる

curl \
  https://api.github.com/repos/gradle/gradle/releases?per_page=40 \
    -H 'accept:application/vnd.github.v3+json' \
    -H "authorization:token ${GITHUB_TOKEN}"

なお、レスポンスの内容は、次のような json(一部省略) の配列であり、ほしい値は name である

  {
    "url": "https://api.github.com/repos/gradle/gradle/releases/22797617",
    "html_url": "https://github.com/gradle/gradle/releases/tag/v6.1.0-RC3",
    "tag_name": "v6.1.0-RC3",
    "target_commitish": "release",
    "name": "6.1 RC3",
  }

お題1 jqjson の配列から json の一部の項目だけを抜き出した配列を作る

配列を イテレータに変換 して、 プロパティを指定して 取り出す(Object Identifier-Index というらしい)

.[] | .name

したがって、現在のコマンドはこんな感じ

curl \
  https://api.github.com/repos/gradle/gradle/releases?per_page=40 \
    -H 'accept:application/vnd.github.v3+json' \
    -H "authorization:token ${GITHUB_TOKEN}" \
jq '
  .[] |
  .name
'

出力はこうなる

6.6
6.6 RC6
6.6 RC5
6.6 RC4
6.6 RC3
6.6 RC2
6.6 RC1
6.5.1
6.5
6.5 RC1
6.4.1
6.4
6.4 RC4
6.4 RC3
6.4 RC2
6.4 RC1
6.3
6.3 RC4

このとき、 RC のバージョンはいらないので、これを除外したい

お題2 jq で特定の文字列を含む項目を取り除く

特定の文字列を含むかテストして、含んでいたら true 、含んでいない場合は false を返す contains という関数がある。これを boolean を受け取って、出力有無を決定する select 関数に渡して絞り込む。

select(contains("RC") == false)

現在のコマンドは次の通り

curl \
  https://api.github.com/repos/gradle/gradle/releases?per_page=40 \
    -H 'accept:application/vnd.github.v3+json' \
    -H "authorization:token ${GITHUB_TOKEN}" \
jq '
  .[] |
  .name |
  select(contains("RC"))
'

出力は次のようになる

6.6
6.5.1
6.5
6.4.1
6.4
6.3

このとき、 6.4.1 はほしいけど、 6.4 は必要がなく、また同じフィルターで 6.36.6 も取れるようにしたい

お題3 マイナーバージョン番号を取得する

[:3] の形でマイナーバージョンの値を取得できるといえば取得できるが、当然マイナーバージョンが 10 以上にもなりうるので(例 : 1.x 2.x 4.x は実際にあった)できれば動的に取得したい。 indices を使うと、指定した部分文字列のインデックス値が取得できる。これと、 Object construction を用いて現在の値も保持する

{.value: ., indices: indices(".")}

ここで値は次のようになる。

{ "value": "6.6", "indices": [1] }
{ "value": "6.5.1", "indices": [1, 3] }
{ "value": "4.10.3", "indices": [1, 4] }
{ "value": "4.10", "indices": [1] }

次に indices を indices の二番目の要素または文字列の長さにマッピングしたい。そこで、 Alternative Operator を使って indices の二番目(インデックスは 1)の要素または 文字列の長さ を取得する。

{value: .value, index: (.indices[1] // (.value | utf8bytelength))}

ここで値は次のようになる。

{ "value": "6.6", "index": 3 }
{ "value": "6.5.1", "index": 3 }
{ "value": "4.10.3", "index": 4 }
{ "value": "4.10", "index": 4 }

最後に index の値をマイナーバージョンにマッピングする

{value: .value, minor: .value[:.index]}

以上を組み合わせると次のようなコマンドと出力になる。

コマンド

curl \
  https://api.github.com/repos/gradle/gradle/releases?per_page=40 \
    -H 'accept:application/vnd.github.v3+json' \
    -H "authorization:token ${GITHUB_TOKEN}" \
jq '
  .[] |
  .name |
  select(contains("RC")) |
  {.value: ., indices: indices(".")} |
  {value: .value, index: (.indices[1] // (.value | utf8bytelength))} |
  {value: .value, minor: .value[:.index]}
'

出力

{ "value":  "6.6", "minor": "6.6"}
{ "value": "6.5.1", "minor": "6.5" }
{ "value": "6.5", "minor": "6.5" }
{ "value": "6.4.1", "minor": "6.4" }
{ "value": "6.4", "minor": "6.4" }
{ "value": "6.3", "minor": "6.3" }

お題4 マイナーバージョンごとにグループ化する

グルーピングは group_by を使って、 .minor ごとにグループを組めばよいのだが、この関数は配列を入力に取るものの、現在の状態はイテレーターによるオブジェクトが入力になってしまうため型があわない。そこで、ここまでの内容を配列に変換するため、ここまでのクエリに Array construction を適用する。

コマンド

curl \
  https://api.github.com/repos/gradle/gradle/releases?per_page=40 \
    -H 'accept:application/vnd.github.v3+json' \
    -H "authorization:token ${GITHUB_TOKEN}" \
jq '
[
  .[] |
  .name |
  select(contains("RC")) |
  {.value: ., indices: indices(".")} |
  {value: .value, index: (.indices[1] // (.value | utf8bytelength))} |
  {value: .value, minor: .value[:.index]}
]
'

出力

[
  { "value":  "6.6", "minor": "6.6"},
  { "value": "6.5.1", "minor": "6.5" },
  { "value": "6.5", "minor": "6.5" },
  { "value": "6.4.1", "minor": "6.4" },
  { "value": "6.4", "minor": "6.4" },
  { "value": "6.3", "minor": "6.3" }
]

そして、改めて group_by を適用する。

コマンド

curl \
  https://api.github.com/repos/gradle/gradle/releases?per_page=40 \
    -H 'accept:application/vnd.github.v3+json' \
    -H "authorization:token ${GITHUB_TOKEN}" \
jq '
[
  .[] |
  .name |
  select(contains("RC")) |
  {.value: ., indices: indices(".")} |
  {value: .value, index: (.indices[1] // (.value | utf8bytelength))} |
  {value: .value, minor: .value[:.index]}
] |
group_by(.minor)
'

出力

[
  [
    { "value": "6.3", "minor": "6.3" }
  ],
  [
    { "value": "6.4.1", "minor": "6.4" },
    { "value": "6.4", "minor": "6.4" }
  ],
  [
    { "value": "6.5.1", "minor": "6.5" },
    { "value": "6.5", "minor": "6.5" }
  ],
  [
    { "value":  "6.6", "minor": "6.6"}
  ]
]

group_by を使うと API で取得できる配列の順番が逆になるので、 reverse を最後に適用しておく

[
  .[] |
  .name |
  select(contains("RC")) |
  {.value: ., indices: indices(".")} |
  {value: .value, index: (.indices[1] // (.value | utf8bytelength))} |
  {value: .value, minor: .value[:.index]}
] |
group_by(.minor) |
reverse

お題5 個々の配列からマイナーバージョンの値を取得する

配列の個々の値を変換していくので、まずイテレーターに変える。 jq では 6.4 より 6.4.1 の方が大きい値なので、 max_by を用いて最終的にほしい値の入ったオブジェクトを取り出す。最後にほしい値のプロパティを指定して取り出す。

.[] | max_by(.value) | .value

または次でも同じものが取れる

.[] | [.[] | .value] | max

コマンド

curl \
  https://api.github.com/repos/gradle/gradle/releases?per_page=40 \
    -H 'accept:application/vnd.github.v3+json' \
    -H "authorization:token ${GITHUB_TOKEN}" \
jq '
[
  .[] |
  .name |
  select(contains("RC")) |
  {.value: ., indices: indices(".")} |
  {value: .value, index: (.indices[1] // (.value | utf8bytelength))} |
  {value: .value, minor: .value[:.index]}
] |
group_by(.minor) |
.[] |
max_by(.value) |
.value
'

出力

"6.6"
"6.5.1"
"6.4.1"
"6.3"
"6.2.2"
"6.1.1"
"6.0.1"
"5.6.4"

ほしい値が取れたが、ダブルクォーテーションがついていると次のプロセスで使いづらい

お題6 出力された文字列のイテレーターからダブルクォーテーションを取り除く

オプション -r / --raw-output を使うと出力が文字列の際に json に則って出力するのではなく、そのままの値を出力する

よって、最終的に次のコマンドと出力を得る

コマンド

curl \
  https://api.github.com/repos/gradle/gradle/releases?per_page=40 \
    -H 'accept:application/vnd.github.v3+json' \
    -H "authorization:token ${GITHUB_TOKEN}" \
jq -r '
[
  .[] |
  .name |
  select(contains("RC")) |
  {.value: ., indices: indices(".")} |
  {value: .value, index: (.indices[1] // (.value | utf8bytelength))} |
  {value: .value, minor: .value[:.index]}
] |
group_by(.minor) |
.[] |
max_by(.value) |
.value
'

出力

6.6
6.5.1
6.4.1
6.3
6.2.2
6.1.1
6.0.1
5.6.4