ぽよメモ

レガシーシステム考古学専攻

Entrykitのrenderで遊ぶ -後編-

頑張りすぎじゃ無いか感が溢れてきます.

発展的な記法

環境変数からの値の取得しか存在しないわけでは無く,色々できます.

include

これは比較的便利に扱えるのでは無いでしょうか,templateのモジュール化ができます.また,第二引数以降に"VAR=VALUE"の形で渡すと,includeするtemplateの中からvar "VAR"などとして値を取り出すことが出来ます.

ここではecho '{{ var "INCLUDED" }}' > include_test.tmplとしてincludeするtemplateを作成しておきます.

{{ include "include_test.tmpl" "INCLUDED=True" }} # -> True

file, text

ファイルの中身を読みとって文字列として展開します.例えばecho "hello world" > hello.txtとしてテンプレートと同じ階層に置いたとします.

# file
{{ file "hello.txt" }} # -> hello world

# text
{{ text "hello.txt" }} # -> hello world

ソースを読む限りこの二つの内部的な実装は全く同じに見えます*1

dir, dirs, files

引数として任意のディレクトリへのパスを渡します.文字列を返すわけでは無いことに注意してください.

# 指定されたディレクトリ中のディレクトリ・ファイルの名称の配列を返す
{{ dir "./" }}

# 指定されたディレクトリ中のディレクトリのみの名称の配列を返す
{{ dirs "./" }}

# 指定されたディレクトリ中のファイルのみの名称の配列を返す
{{ files "./" }}

存在しないディレクトリを参照しようとするとnot foundなエラーを発して中断します.

httpget, urlquery

APIリクエストしたりすることができます*2ca-certificatesをインストールしていないとエラーを吐いて死にます.

{{ httpget "http://search.twitter.com/search.json?lang=ja&rpp=20&q=%23poyo" }}

注意すべきなのは,返すオブジェクトが文字列では無いことでしょう.Goのオブジェクトが返却されるため,htmlがそのまま出力されるわけではありません.

json, tojson

ではAPIリクエストしたところでどうするんだという話ですが,これでjsonオブジェクトに変換できます.

{{ httpget "http://search.twitter.com/search.json?lang=ja&rpp=20&q=%23poyo" | json }}

これもまた返り値はGoのオブジェクトなのでノイズが混ざっています.純粋にレスポンスを記述するだけなら以下の様にします.

{{ httpget "http://search.twitter.com/search.json?lang=ja&rpp=20&q=%23poyo" | json | tojson }}

tojsonはgoのオブジェクトを受取り,jsonをstringにして返します.

yaml, toyaml

jsontojsonyaml版です.

split, join

stringから配列を生成することができます.

# 配列の生成
{{ split "a,b,c,d" }} # -> [a, b, c, d]

# 任意のセパレータで区切った文字列に変換
{{ split "a,b,c,d" | join ":" }} # -> a:b:c:d

splitkv, joinkv

少し難解で使いどころがよく分からないですが,splitjoinが配列を扱うのに対してmapを扱います.

# mapの生成
{{ split "\n" "a=A,b=B,c=C" | splitkv "=" }} # -> [a=A b=B c=C] 

# mapの値へアクセス
## mapを変数へ代入
{{ $map_var := (split "\n" "a=A,b=B,c=C" | splitkv "=") }}
 ## アクセス
{{ $map_var.a }} # -> A
{{ $map_var.d }} # -> <no value>
## 代入はできないようでエラーを吐く
{{ $map_var.d := "D" }} # -> unexpected ":=" in operand

# 任意のセパレータで文字列にする
{{ joinkv ":" $map_var }} # -> [a:A b:B c:C]

joinkvは直接文字列を返すわけではなく,key{{任意のセパレータ}}valueの形で文字列化し,その配列を返します.全体を文字列にするにはさらにjoinをはさむ必要があります,

seq

数値の配列を作成します.引数に数字(intまたはstring)をとり,0からその値までの配列を作って返します.

{{ seq 10 }} # -> [0 1 2 3 4 5 6 7 8 9 10]

