BlankSlate

いや、そういう声もあるというだけです

Oyaをリリースしました

先日、マミーが10047歳の誕生日を迎えました。昔はちゃんと「お母さん」って言ってた気がするんですが、15を越えたくらいから恥ずかしさから「母ちゃん」とか「マミー」と呼ぶことが多くなった今日この頃です。お母さん、誕生日おめでとう。

祝辞はここまでとして、OyaというCLIツールをリリースしたので宣伝です。

Oyaとは

簡単に言ってしまえば、ファイルの変更を検知し、任意のコマンドを実行するRubyスクリプトです。こういった動きをするソフトウェアはGuardGruntがありますが、短いながらも設定ファイルを書かないといけないのが億劫(おっくう)でした1

いままでは、

$ while :; do command1; command2; done

などで誤魔化していましたが、ファイルの変更を定期的に監視して、特定のコマンドを実行するシェルスクリプト(@tamanobi、Qiita)を見て、簡単に作れそうなので作っちゃいました2

利用法としては、

  • 書きなぐりのCLIツールのオプション引数のテスト
  • ANSIエスケープシーケンスを駆使したcowfileの編集・確認

なんかで使えると思っています。

実行例

rengeの実行結果を~/textに追記(右ペイン)すると、oyaが変更を検知して追記された行を表示している(左ペイン)のが確認できると思います。

Oyaの裏側

Oya::Watcher

一応、年始くらいからデザインパターンをやってるのでオブザーバ・パターンで書きました。というか、挙動としてはそのまま当てはまりますね。

インスタンス生成時にファイルの変更通知を受け取りたいクラスをハンドラとして登録しておけば、あとはOya::Watcherが各ハンドラのupdateクラスを呼び出します。デザインパターンっていうと固いイメージですが、やってることはインターフェイスの強制というかポリモーフィズムぽいですね。

# Oya::Watcherの一部
loop do
  # ファイルが変更されている場合は
  if target.changed?
    # ハンドラに通知する
    notify_handlers
  end
end
# Oya::Watcherから通知を受け取るハンドラの例: シェル通知
class Oya::Handler::ShellNotifier < Oya::Handler::Base
  def initialize(message)
    @message = message
  end

  # Oya::Watcherから更新通知を受け取った場合は
  def show(params={})
    # `[時間] メッセージ' を表示する
    puts "\n[#{params[:time]}] #{(params[:message] || @message)}"
  end

  alias :update :show
end

通知先を増やしたい場合は、Oya::Handler::Base(定数参照用のベースクラス)を継承したクラスを生成し、updateOya::Watcherからの通知を受け取れるようにすればよく、Oya::Watcher側で改修作業をする必要がありません3

私は光学ドライブを持ってないので実装も確認もできませんが、「ファイルが変更されたらドライブをエジェクトする」ハンドラを追加するPR待ってます。

ライブラリAPIのインターフェイス

Rubyを始めて8ヶ月目、Gemを5個くらいしかリリースしてないぺーぺーなので毎回悩みます。

watch = Oya.watch(target_path) do
  # 更新検知間隔をオプション引数で指定された値に設定
  interval = option[:interval]

  # 通知を受け取るハンドラを追加
  add_handler Oya::Handler::ShellNotifier.new('Target update!')
  add_handler Oya::Handler::DesktopNotifier.new('Target update!')
  add_handler Oya::Handler::Command.new(command_str)
end

個人的には、コンストラクタにブロックを渡すとselfをブロック引数にするような動きの方が好みです。ハンドラ登録が長くなりそうだったんで、instance_evalにしましたが…

外部設定ファイルの読み込み

ここで書いたんですが、optparseライブラリのバグを踏んじゃったので、自前で実装しました。詳しくはリンク先と実装を見てください。

Rakefile

gemspec(gemパッケージマネージャーが読み取るgemの基本情報や依存関係が記述されたファイル)を書く元気もなく依存する外部Gemもないので、インストールはRakefile/usr/local/binへ直接コピーすることにしました。

一応、Ruby標準ライブラリのun(OS・ディストリビューション間の実装差異に関係なくコマンドを実行できる)を使っているので、PREFIXを正しく設定すれば、どこでもインストールできるはずです4

$ # `un' ライブラリ使用例、`mkdir' を実行している
$ ruby -run -e mkdir -- -p /usr/local/lib/oya

課題

テストがない

俺のマシンでは動いてるよ

感想

n_cipher以来、まともなオレオレCLIツールを作ってなかった5ので楽しかったです。rengeをリリースしたのがちょうど1年前くらいで、「テキストファイルをランダム表示するだけのシェルスクリプト」よりは成長した気もします。

  1. CLIで動く同種のソフトウェアとしてnotify-toolsがあるらしい。これは変更検知するAPIのみを提供っぽい?

  2. ねぇ、Polyanessは?

  3. 理想

  4. コマンドライン引数のエンコード変換処理をしてないので、多分そのままでは動かないだろうけど

  5. log_zipperとか仕事では作ったんだけどね

History