Ruby で作る、簡単 CLI ツールのススメ

  • 366
    Like
  • 0
    Comment
More than 1 year has passed since last update.

Ruby 開発環境 AdventCalendar 10 日目です。前日は、 aereal さんでした。

さて、皆さんコマンド打ちまくってますか?僕は Ctrl+R で履歴から引っぱり出さないとタイポで撃沈します。

そんなこんなで皆さんいろんな CLI ツールをご利用中だと思います。 vim とか emacs とか、 rails g とか、 guard とか。実に便利なものたちですが、現実の開発現場に即した、素晴らしいニッチなツールというのは、得てしてそんなにないものです。

「こんなに重厚でなくていい」

「もうちょっとざっくりとした……なんというか痒いところに手が届くような……」

などなど、いろいろあると思います。例えば「チームで開発しているのだが、今行ったコミットのレビューを依頼するメールを書くのが面倒なので社内 Twitter 的なものに書こうかと思うのだがそれもめんどくさいのでコミット Diff を見れる URL をぺたっと社内 Twitter に貼ってくれるだけ」のツールとか、欲しくなるもんです。無いです。

ということで、実際に弊社、株式会社 Qterasで利用されている、社内製 CLI ツールの配布とその開発についてお話します。

具体的な開発

実装編

さて、実際に作って皆で使ってスペシャルハッピーになりたいのですが、Ruby でCLI ツールを作るのはすごく簡単です。

bundlerの偉大さ
$ bundle gem special_happy -b
      create  special_happy/Gemfile
      create  special_happy/Rakefile
      create  special_happy/LICENSE
      create  special_happy/README.md
      create  special_happy/.gitignore
      create  special_happy/special_happy.gemspec
      create  special_happy/lib/special_happy.rb
      create  special_happy/lib/special_happy/version.rb
      create  special_happy/bin/special_happy
Initializating git repo in ~/tmp/special_happy

ひな形が一撃で作れました。ということで実際に実装していくわけですが、とりえあえず Thor gem を導入します。あと、このリポジトリを必ず Git で管理してください。そして社内のリポジトリサーバーにアップしてください。これは配布編で必要な手順です。

Thor は CLI ツールフレームワークで、自分で optparse なんかを使うより断然簡単に CLI ツールを実装することが出来ます。とりあえずやってみましょう。

まずは special_happy.gemspec に Thor を追加します。

special_happy.gemspec
# -*- encoding: utf-8 -*-
require File.expand_path('../lib/special_happy/version', __FILE__)

Gem::Specification.new do |gem|
  gem.authors       = ["Sho Kusano"]
  gem.email         = ["rosylilly@aduca.org"]
  gem.description   = %q{TODO: Write a gem description}
  gem.summary       = %q{TODO: Write a gem summary}
  gem.homepage      = ""

  gem.files         = `git ls-files`.split($\)
  gem.executables   = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
  gem.test_files    = gem.files.grep(%r{^(test|spec|features)/})
  gem.name          = "special_happy"
  gem.require_paths = ["lib"]
  gem.version       = SpecialHappy::VERSION

  gem.add_dependency 'thor' # ここ追加
end

お次は肝心の起動部分です。さらっと。

bin/special_happy
#!/usr/bin/env ruby

require 'special_happy'

SpecialHappy::CLI.start # ここ追加

そしたらこの SpecialHappy::CLI が読み込まれるように。

lib/special_happy.rb
require "special_happy/version"
require "special_happy/cli" # ここ追加

module SpecialHappy
  # Your code goes here...
end

後は新規でファイルを作って中身をさっくり作ります。とりあえず機能は文字列を引数で与えると赤色で出力してくれるというものにしましょう。便利そうです。

lib/special_happy/cli.rb
require 'special_happy'
require 'thor'

module SpecialHappy
  class CLI < Thor
    desc "red WORD", "red words print." # コマンドの使用例と、概要
    def red(word) # コマンドはメソッドとして定義する
      say(word, :red)
    end
  end
end

これで準備は整いました。さっそく試しに使ってみましょう。

