GitHub Actionsが動かなかったことでちょっと迷ってたけど、デフォルトのブランチ名が変わったからだった

タイトルオチ

GitHub Actionsを動かしたかった

ざつに作ったアプリケーションでgh-pagesを使いたかったので、以下の記事から雑に引用してGitHub Actionsを使用したデプロイを試みた。

qiita.com

引用するとこう

name: github pages

# masterブランチにプッシュしたときjobsに記述した操作を行う
on:
  push:
    branches:
    - master

jobs:
  build-deploy:
    # ubuntu OS を仮想マシン上に用意する
    runs-on: ubuntu-18.04
    steps:
    - uses: actions/checkout@v2

    # Node.js環境のセットアップを行う
    - name: setup node
      uses: actions/setup-node@v1
      with:
        node-version: '12.x'

    # npm install の際にキャッシュを使うよう設定
    - name: Cache dependencies
      uses: actions/cache@v1
      with:
        path: ~/.npm
        key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
        restore-keys: |
          ${{ runner.os }}-node-

    # package.jsonに基づき依存パッケージをインストールする
    - name: install
      run: npm install --frozen-lockfile

    # Next.jsアプリをビルドする
    # プロジェクトルート直下に.nextディレクトリができる
    - name: build
      run: npm run build

    # 静的なHTMLとしてNext.jsアプリを生成する
    # プロジェクトルート直下にoutディレクトリができる
    # そのなかに、HTMLファイル群と、それらが読み込むJSファイル群を収めた_nextディレクトリがある
    - name: export
      run: npm run export

    # しかしGitHub Pagesの仕様として_から始まるディレクトリが見えず404となる
    # つまりHTMLからJSを読み込めない
    # これを回避するために.nojekyllファイルをoutディレクトリに作る
    - name: add nojekyll
      run: touch ./out/.nojekyll

    # gh-pagesブランチにoutディレクトリの中身をプッシュする
    # gh-pagesブランチは自動的に作成される
    - name: deploy
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./out

俯瞰し、コピペでいけるやん!となってウキウキでpushしたが、そもそもGitHub Actionsが起動しない

github.com

f:id:naari_3:20201231004603p:plain
CIが走っていることを示す・が出てくれない…

まぁCIってそういうものだし…

結局数分くらい待っても動かなかった

マネージドでないCIを運用していた経験が多いこともあり、「CIって割とコミットを取り逃したりするよね、あるある~」とか言いながら、次は適当なコミットを追加することにした

本当はamendで良いのではとも思ってたが、workflowのファイルを追加したコミットでは反応しないのでは?という邪推もあったのでコミットを追加した

しかし、やはり起動しない

github.com

f:id:naari_3:20201231010749p:plain
意味のないコミット

原因

この時点で既にリポジトリActions タブにはworkflowが追加されていたので、yml自体の悪さではないということはわかっており、何が悪いのかよくわからず手詰まりになっていた

全体を俯瞰した時、「ちゃんと on: push: branches: - master になっているしなぁ~」と見直したタイミングでようやく

「あ!デフォルトブランチってもうmasterじゃないじゃん!!!」

となりました…

いつかのタイミング以降、リポジトリを新規で作成するとデフォルトブランチは master ではなく main になっているのでみなさんは注意してください…

github.com

f:id:naari_3:20201231010829p:plain
念願の✔

おわりに

今会社で使っているGH:eはまだデフォルトがmasterのままで、それに依存した実装とか存在している覚えがあるのでその対応をしなければならない気がする

多分他の人もこれで困ると思う(特に過去のコードをコピペだけで動かしたいという需要は絶対に存在する)し、各自が気をつける、サンプルを更新するなどしていただきたい

ARIB外字をVLCで表示するには

はじめに

VLCARIB外字の字幕を表示する方法

ろくに規格を見ずにソースコードと振る舞いの確認のみで終わらせてしまったので間違った情報かもしれません、まったく精査してません

tl;dr

drcs_conv.ini を育てる必要がある

nkoriyama/aribb24

詳細

VLCで撮り溜めたm2tsを見るとたまに次のようなものを見ることがある

f:id:naari_3:20201225024350p:plain

例では薩摩の という字が に置き換えられている

これは外字置換という事象が発生している

ARIB外字とは

ja.wikipedia.org