append, drop

配列のへの値の追加,削除ができます.

# 数値でも
{{ seq 10 | append 11 | drop 1 }} #-> [0 2 3 4 5 6 7 8 9 10 11]

# 文字列でも
{{ split "," "a,b,c,d" | append "e" | drop "a" }} # -> [b c d e]

index

配列への添字アクセスができます.

{{ index (seq 5) 1 }} # -> 1

第一引数に配列を取ることに注意が必要です.また,index {{任意の多次元配列}} 1 2 3などは{{任意の多次元配列}}[1][2][3]と同義になります.

sh

もはやなんでもありですが任意のコマンド実行が出来ます.出力された値が文字列として取得できます.

{{ sh "ping -c 1 www.google.com" }}

sigilにはあるが使えなかったもの

  • jmespath

jsonからクエリを使って値を取り出すことができるようなのですが,renderではnot definedと言われてしまいました.

組み合わせる

ファイル一覧でループ

配列の生成ができることから,色々なループ処理が可能になります.例えば任意のディレクトリ内のファイル一覧に対して,*.logの場合には監視対象に加える,などといった処理が可能になります.

{{ range $index, $f := (files "/path/to/log") }}
{{ if match $f "*.log" }}
    something $f to do
{{ end }}

環境変数を判定

()を用いるとだいたいなんでもできるので,例えば環境変数も(どっかのQiitaではできないとか書いてましたが)ifで用いることが出来ます

{{ if eq "test" (var "TEST") }}
  This is test.
{{ else }}
  This is not test.
{{ end }}

まとめ

もっと色々やろうかと思ったけど力尽きた.

*1:textの方は何かエラー処理のコードが入っていますが,エラーがnilかどうかを判定して受け流しているだけのように思います.

*2:例ではTwitter API 1.0を使用しているので普通にエラーメッセージが返ってきます

Entrykitのrenderで遊ぶ -前編-

Dockerコンテナでsedで頑張っているみんながたどり着くところ.

Entrykitとは

github.com

たぶん僕がかいつまんで紹介するよりもこちらの記事の方が全体の紹介がしっかりされているのでこちらを読んで頂ければ良いと思います.

qiita.com

今回扱うのはそのうちのrenderで,シェルスクリプトで頑張っていたところをGo言語のtemplate機能で楽にやろうというものですね.

できること

  • 環境変数からの値の埋め込み
  • ifによる条件分岐
  • rangeによるループ
  • template中での変数の宣言と使用
  • ファイルの読み込み
  • インターネットリソースの取得
  • 任意のコマンド実行結果の取得

できないこと

もし以下のことも可能であるならコメント等で教えて頂きたいです.

  • template中での四則演算
  • 改行モードの変更*1

環境

ここでは以下の様なDockerfileのコンテナを用意し試しています.

FROM alpine:latest

ENV WORKDIR /workdir/
ENV ENTRYKIT_REPO progrium/entrykit
ENV ENTRYKIT_VERSION 0.4.0
ENV ENTRYKIT_DL_FILE entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz

RUN mkdir ${WORKDIR}

RUN apk --update --no-cache add wget ca-certificates\
    && wget --no-check-certificate \
    https://github.com/${ENTRYKIT_REPO}/releases/download/v${ENTRYKIT_VERSION}/${ENTRYKIT_DL_FILE} \
    && tar xvzf ${ENTRYKIT_DL_FILE} \
    && rm ${ENTRYKIT_DL_FILE} \
    && mv entrykit /bin/entrykit \
    && chmod +x /bin/entrykit \
    && entrykit --symlink \
    && apk --no-cache del wget

WORKDIR ${WORKDIR}

CMD ["render", "test.conf", "--", "cat", "test.conf"]

カレントディレクトリに試したいtest.conf.tmplというテンプレートファイルを用意してコンテナの/workdirにマウントして実行すると最後にrenderした結果を出力してくれます.

$ docker run --rm -v $PWD:/workdir {{ビルドしたコンテナ}}