試運転
$ bundle install # Thor なんかをインストールするために
$ bundle exec bin/special_happy red 赤い!
赤い!

カラーの端末なら、"赤い!"の文字が赤く表示されるはずです。少々あっけなかったですか?これでいいのです。あとは Ruby でコマンドを好きなように実装することが出来ます。詳しい Thor の解説などは、本家 wiki を見るとよいでしょう。

ここまで作業したリポジトリは僕の Github にあがっています。

配布編

さて、さっくり作れたコマンドラインツールを別のプロジェクトからさっくり使いましょう。

もちろん gem として作っているので、思い切りよく Rubygems に公開してしまうのも OK ですが、作っているツールはニッチですから、無駄に Rubygems の名前空間を消費しそうなら、 pebbles 配下にしておくなどの配慮をすると喜ばれるでしょう。

さて、後はチームメンバーへの配布のみとなる訳ですが、ここで gem 化すると Rubygems に上げないとアップデートできないなどの問題が発生して面倒です。そこで、 Gemfile を利用します。

プロジェクトルートディレクトリに以下のような Gemfile を作ります。

Gemfile
gem "special_happy", :git => "git://github.com/rosylilly/git-advent-calendar-sample-special-happy.git" # URI は適宜書き換えてください

こうすることで、 Rubygems を経由することなく、閉じた環境で gem のインストールを行うことが出来ます。

これなら社内用の秘匿ツール(表だしするとマズイツール)なども開発、運用が容易です。利用者は以下のコマンドを実行するだけです。

簡単!
$ bundle install
$ bundle update # たまにやろう

ツールが依存する gem も同時にインストールされるので、環境ごとに頑張る必要もほぼありません(Windows 環境だと gzip とか ssl 周りで苦労しますが、乗り越えればこっちのものです)。

するとあとは

楽ちん!
$ bundle exec special_happy red 赤!
赤!

という具合に利用することが出来ます。

実運用

こんな具合に簡単に使うことが出来ました。実際に Qteras では

  • JS の require を解決して結合する(Sprockets の流用)
  • JS-Beautify をかけて記法を綺麗にする
  • Google Closure Compiler を使って圧縮、型チェックを行う

というひと通りの動作を一気に行なってくれるツールが開発・運用されています。これを導入する前は個別に Closure Compiler などをインストールしていたのですが、そこは Gemfile と git submodule の妙技により、新規メンバーは bundle install だけで即開発に入れるようになりました。

また、「もしも Rubygems が突如撃沈しており、まったく無反応である」と言った場合にも、依存 gem がインストール出来るように

何からなにまで世話になる
$ bundle package

のコマンドによって、依存 gem の .gem ファイルをローカルにバックアップしておくのもよいです。バックアップしてある .gem は

足を向けて寝れない
$ bundle install --local

でインストール可能です。

小さなしあわせ生活

とまあこのようにして社内用の小さなツールを作る、運用する、という点においては今のところ僕は Ruby より便利な環境を知りません。 C++ なんかで実装したほうがバイナリ配布だけで済むとかあるのでしょうが、それをここまで手早く柔軟に作れるかというと、また別だと思います。

社内ツールというのは往々にして利益を望みにくく、またその効果も実感として得難いものです。実際に利益を受けて便利になった人にとってすら、便利さというのはそのひととき味わえば、数刻後には普通のことになってしまって、小さなしあわせです。

僕はどちらかと言えば派手な仕事よりこういう「隣にいる人」の小さなしあわせを眺める方が好きなので、ちょこちょこ作っては同僚に押し付けたりしています。あまりよくないことですが、しかし便利なのも事実なのです。老後は「エンジニアリングサポート技術基盤部」とかそういう部署に腰を落ち着けたいものです。

そんなこんなで、もし興味が湧いたのでしたら、ぜひあなたもチャレンジしてみてはいかがでしょうか?ディスプレイの向こうのユーザーさんが喜ぶのも嬉しいですが、隣の人の「いやー便利だな」という小さなつぶやきも、負けず劣らずの嬉しさですよ。