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リクエストしたりすることができます*2.ca-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
json
,tojson
のyaml版です.
split, join
string
から配列を生成することができます.
# 配列の生成 {{ split "a,b,c,d" }} # -> [a, b, c, d] # 任意のセパレータで区切った文字列に変換 {{ split "a,b,c,d" | join ":" }} # -> a:b:c:d
splitkv, joinkv
少し難解で使いどころがよく分からないですが,split
,join
が配列を扱うのに対して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 }}
まとめ
もっと色々やろうかと思ったけど力尽きた.
Entrykitのrenderで遊ぶ -前編-
Dockerコンテナでsed
で頑張っているみんながたどり着くところ.
Entrykitとは
たぶん僕がかいつまんで紹介するよりもこちらの記事の方が全体の紹介がしっかりされているのでこちらを読んで頂ければ良いと思います.
今回扱うのはそのうちの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が読める人はこっちを読んだ方が早いかも知れません.
パイプライン
実行結果をチェーンすることができます.前のコマンドの実行結果が,最後の引数として与えられます.つまり,
{{ 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 != arg2lt
: arg1 < arg2le
: arg1 <= arg2gt
: arg1 > arg2ge
: 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 追記 ] 後編書きました.
*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を使用します.
今回のゴール
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の通りです.
また,インストールディレクトリは主流に従って~/.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
を導入しています.
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でそれなりになるとはいえまだまだ真っ黒画面で味気ないので,実際に便利かはさておき気分的に盛り上がるプラグインを入れます.
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.
悪くは無いけど,ちょっとダサい…他の人たちのairlineもっとかっこよくない????
というわけでvim-airline
のhook_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
すると以下の様に文字化けが起きます(たぶん)
これはiTerm2のデフォルトのフォントがPowerline用の文字を含んでいないためで,セパレータ部分が化けてしまうようです.
文字化けを直す
フォントのインストール
Powerline用にフォントを合成しインストールするというのが一般的な手法で,Rictyがよく使われているようだと言うことが分かりました.
macで導入する方法はいくつかありますが,まずググって出てきた方法として
$ brew tap senemat/font $ brew install ricty --with-powerline
を試してみましたが,常にSEGVで落ちるためうまく行きませんでした.brewでインストールされるfontforgeが良くないというようなことがエラーメッセージのリンク先のissueには書いてありましたが自力でインストールするのは面倒なのでパス,
ググっているとどうもすでにパッチが当てられたフォント*2が配布されている模様.mzyy94氏に感謝を捧げつつ使わせて頂きます.
$ 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
にチェックを入れ,先ほどインストールしたフォントを選択します.
これを適用すると以下の様になります*4.
しかしまだズレています.右下の部分はセパレータが半角スペース一つ分くらいズレているのがわかります.
セパレータを上書き
この半角ズレがなかなか修正できず,半日ほどかかりました.
set ambiwidth=double
すると良いということが書かれていた記事もありましたが,これを設定しても以下の様になりさらに崩れてしまったため,今はコメントアウトしています.
最終的に以下の様にセパレータ部分の文字を指定することで解決しました.
let g:airline_left_sep = '⮀' let g:airline_left_alt_sep = '⮁' let g:airline_right_sep = '⮂' let g:airline_right_alt_sep = '⮃'
正直解決法がこれで合っているのか分かりません.何か原因が他にあり,この操作の副次的な作用で解決している可能性もあります.何か分かる方はコメント頂けると嬉しいです…
課題
- vim-quickrunまだ導入できてない
- denite.nvimもちゃんと導入したい
- Ubuntuでの導入にまだ着手できてない
- python仮想環境での扱い方(補完候補の表示など)
まとめ
設定値の全体は以下にあります.
マシン自体が高速になったせいもあるでしょうが,補完候補の表示やそもそもの起動速度などがかなり快適で,乗り換えた価値がありました.
Shougo氏には足向けて寝れません.