基本文法

基本的な文法はGoのtext/template*2と同じです.それに加えていくつかの特殊な文法を使用することが出来ます(ここではそれらの区別に関して言及しません).ここではそれらのうちの基本的な部分を示します.
ちなみに,内部的にはSigilというテンプレートエンジンを使用しているため,Goが読める人はこっちを読んだ方が早いかも知れません.

github.com

パイプライン

実行結果をチェーンすることができます.前のコマンドの実行結果が,最後の引数として与えられます.つまり,

{{ did hoge | do }}

という式は

{{ do (did hoge) }}

と等価です.

変数宣言

{{ $x := "hoge" }}

これでxという変数を宣言したことになり,以下の様に呼び出すことが出来ます.

{{ $x }} # -> hoge

ifなどの構文中でも使用できます.

var

環境変数から値を取得します.内部ではos.Getenv()しているだけなので,存在しない場合は空文字列が返ります.

{{ var "VARIABLE" }}

if

条件分岐が使えます.

{{ if expr1 }}
    expr1 is True
{{ elif expr2 }}
    expr2 is True
{{ else }}
    False
{{ end }}

また,比較演算子として以下のものが使用できます.

  • eq : arg1 == arg2 || arg1 == arg3 || …
  • ne : arg1 != arg2
  • lt : arg1 < arg2
  • le : arg1 <= arg2
  • gt : arg1 > arg2
  • ge : arg1 >= arg2

論理演算子の文法は以下です.

{{ if and expr1 expr2 }}
    expr1 and expr2 are True
{{ elif or expr1 expr2}}
    expr1 or expr2 is True
{{ end }}

{{ if not expr }}
  expr is not True
{{ end }}

ただしandを使用した場合,expr1,expr2はどちらも評価されます.

range

ループ表現が使えます.ただしこのexprはiterableなオブジェクトである必要があります.具体的には後編で記述します.

{{ range expr }}
{{ . }}
{{ end }}

{{.}}で現在参照しているオブジェクトを取得できます.

補助的な文法

これらの表現はいずれもパイプラインでチェーンして文字列を受取り,文字列を返します*3

default

値が存在しない場合,デフォルト値を設定します.

{{ var "NOT_EXISTS_VAR" | default "default"}}

capitalize

最初の1文字が小文字の時,大文字に変換します.

{{ "abcd" | capitalize }} # -> Abcd

lower

全て小文字にします.

{{ "AbCdEf" | lower }} # -> abcdef

upper

全て大文字にします.

{{ "AbCdEf" | upper }} # -> ABCDEF

replace

与えたテキスト中の特定の文字列を全て置き換えます.内部的にはstrings.Replace()しているだけなので,正規表現などを使うことは出来ません.

{{ "Target Text (old)" | replace "old" "new" }} # -> Target Text (new)

trim

文字列の両端から指定文字を取り除きます.具体的には内部でstrings.Trim()の第二引数に\nを渡しているので,改行コードと空白が両端から取り除かれます.
docker-compose.ymlなどでyamlで複数行の文字列を渡したときも全体の両端しか認識しないため,文中の改行は保持されます.

{{ "    a b c    " | trim }} # -> a b c

indent

二行目以降の先頭に特定の文字列を付加します.使い方がイマイチ分からないですが,第二引数で渡した文字列を\nを認識して分割,2つめ以降の要素の先頭に第一引数の文字列を付加します.

{{ "This is a pen.\nThis is not a pen." | indent "a" }}

結果は以下の様になります.

This is a pen.
aThis is not a pen.

len

文字列の長さを返します.

{{ "abcd" | len }} # -> 4

Sigilにはあるが使えないもの

  • substr

not definedと言われてしまいました.

改行の取り扱い

goのtemplateのように-で改行を制御することが(試した限りでは)できないようです.つまり,制御構文を書いた行は空行になってしまいます.

# 以下のif文をrender
{{ if eq 4 (len "abcd") }}
    This term has 4 characters.
{{ else }}
    This term has not 4 characters.
{{ end }}