ひらたく言うと、放送データに添付される字幕データにはたまにShift_JISの範囲外になるような規格の文字も含まれている そのような字のことをARIB外字と呼ぶ
(ARIB外字制定後一部はShift_JISの範疇になったらしいが、それ以外については)同時にフォントのデータも送信される これはARIB STD-B24という規格によって定められた方法で送られるらしい
基本的にはJIS X 0208に収録されいてない文字が対象になっており、先程例としてあげた もその対象になっている

外字置換とは

外字置換とは、上記のデータが受信できなかった/送信されなかった/使用されなかった場合に代替として使われる文字に置換されることで、上で上げた例のようによく下駄文字が使われる

VLCでのARIB外字の扱い方をしらべる

例で上げた通りだが、そのまま表示するだけでは下駄になってしまう

検索すると出てくる情報

この件について検索すると https://github.com/nkoriyama/vlc-aribsub パッチをあてたVLCついての話が出てくる
特別な対応を当てる必要があることの記述があるが、結果としては確認せずスキップで良さそうだった

それ以外にもおすすめのフォントについての記述があるので、それだけは見ても良さそうに思う(がこれでは解決しなさそう) VLCでARIB字幕を見る · GitHub

また、このページで紹介されているパッチやビルドがskydriveの消滅とともにリンク切れになっており内容を推測する他ないのだが、nkoriyama/aribb24というARIB STD-B24を扱うライブラリを使うパッチなのではないかと思っている

根拠として、vlc-aribsubの説明はほとんどnkoriyama/aribb24の動作と同じであることが挙げられる

であるならば、パッチがあてられたVLCを使用している前提でvlc-aribsubのREADME.mdに従うことでARIB外字を扱うことができるのではないかと推測した

実際に解決できた方法

実は最新版(少なくとも3.0.11)のVLCではnkoriyama/aribb24を使用するパッチは既にマージされていて、残りは単に所定のディレクトリにしかるべきファイルを置くだけで動作する

このあたり参照 https://github.com/videolan/vlc/blob/95e01f73802460ec2374894cdb769888e23b108d/contrib/src/aribb24/rules.mak

aribb24のbasedirを指定している箇所 https://github.com/videolan/vlc/blob/95e01f73802460ec2374894cdb769888e23b108d/modules/codec/arib/aribsub.c#L134

aribb24に使用するbasedirを指定する箇所 https://github.com/videolan/vlc/blob/95e01f73802460ec2374894cdb769888e23b108d/modules/codec/arib/aribsub.c#L200

実際にそのようにしてみる

%APPDATA%\vlc\arib を確認してみると、既に data ディレクトリが存在している
中身を確認すると、次のような画像が存在していた

f:id:naari_3:20201225034248p:plain
b1d62062d089551469efaeb94466263e.png
これは https://github.com/nkoriyama/vlc-aribsub の説明どおりで、受信したビットマップのデータは保存するが、特に使用されることはない

一部外字については、ビットマップデータ自体がDRCS外字として伝送されます。 このデータを直接画面に表示することもできなくはないのですが、現状は既存の文字に置換して表示するようにしています。

