devdocs.io が便利すぎたので Vim プラグインつくった
devdocs.io
最近,GitHub Trending Repositories のページで devdocs.io という便利なサービスを知りました.
devdocs.io は複数のドキュメントを素早く横断的に検索できるサービスです.多分使ってみると一瞬で分かるので詳細は省きますが,各言語や DOM,React などのフレームワークのドキュメントをサクッと検索できます.どのドキュメントを有効/無効にするかも選択でき,IndexDB を使ってローカルにドキュメントを置くことでローカルでも利用できます.いつでも devdocs.io を開くだけで使えますし,モバイル対応もしています.
また,Ruby 2.2 と Node.js が入っていればローカルでも簡単に立てられます.デフォルトでもウェブデベロッパーにとってうれしいドキュメントがたくさん入っていますが,さらに Scraper を使ってローカルの devdocs に新しいドキュメントを追加することもできるようです.
devdocs.vim とは
ここ数日試しに使ってみて,なかなか使い勝手が良かったので Vim 内から直接 devdocs.io を開けるようにプラグインを作りました.
主な目的は下記の2つです.
- Vim の既存の
K
マッピングを使って,カーソル下の単語をすぐに devdocs.io で検索したい - ファイルタイプに特定のドキュメントを紐付けたい
Vim の既存の K
マッピングを使って,カーソル下の単語をすぐに devdocs.io で検索したい
Vim にはカーソル下の単語を直接ドキュメント検索できる K
というマッピングがあり,デフォルトでは Vim のヘルプを開いたり man を開いたりできます.この K
を下記のように特定のファイルタイプだけ devdocs.io で開くように設定できます.
augroup plugin-devdocs autocmd! autocmd FileType c,cpp,rust,haskell,python nmap <buffer>K <Plug>(devdocs-under-cursor) augroup END
例えば上記では C, C++, Rust, Haskell, Python のコードを書いている時に K
で直接 devdocs.io を開きます.
ファイルタイプに特定のドキュメントを紐付けたい
例えば,僕は JSX なコードには javascript.jsx
というファイルタイプを割り当てていて,この時は React.js のドキュメントだけ見たいとします.また,devdocs.io には TypeScript のドキュメントはありませんが,TypeScript を書いている時は JavaScript のドキュメントが役に立つので,TypeScript を書いている時は JavaScript のドキュメントを開きたいとします.その場合は下記のように設定すると,各ファイルタイプでページを開いた時のデフォルトのドキュメントを指定できるようにしました.
let g:devdocs_filetype_map = { \ 'typescript': 'javascript', \ 'javascript.jsx': 'react', \ }
ローカルの devdocs.io を開きたい
変数で指定できるようにしました.
let g:devdocs_host = 'localhost:9292'
その他の devdocs.io クライアント
下記のようなツールがあります.便利そう…
- Alfred workflow
- Electron でデスクトップアプリ化
- シェルから直接 devdocs を開けるスクリプト(アドレス失念)
東京Node学園祭 2015 で Electron について話した
画像は公式サイトから
発表について
11/7 にあった 東京 Node 学園祭 2015 で Electron の発表した.
周りすごい人達ばかりで,明らかに今年の6月ぐらいに初めた自分よりも,JavaScript も Node.js もフロントエンドも書ける人達に囲まれての発表だったので,正直どうしようかなと思ったけれど,自分が Electron で Web フロントエンド&Node.js に入門することになった経緯と,その過程で得た知見を共有する感じにしようとなってこんな感じの資料になった.
色々詰め込もうとして25分に対して42ページになったものの,一応発表練習しておいたのもあって,時間通り喋れてよかった.ただ,スライド内で紹介した僕が書いたアプリのコードはまだかなり汚くて設計もいけてないと思うので,awesome-electron にある他のアプリのほうが参考になるかもしれない…
本当は <webview>
についても少し載せようとしたけど,時間がなかったのでここで追記します.
<webview>
はレンダラプロセス上でさらに別のプロセスを生成し別権限で WebContents を描画できるタグで,レンダリング元とのやり取りは ipc 経由になる.でも,外部ページを表示する場合は node integration を有効にすると jQuery がロードできなくなったり,セキュリティ的にも完全にアウトなので node integration は無効にする.それだと require('ipc')
できなくなるので困る!となるけれど,実は <webview>
には preload
というプロパティ があって,それが解決してくれる.node integration を無効にしていても preload
で読み込まれたスクリプト内では node の API が使えるらしい.よって大雑把には下記のような順でスクリプトが読まれる.
よって,レンダリング元と ipc で通信しつつ node integration をコンテンツ内では無効にできる.
また,懇親会でいくつか質問をいただいたので,それについてもここにメモしておきます.
- メニューは OS によって微妙に異なるが,その差異はどうするのか
例えば OS X ではメニューアイテムの一番最初にアプリ名の項目があって,Windows や Linux には無い.Electron にはこれをサポートする仕組みは特に無いので,テンプレートを出し分けるしかない.
import {buildFromTemplate, setApplicationMenu} from 'menu'; const osx_template = {...}; const other_template = {...}; const template = process.platform === 'darwin' ? osx_template : other_template; const menu = buildFromTemplate(template); setApplicationMenu(menu);
アプリのアイコンはどうしてるのか
- Shiba → いらすとやさんのを使わせてもらった.ここの絵は中毒性があっておすすめです.
- Trendy → Yosemite 風アイコンジェネレータ を使わせてもらった.困ったらとりあえずこれで生成しておけば問題なさそう.
- Tilectron → Chrome のロゴを参考にしつつ Inkscape でポチポチ描いた
他の発表の感想
The State of JavaScript
僕の知らない JavaScript がいっぱい出てきた.個人的には WebAssembly が気になっていて,LLVM IR を吐くコンパイラフロントエンドがブラウザで動くコードを吐けるようになったりするのかなと思うと楽しみ.
技術文書をソフトウェア開発する話
文書を正しくソフトウェアエンジニアリングしていてすごい.コード片とかは裏ではテストできる形でフルで持っておいて,書籍には一部だけ載せるみたいなことができると良いなぁと思った.用語の統一とか口調の統一とかは複数人で書くとかなりブレが出てしまうみたいなので,ツール使って CI 回すのはかなり効果ありそう.
"npm":">=3"
npm3 は node_modules
以下がフラットになるのとインストール状況が表示されるぐらいしか知らなかったので色々参考になった.shrinkwrap
も知らなかった… npm のロードマップ も面白そうなことがいっぱい書いてあって,特にフロントエンドモジュールが気になる…
Electroknit! - Pixel to sweater with Node.js
編み物…?と思ったけれど,正しく Node.js で編み物していた. 画像処理とか問題を表層的にでなくちゃんと中身まで理解して進めていて,(本人は色々謙遜されていたけれど)正しくエンジニアリングして進めていてすごい.
ESDoc - ES6時代のドキュメンテーションツール -
とりあえずまずは手元の小さいモジュールにドキュメントつけてみるぞ!と思ったら,今まで書いた node モジュールは全部 TypeScript 製だった… TypeScript の型定義ファイル読み込んで反映出来たり,assertion の条件を反映出来たりするとコードで書かれた仕様がそのままドキュメントに落ちてきて良さそうだなと思った.
フロントエンドに秩序を取り戻す方法 〜はてなブログ編集画面をリニューアルするためにやったこと〜
すごくパワフルな発表だった.継続的に改善し続けるしかなくて体力が要ると思うし,ひたすらやっていく💪しかない.あと,フロントエンド界隈はやっぱり捨てられるかどうかが大きいんだなぁと思って聞いていた.
懇親会
知り合いと話したり,知り合いの知り合いの方と話したり,発表を聞いていただいた方に声をかけてもらって話したりしていた.特にこの界隈に顔なじみの人もあまりいないので,声をかけてもらえるのはありがたかった.:sushi: :pizza: :beer: 最高だった.
全体の感想
Electron や Node.js を触り始めてぼちぼち 東京 Node 学園とか React meetup とかに出かけて行って感じていたけれど,やっぱり JavaScript 界隈は熱量がすごいなと思った.勉強会はどこも大盛況だし,スポンサーも IBM とか Microsoft はじめでかいところとか有名なところがついてる.その分流れてくる情報量もすごく多いので,ガンガン吸収していきたい.
運営の方々,素晴らしいイベントをありがとうございました.
あと,「ウェブの人になるんですか?」と最近たまに聞かれますが,今までどおり C++ も Vim もやっていく気持ちです.
Crystal 言語で CLI ツールを書いてみる
Crystal はつくりかけの言語です.この記事は 0.8.0 を元に書きましたが,今後かなり変更されることが予想されます
Crystal は Ruby に強くインスパイアされたコンパイル言語です.結構前から気になっていて,Crystal のコンパイラが Ruby で書かれていた頃(今はセルフホストしています)から見ていて最近はぼちぼちコントリビュートしたりしてます.インスパイアされているというだけで,色々 Ruby と違うところもあります(例えばいま話題の文字列は Crystal では全て immutable です)
僕は Ruby が好きなので,ちょっとしたツールや書き捨てのスクリプトは特に理由が無い場合は Ruby で書いています.それもあって Crystal でちょっとしたツールを書くのは次のような利点があります.
- Ruby と同じように楽に(そして雑に)書ける
- Ruby とは違い実行速度が速い(まだあまり実感してないですが)
- Ruby とは違いコンパイル時に型チェックが入る(各式が取り得る型の union type で型付けします)
- Ruby とは違いバイナリファイル1つになる(共有ライブラリなどを使わなければ)
コマンドラインツールは Crisp (Crystal で書かれた Lisp 処理系)や crdoc (コマンドラインから Crystal のドキュメントを検索&開くツール)をつくってみたので,今回は自分なりのやり方の手順をまとめてみます.
といっても大体 Ruby と同じですが…
準備
Go 言語における go
コマンドのように,Crystal にも crystal
コマンドがあります. 公式のインストールページ に従ってインストールします.残念ながらまだ Windows では利用できません(対応はぼちぼち進んでいるようです).
次に開発環境を入れます.お好みに合わせて導入してください.(もし需要があれば開発ツール周りもブログに書きたい.)
- Atom: language-crystal-actual
- Sublime Text: sublime-crystal
- Vim: vim-crystal
- Emacs: emacs-crystal-mode
リポジトリ生成
試しにファイルの中身を表示するだけのコマンド mycat
をつくってみます.
まずは crystal init
コマンドで初期のリポジトリを生成します.
$ crystal init app mycat
次のようなディレクトリ構成が自動でつくられます.
--- mycat |--- .gitignore |--- LICENSE |--- README.md |--- .travis.yml |--- shard.yml |--- src | |--- mycat.cr | |--- mycat/version.cr | |--- spec | |--- spec_helper.cr | |--- mycat_spec.cr | |--- .gitignore
僕はコマンドラインツールを書く時はなるべくライブラリとしても使えるようにつくるので,src/mycat.cr
は API だけ書いて,bin/mycat.cr
にコマンド実行処理を書きます.そのほうがテストもしやすいです.
最終的にはこんな感じです.
--- mycat |--- .gitignore |--- LICENSE |--- README.md |--- .travis.yml |--- shard.yml |--- bin | |--- mycat.cr | |--- src | |--- mycat.cr | |--- mycat/version.cr | |--- spec | |--- spec_helper.cr | |--- mycat_spec.cr | |--- .gitignore
ドキュメントを書く
最初の時点で脳内で決まっている範囲で簡単なドキュメント(intro,コマンドの引数など)を README.md
に書きます.生成された README.md
はスニペットになっているので,GitHub のユーザ名などを自分で埋めます.
また,(なぜか)LICENSE が MIT で決め打ちになっているので,必要があれば修正します.
shard.yml
を書く
npm の package.json
と同じように,パッケージ情報を shard.yml
に書きます.これは半標準(いずれ本体に取り込まれる?)のパッケージマネージャ shards を使う前提で生成されています.
name: mycat version: 0.0.1 authors: - 名前 <mail@address> license: MIT
依存するパッケージが出てきた時はここに書きます.
スペックを書く
まずは mycat
ディレクトリでおもむろに crystal spec
と実行してみます.
次のように失敗すると思います.
F Failures: 1) Mycat works Failure/Error: false.should eq(true) expected: true got: false # ./spec/mycat_spec.cr:7 Finished in 0.55 milliseconds 1 examples, 1 failures, 0 errors, 0 pending Failed examples: crystal spec ./spec/mycat_spec.cr:6 # Mycat works
それも当然です.spec/mycat_spec.cr
は次のようになっています.
require "./spec_helper" describe Mycat do # TODO: Write tests it "works" do false.should eq(true) end end
ここで実装を考えながら必要なメソッドのテストを書きます.ファイル名を受け取って IO に出力に出すメソッド meow
を持ったクラス Cat
をつくることにするとこんな感じでしょうか.test.txt
は spec ファイルと同じディレクトリに適当につくります.__DIR__
はそのファイルの親ディレクトリを表す大域定数(みたいなもの)です.
require "./spec_helper" describe Mycat do describe Mycat::Cat do describe "meow" do it "writes the content of file to specified IO" do io = StringIO.new c = Mycat::Cat.new io c.meow "#{__DIR__}/test.txt" io.to_s.should eq("foo\nbar\nbaz\n") end end end end
まだ実装が無いのでもちろんこれも失敗します.
実装を書く
require "./mycat/*" module Mycat # TODO Put your code here end
最初はこうなっていると思います.
require "./mycat/*" module Mycat class Cat def initialize(@mouse) end def meow(file_name) File.open file_name do |f| @mouse << f.read end end end end
できました.受け取った IO にファイルの中身を吐くだけなので簡単ですね.コンストラクタの引数のように,直接インスタンス変数名を指定してその変数に代入することができます.(コンストラクタ以外のメソッドでも使えます)
試しにビルドしてみましょう.crystal build
コマンドを使います.
$ crystal build src/mycat.cr
問題が無ければ,先ほどと同じ方法で spec を実行します.今度はテストが通ると思います.
コマンド実行処理をつくる
bin/mycat.cr
を実装します.
require "../src/mycat" require "option_parser" begin OptionParser.parse! do |parser| parser.banner = "Usage: mycat file_name" parser.on("-v", "--version", "Show version") { puts Mycat::VERSION; exit 0 } parser.on("-h", "--help", "Show this help") { puts parser; exit 0 } end cat = Mycat::Cat.new STDOUT ARGV.first?.try do |arg| cat.meow arg end rescue e STDERR.puts e exit 1 end
オプションの実装は Ruby でもおなじみの OptionParser
が使えます.今回はバージョンとヘルプだけをパースしています.
Enumerable#first?
はリストの最初の要素があればそれを返し,無ければ nil
を返します.Object#try
は Ruby ではお馴染みの,nil
で無い値で実行すればブロック部分が実行されるメソッドです.例えばここで間違えて cat.meow ARGV.first?
など書くとコンパイラエラーになってくれます.(コンパイラは Cat#meow(f: String)
に推論したのに String?
な値を渡しているため)
試しに実行してみましょう.crystal
コマンドはデフォルトでファイルを直接実行する(実際はコンパイルして一時ファイルを生成しそれを実行する)ので,下記のようにします.crystal
コマンドで実行されるプログラム側に引数を与えるには --
オプションの後に置く必要があります.
$ crystal ./bin/mycat.cr -- README.md $ crystal ./bin/mycat.cr -- -v $ crystal ./bin/mycat.cr -- --help
正しく動いていることが確認できたらリリースビルドで最適化されたバイナリを作成します.
$ crystal build --release ./bin/mycat.cr
カレントディレクトに mycat
というバイナリファイルができていると思うので,それをパスの通ったところに置くなりします.
Travis での CI
デフォルトで .travis.yml
が生成されますが,中身はほぼ空っぽです.
次のようにテストスクリプトを書きます.
Crystal の CI 環境は Crystal コミュニティによって提供されているので,こちらで頑張って用意する必要はありません.
language: crystal script: - crystal spec - crystal build ./bin/mycat.cr
まとめ
プログラミング言語 Crystal でコマンドラインツールを書く自分なりの手順をまとめました.まだ開発途中の言語なのでこれを使ってでかいツールを…とはなかなかいかないかもしれませんが,僕みたいに Ruby 好きな人は書いてみるとハードルも低く新鮮で楽しいかもしれません.