上の様なコードをrenderすると以下の様になります.

# 以下のif文をrender

    This term has 4 characters.


あまり空行が問題になることは無いかとは思いますが,気になる場合は以下の様にワンラインで書く方が良いかも知れません.

{{ if eq 4 (len "abcd") }}    This term has 4 characters.{{ else }}    This term has not 4 characters.{{ end }}

前編まとめ

このように,いちいちsedでコンフィグを書き換えなくても.tmplファイルを用意すれば簡単に埋め込みが出来ます.
正直これくらいまでがこのツールの良い使い道でこれ以上は無理しすぎではという感じもしますが,一応出来るので後編で紹介していきます.

[ 2017/09/22 追記 ] 後編書きました.

poyo.hatenablog.jp

*1:Golangのtemplate機能ではここにあるように-の有無で改行を制御できるのですが,renderではinvalidだと怒られてしまいました. developers.eure.jp

*2:template - The Go Programming Language

*3:別に使い方としてそう決められているわけでは無いですが,主な使い方としてはそうなるでしょう.

alias vim="nvim"した

動機

院試が終わったから積んでたタスクをこなしていきます.
かつてvim + NeoBundleで頑張っていた頃はどうにも重かったので最近はプラグイン類を敬遠していましたが,最近流行りのneovimはヌルヌルらしいと聞き前から試してみたいと思っていました.

環境

  • macOS Sierra 10.12.6
  • Homebrew 1.3.1
  • iTerm2 3.0.15
  • neovim v0.2.0
  • pyenv 1.1.0
  • python 2.7.13
  • python3 3.6.0

pyenvで作った環境のpythonを使用します.

今回のゴール

  1. neovimへ移行する
  2. dein.vimでtomlファイルによるプラグイン管理をする
  3. vim-airlineで見た目をかっこよく

neovimへ移行する

インストール

brewから叩くだけ.

$ brew tap neovim/neovim
$ brew install neovim

pyenvで仮想環境を作成します(pyenv-virtualenvを利用しています ).

$ pyenv install 2.7.13 3.6.0
$ pyenv virtualenv 2.7.13 neovim2
$ pyenv virtualenv 3.6.0 neovim3
$ pyenv global neovim2 neovim3
$ pyenv which python
/usr/local/var/pyenv/versions/neovim2/bin/python
$ pyenv which python3
/usr/local/var/pyenv/versions/neovim3/bin/python3

neovimの真価を発揮させるためにpython用ライブラリをインストールします.

$ pip install --upgrade neovim
$ pip3 install --upgrade neovim

neovimを起動し,:echo has("python3")して1が返ってきたらOK.

設定ファイルを作る

~/.config/nvim以下に設定ファイルを作るのが主流のようなのでそれに乗っかります.

$ vim ~/.bashrc
# 以下を追記
export XDG_CONFIG_HOME=/Users/<ユーザ名>/.config
export XDG_CACHE_HOME=/Users/<ユーザ名>/.cache

設定ファイルはinit.vimになります.

$ mkdir ~/.config/nvim
$ touch ~/.config/nvim/init.vim

aliasを張る

$ echo "alias vim='nvim'" >> ~/.bashrc

dein.vimプラグイン管理

暗黒美夢王ことShougo氏に感謝.

インストール

githubの通りです.

github.com

また,インストールディレクトリは主流に従って~/.cacheとします(.bashrcに追記したXDG_CACHE_HOMEになります).

$ curl https://raw.githubusercontent.com/Shougo/dein.vim/master/bin/installer.sh > installer.sh
$ sh ./installer.sh ~/.cache/dein

tomlでプラグイン管理

この状態ではまだdein.vimは読み込まれないのでinit.vimに設定を追記していきます.

" ============== dein =================
" Pythonインタプリタへのパスを指定
" Ubuntuでも使い回す予定なので分けています
if has("mac")
    let g:python3_host_prog = '/usr/local/var/pyenv/versions/neovim3/bin/python3'
    let g:python_host_prog = '/usr/local/var/pyenv/versions/neovim2/bin/python'