(実装としてはこのあたり https://github.com/nkoriyama/aribb24/blob/5e9be272f96e00f15a2f3c5f8ba7e124862aec38/src/drcs.c )

そのため、ここで受信したような文字について、(おそらく)手動でマッピングをする必要がある

そのファイルが drcs_conv.ini となる

drcs_conv.init の記述方法

単純で、先程の保存されたビットマップのファイル名( b1d62062d089551469efaeb94466263e.pngb1d62062d089551469efaeb94466263 部分)と、それに対応するUnicode文字を記述すれば良い

https://github.com/nkoriyama/vlc-aribsub/blob/master/drcs_conv.ini が参考として優秀だったが、以下に具体的な追加例を示してみる

例えば次の通り

; ここ以上は https://github.com/nkoriyama/vlc-aribsub/blob/master/drcs_conv.ini そのままか何もなしの状態
; 薩
b1d62062d089551469efaeb94466263e=U+85A9

このようなファイルを次のパスに置いてからVLCを起動する

%APPDATA%\vlc\arib\drcs_conv.ini

すると、実際に表示される字幕も次のようになる

f:id:naari_3:20201225035025p:plain

また、このマッピングは好きな文字を割り当てることが出来るので、ミスったりすると次のような感じになる

f:id:naari_3:20201225035134p:plain
パソ摩

これで一旦対応されていないAIRB外字のマッピングに成功できた

基本的には下駄文字が表示されたときに %APPDATA%\vlc\arib\data を確認し、追加された画像を参考に置き換える文字を探せばよいのだともう

疑問

これ、インターネットの集合知を利用して更新された健全な drcs_conv.ini があれば良いと思うのですが、無いのでしょうか?

個人同士で管理するのはちょっと馬鹿げていると思います

2021年の目標

かっぱさんに「なありさんも書いたら」と言われてせっかくだったので書いてみた

優先度順とかではない

  • 音MADの映像を作る
    • そろそろ川谷絵音と決別する
    • 183を消費する
  • DTMでトーナルやコード進行から逃げない
  • レーベルにトラックを投げてみる
  • mixを3ヶ月に1つは作る
  • 保険証を再発行する
  • マイナンバーカードを再発行する
  • 引っ越しをする
  • このブログをちゃんとやる
    • 溜まっている下書きを消費する
  • 整理整頓のスキルを身につける
  • おしゃれに気を使ってみる
  • チェーン店でない好きな飲食店を見つける
  • よく使うようなコードをギターですぐ弾けるようにする
  • キーボードに再度向き合う
  • ブラウン管でプレイしながらキャプチャできる環境を作る
  • いいかげん録画鯖を作る
  • グラボを買う
  • 自宅のk8sクラスタを構築する
  • アドセンス広告を貼れるようなサービスを作る
  • SRE的な力をつける
    • やはりCI/CDをちゃんとやりたいし、自分がアプリ開発メンバーとしてjoinしたい環境がいい
  • 給料を上げることを考える

A

Amazon Prime Nowを使いました

さようならではない

Amazon Prime Nowとは

ここを見てください

Amazon Prime Now(プライム ナウ) - 生鮮食品、日用品を最短2時間でお届け

なぜ今

(ずっとだけど)コロナが怖くなって外に出ていないが、食料を用意するのはやはり必要なようで、その手段が必要だった

Uber eatsを週7回利用してみると高くつき、週1で外に出るのもなかなかに面倒なうえに人混みを避けることが出来なさそうだった

そのタイミングでAmazon Prime Nowの広告が飛んできたので使ってみる

  • 2年くらい前に一度だけ使ったが、最短で1時間以内の配送が可能だった覚えがあったのでそれも動機だった

Amazon freshにあるミールキットが購入できる

Amazon freshに対応している地域ではないが、一部はAmazon Prime Now経由で購入できた

生鮮食品のレパートリーはごく一部のみ

ライフ(スーパーマーケット)から注文できるっぽい

Amazon Prime Nowを通して↑ができるらしい

ライフは自前でネットスーパーをやっているはずだが、それでも共通のインターフェイスで触れるのは素晴らしい

ただしAmazonとは別で発送が行われるので考慮する必要がある

計画性が必要

Amazon Prime Nowは2000円とか2500円とかの下限を超えないと注文することが出来ない

また配達方法についても以下の二種類あるが、どちらも少し難点がある

  • 2時間便

    • これは2時間以内に来るものではなく、「明日以降で2時間ごとの枠のうちいつ届くか指定できる」というもの
    • 送料無料
  • 1時間便

    • 本当に一時間以内で届く(らしい)
    • 送料890円

本当にすぐ欲しい場合は1000円弱払えば良いが、それだったらUber eatsとあまり変わらない気がする

下限と送料の2つを考慮すると、結局のところ食料を調達するにはある程度の計画性をもって注文、行動しないといけないことがわかってしまった

食は難しい

GoのCLI用ライブラリcobraのzsh用の補完生成機能は弱い (?) のと、それをどうするかの話

フラグのカスタム補完がうまくできなかったので書きました

cobra の補完生成機能とは

みんな大好き cobra には bashzsh 向けの補完生成機能が存在する

詳しくは cobra/bash_completions.md at master · spf13/cobra · GitHub を見てほしいけど、こんな感じ ↓ で簡単に補完の機能を生成し、出力させることが出来る

# https://qiita.com/minamijoyo/items/9dceb1d8a66e48ab45cd
package cmd

import (
    "os"

    "github.com/spf13/cobra"
)

func init() {
    RootCmd.AddCommand(newCompletionCmd())
}

func newCompletionCmd() *cobra.Command {
    cmd := &cobra.Command{
        Use:   "completion",
        Short: "Generates shell completion scripts",
        Run: func(cmd *cobra.Command, args []string) {
            cmd.Help()
        },
    }

    cmd.AddCommand(
        newCompletionBashCmd(),
        newCompletionZshCmd(),
    )

    return cmd
}

func newCompletionBashCmd() *cobra.Command {
    cmd := &cobra.Command{
        Use:   "bash",
        Short: "Generates bash completion scripts",
        Run: func(cmd *cobra.Command, args []string) {
            RootCmd.GenBashCompletion(os.Stdout)
        },
    }

    return cmd
}

func newCompletionZshCmd() *cobra.Command {
    cmd := &cobra.Command{
        Use:   "zsh",
        Short: "Generates zsh completion scripts",
        Run: func(cmd *cobra.Command, args []string) {
            RootCmd.GenZshCompletion(os.Stdout)
        },
    }

    return cmd
}

cobra.CommandGen{Ba,Z}shCompletion という関数が生えているので、それを叩くだけ

例に示した通りに bash 用、zsh 用の関数があることがわかる

これを実装した後はこんな感じ ↓ で補完を読み込むようにして、再度 shell を開くなりして補完を利かすことが出来る

$ echo ". <(hogefuga completion bash)" >> ~/.bashrc
$ hogefuga completion zsh > /usr/local/share/zsh/site-functions/_hogefuga

すごくシンプルなものであればこれで結構事が足りて、用意したフラグ名についても正しく補完されるようになる

custom completion について

たとえば docker の補完の場合、 docker container rm <TAB> と入力すると現在起動しているコンテナ一覧が表示され、容易に好きなコンテナを指定することが出来る

これは、補完のスクリプト内で docker のコンテナ一覧を取得する処理が走っており、任意の変数に代入されることで実現している

これを cobra で実現する場合、次のようなコードを書くことになる

const (
        bash_completion_func = `__docker_get_container()
{
    local docker_output out
    if docker_output=$(docker container ls -q 2>/dev/null); then
        out=($(echo "${docker_output}" | awk '{print $1}'))
        COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
    fi
}

__docker_custom_func() {
    case ${last_command} in
        docker_container_rm)
            __docker_get_container
            return
            ;;
        *)
            ;;
    esac
}
`)

// 省略

cmds := &cobra.Command{
    Run: runSomething,
    BashCompletionFunction: bash_completion_func,
}

詳しく説明しないし、↑ のコードは動かしてないから動作しないかもしれないけど簡単に言うと補完するコマンドが見つからない際に __docker_custom_func が発火され、そこでなんというコマンドが実行されたか見て、ここで docker_container_rm だった場合は __docker_get_container が発火される

__docker_get_container はいろいろやってるけど結局は COMPREPLY にコンテナの id 一覧を代入している

ちなみにフラグにわたす場合はこんな感じ

func rootCmd() *cobra.Command {
    cmd := &cobra.Command{
        RunE: func(c *cobra.Command, args []string) error {
            // something
        },
        BashCompletionFunction: `__docker_get_container()
{
    local docker_output out
    if docker_output=$(docker container ls -q 2>/dev/null); then
        out=($(echo "${docker_output}" | awk '{print $1}'))
        COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) )
    fi
}
`,
    }

    # しょうりゃく

    cmd.Flags().StringVar(&opts.profile, "container", "fuga", "set container id")
    cmd.MarkFlagCustom("profile", "__docker_get_container")

    # しょうりゃく
}

cmd.MarkFlagCustom で指定のフラグに対する補完用のスクリプトを指定する

zsh では custom completion が効かない

他は大体いい感じにしてくれるのに、custom completion だけ使えない

公式にある zsh_completions.md にはこう書かれている

What's not yet Supported

  • Custom completion scripts are not supported yet (We should probably create zsh specific one, doesn't make sense to re-use the bash one as the functions will be different).
  • Whatever other feature you're looking for and doesn't exist :)

なので、上の例に登場していた BashCompletionFunctionzsh 版みたいなものは存在しない

つまり、素直に GenZshCompletion を叩いたところで custom completion は全く効かない

じゃあどうすればよいのか

でも我々は cobra 製である kubectl、minikube(CLI)あたりに zsh 用の素晴らしい補完が用意されていることを知っている

あれはどうやって custom completion を実現しているのだろうか?

結論はこんなかんじ ↓ だった

  1. bash 用の補完スクリプトを生成する
  2. bash 用の補完スクリプトから zsh 用の補完スクリプトに変換するスクリプトを生成する
  3. 1 と 2 を結合して完成

まじかよ…

実際のコードはこんな感じ

bash to zsh 用テンプレートの中に GenBashCompletion の結果を埋めていることがわかる

各自で開発している CLI にこのテンプレートを埋め込む場合は minikube の GenerateZshCompletion を参考にするとよさそう ( __minikube などの文字列を __hogefuga に置き換えるだけで動いた)

おわりに

あまり zsh と補完に詳しくないからわからないけど、なんで cobraZshCompletionFunction を実装しないの…?

full_joinというgemを作った

rubygems.org

github.com

名前が悪い

どういうgemか

こういう処理をしてくれる Array#full_join を提供する

Hoge = Struct.new(:id, keyword_init: true)

array1 = [Hoge.new(id: 1), Hoge.new(id: 2), Hoge.new(id: 3)]
array2 = [Hoge.new(id: 2), Hoge.new(id: 3), Hoge.new(id: 4)]

array1.full_join(array2)
#=> [
#  [#<struct Hoge id=1>, nil]
#  [#<struct Hoge id=2>, #<struct Hoge id=2>]
#  [#<struct Hoge id=3>, #<struct Hoge id=3>]
#  [nil, #<struct Hoge id=4>]
#]

こんな使い方もできる

Hoge = Struct.new(:id, :name, keyword_init: true)
Fuga = Struct.new(:id, :name, keyword_init: true)

array1 = [
  Hoge.new(id: 1, name: "AAA"),
  Hoge.new(id: 2, name: "BBB"),
  Hoge.new(id: 3, name: "CCC")
]
array2 = [
  Fuga.new(id: 101, name: "BBB"),
  Fuga.new(id: 102, name: "CCC"),
  Fuga.new(id: 103, name: "DDD")
]

array1.full_join(array2, &:name)
#=> [
#  [#<struct Hoge id=1, name="AAA">, nil],
#  [#<struct Hoge id=2, name="BBB">, #<struct Fuga id=101, name="BBB">],
#  [#<struct Hoge id=3, name="CCC">, #<struct Fuga id=102, name="CCC">],
#  [nil, #<struct Fuga id=103, name="DDD">]
#]

ようはFull Outer Joinみたいなまとめ方をしつつArray#zipするようなメソッドを追加してくれる

業務のコードを書くときにこれと同じ動きしてほしい*1、という場面に数回ぶつかったのでgemを作っちゃった

名前が悪い

最初は「こういうのってなんていう名前の配列操作なんだろう」と悩んで自分なりに検索したけど見つからなかった

どう形容すべきか分からなかったし、full_joinって名前もこの操作にピッタリあっているとは思えない

*1:とあるワードごとのモデルの対応表を作ることになっている

Railsの静的なエラー画面表示時にhtml以外でもpublic以下のファイルを読むようにするRack middleware

Railsで例外が発生し、かつApplicationControllerとかで拾われない場合、通常は設定された exceptions_app ( デフォルトだと ActionDispatch::PublicExceptions ) が呼ばれてエラー画面表示の処理が行われる

application/html がcontent-typeに指定されてると public/500.html とか public/404.html とかを返してくれるけど、htmlじゃない場合はある規定された形のhashに対して to_xxx したものをbodyとして返すだけになってしまい、こちらが定めた定形のメッセージを返そうとした際に少し不便になる

実際に public/500.html を返すMiddlewareはどうなっているかというと以下の通りになっている

rails/public_exceptions.rb at 4dcb46182a4aaa57f44f3eb722c1db54fa0ff843 · rails/rails · GitHub

今回はこれの各種 content-type 対応版を書いた

まだgemにはしてないが以下の通り (というか既に存在しそうだし)

class MimeTypePublicExceptions < ActionDispatch::PublicExceptions
  private

  def render(status, content_type, _body)
    ext = content_type.symbol || 'html' # symbolは拡張子を表すメソッドではない
    path = [
      "#{public_path}/#{status}.#{I18n.locale}.#{ext}",
      "#{public_path}/#{status}.#{ext}"
    ].find { |fp| File.exist?(fp) }
    if path
      render_format(status, content_type, File.read(path))
    else
      [404, { 'X-Cascade' => 'pass' }, []]
    end
  end
end

こんなかんじでつかう

Rails.application.configure do
  config.exceptions_app = lambda do |env|
    MimeTypePublicExceptions.new(Rails.public_path).call(env)
  end
end

おわり