else
    let g:python3_host_prog = '/usr/bin/python3'
    let g:python_host_prog = '/usr/bin/python'
endif

" 各種ファイルへのパス
let s:dein_cache_dir = $XDG_CACHE_HOME . '/dein'
let s:dein_config_dir = $XDG_CONFIG_HOME . '/nvim'
let s:dein_repo_dir = s:dein_cache_dir . '/repos/github.com/Shougo/dein.vim'
let s:toml = s:dein_config_dir . '/dein.toml'
let s:toml_lazy = s:dein_config_dir . '/dein_lazy.toml'

"dein Scripts-----------------------------
if &compatible
  set nocompatible               " Be iMproved
endif

" Required:
let &runtimepath = s:dein_repo_dir .",". &runtimepath

" Required:
if dein#load_state(s:dein_cache_dir)
  call dein#begin(s:dein_cache_dir)

  " Let dein manage dein
  " Required:
  call dein#add(s:dein_repo_dir)
  
  " tomlファイルからプラグインのリストをロードしキャッシュする
  call dein#load_toml(s:toml, {'lazy': 0})
  call dein#load_toml(s:toml_lazy, {'lazy': 1})
  
  " Required:
  call dein#end()
  call dein#save_state()
endif

" Required:
filetype plugin indent on
syntax enable

" If you want to install not installed plugins on startup.
if dein#check_install()
  call dein#install()
endif

"End dein Scripts-------------------------

~/.config/nvim/dein.tomlに管理するプラグインを書いていきます.また,インサートモード時のみなどに遅延して読み込むようなプラグインdein_lazy.tomlに記述していくこととします.

まずdein.tomlから.colorschemeには比較的目に優しくtrue color対応していたtenderを使用しています.

[[plugins]]
repo = 'Shougo/dein.vim'

[[plugins]]
repo = 'Shougo/vimproc.vim'
hook_post_update = '''
    if dein#util#_is_windows()
        let cmd = 'tools\\update-dll-mingw'
    elseif dein#util#_is_cygwin()
        let cmd = 'make -f make_cygwin.mak'
    elseif executable('gmake')
        let cmd = 'gmake'
    else
        let cmd = 'make'
    endif
  let g:dein#plugin.build = cmd
'''

[[plugins]]
repo = 'jacoborus/tender.vim'

[[plugins]]
repo = 'scrooloose/nerdtree'
hook_add = '''
    nnoremap <silent><C-e> :<C-u>NERDTreeToggle<CR>
    let g:NERDTreeShowHidden = 1
'''

次にdein_lazy.toml.補完候補表示のためのエコシステムとしてこれまたShougo氏作のdeoplete.nvimを導入しています.

github.com

lexima.vimは対応する括弧の自動入力プラグインです.

[[plugins]]
repo = 'Shougo/deoplete.nvim'
on_i = 1
hook_source = '''
    let g:deoplete#enable_at_startup = 1
    let g:deoplete#enable_smart_case=1
    inoremap <expr><TAB> pumvisible() ? "\<C-N>" : "\<TAB>"
'''

[[plugins]]
repo = 'zchee/deoplete-jedi'
depends = ['deoplete.nvim']
on_ft = ['python']

[[plugins]]
repo = 'cohama/lexima.vim'
on_i = 1

on_i = 1でインサートモード時のみ起動と指定できるのがスマートで嬉しい. 次回起動時にプラグインが自動的にインストールされます.

vim-airlineでかっこよくする

colorshcemeでそれなりになるとはいえまだまだ真っ黒画面で味気ないので,実際に便利かはさておき気分的に盛り上がるプラグインを入れます.

github.com

dein.tomlに追記

[[plugins]]
repo = 'vim-airline/vim-airline'
depends = ['vim-airline-themes']
hook_add = '''   
    set laststatus=2
    let g:airline_theme = 'deus'
'''

[[plugins]]
repo = 'vim-airline/vim-airline-themes'

プラグインの導入が完了すると以下に近い見た目になると思います*1

f:id:pudding_info:20170903155926p:plain

悪くは無いけど,ちょっとダサい…他の人たちのairlineもっとかっこよくない????
というわけでvim-airlinehook_addを以下の様にします.

set laststatus=2
let g:airline_theme = 'deus'
let g:airline_powerline_fonts = 1
let g:airline#extensions#tabline#enabled = 1
let g:airline#extensions#tabline#buffer_idx_mode = 1
let g:airline#extensions#virtualenv#enabled = 1

すると以下の様に文字化けが起きます(たぶん)

f:id:pudding_info:20170903160341p:plain

これはiTerm2のデフォルトのフォントがPowerline用の文字を含んでいないためで,セパレータ部分が化けてしまうようです.

文字化けを直す

フォントのインストール

Powerline用にフォントを合成しインストールするというのが一般的な手法で,Rictyがよく使われているようだと言うことが分かりました.
macで導入する方法はいくつかありますが,まずググって出てきた方法として

$ brew tap senemat/font
$ brew install ricty --with-powerline

を試してみましたが,常にSEGVで落ちるためうまく行きませんでした.brewでインストールされるfontforgeが良くないというようなことがエラーメッセージのリンク先のissueには書いてありましたが自力でインストールするのは面倒なのでパス,

ググっているとどうもすでにパッチが当てられたフォント*2が配布されている模様.mzyy94氏に感謝を捧げつつ使わせて頂きます.

github.com

$ git clone https://github.com/mzyy94/RictyDiminished-for-Powerline.git
$ cd RictyDiminished-for-Powerline/powerline-fontpatched
$ cp Ricty*.ttf ~/Library/Fonts/

powerline-fontpatchedディレクトリに入っているフォントのみで上手くいきました.最初はvim-powerline-fontpatchedに入っているフォントを使用したのですが,うまく行きませんでした*3

iTerm2に設定

インストールしただけではダメなのでiTerm2のフォント設定を行います.
Use a different font for non-ASCII textにチェックを入れ,先ほどインストールしたフォントを選択します.

f:id:pudding_info:20170903161642p:plain

これを適用すると以下の様になります*4

f:id:pudding_info:20170903161855p:plain

しかしまだズレています.右下の部分はセパレータが半角スペース一つ分くらいズレているのがわかります.

セパレータを上書き

この半角ズレがなかなか修正できず,半日ほどかかりました.
set ambiwidth=doubleすると良いということが書かれていた記事もありましたが,これを設定しても以下の様になりさらに崩れてしまったため,今はコメントアウトしています.

f:id:pudding_info:20170903162751p:plain

最終的に以下の様にセパレータ部分の文字を指定することで解決しました.

let g:airline_left_sep = '⮀'
let g:airline_left_alt_sep = '⮁'
let g:airline_right_sep = '⮂'
let g:airline_right_alt_sep = '⮃'

f:id:pudding_info:20170903162955p:plain

正直解決法がこれで合っているのか分かりません.何か原因が他にあり,この操作の副次的な作用で解決している可能性もあります.何か分かる方はコメント頂けると嬉しいです…

課題

  • vim-quickrunまだ導入できてない
  • denite.nvimもちゃんと導入したい
  • Ubuntuでの導入にまだ着手できてない
  • python仮想環境での扱い方(補完候補の表示など)

まとめ

設定値の全体は以下にあります.

github.com

マシン自体が高速になったせいもあるでしょうが,補完候補の表示やそもそもの起動速度などがかなり快適で,乗り換えた価値がありました.
Shougo氏には足向けて寝れません.

f:id:pudding_info:20170903232551p:plain

参考にしたサイト

*1:様々なカラースキームはここScreenshots · vim-airline/vim-airline Wiki · GitHubにあります

*2:RictyではなくRicty Diminished

*3:なぜなのかは分からないです.名前的にこっちかなぁと思ったのですが…

*4:少し縦方向にズレているなと感じた場合,先ほどのフォント選択画面からCharacter Spacing > Verticalの項目を少し大きくすることで解決します