LoginSignup
12
10

More than 5 years have passed since last update.

Thor をタスクランナーとして使うために調べたこと

Last updated at Posted at 2016-09-19

はじめに

thor は Ruby で CLI ツールを作るときによく使われる gem で、サブコマンドに対応しているなど、機能が充実しています。

Thor as a Task Runner

最近、thor をタスクランナーとして Rake の代わりに使うこともできる、と知りました。

この記事では、Rake に対するメリットや、thor をタスクランナーとして使うための機能について、調べたことや試したことを載せています。

使い方

下の記事に割とよくまとまっています。

Thorfile

Rakefile に相当するものです。

中身としては、Thor を継承した CLI クラスを記述します。
単純なものだと下のような感じ:

Thorfile
class Foo < Thor
  desc 'foo', 'Say foo'
  def foo
    say 'foo'
  end
end

実行例は以下です。

% thor -T
foo
---
thor foo:foo  # Say foo

% thor foo:foo
foo

#sayThor::Shell::Basic で定義されています。
上の例なら puts でいいのですが、#say だと色を付けたりもできます。
Thor::Shell::Basic の他の機能については、後半でもう少し紹介します。

上のファイルは foo.thor として、カレントディレクトリや lib/tasks/ 以下に置いても動作します。

参考記事

本家の Wiki にいくつか関係する記事があります:

Rake に対する利点

まとめると以下になりそうです:

  • 引数の取り扱いが楽になる。コマンド引数もオプション形式もふつうに使える。
  • 構造化しやすい。ネームスペースを class や module 単位で分割できる。
  • テストコードが書きやすい。

上の記事にもありましたが、Rake のペインポイントとして「タスクに引数を渡したいときにつらい」というのがあるようで、thor ではシンプルに関数引数や、オプションの形で扱えます。

あと、thor であればテストコードが書きやすいとか、構造化しやすいという意見があります。

Rake だと全てのコードがグローバルスコープに置かれるから、名前の競合がつらい、という話も聞いたことがあります。
経験が浅いので、実際どのくらいまずいのか肌感覚ではわかりませんが、タスク用の関数をあちこちに書くようになったら危険なのかな、などと想像しています。
求む知見。

便利な機能

Thor::Actions

コマンド実行やファイル・ディレクトリ操作を提供してくれます。
Thor を継承した class で include して利用します。

よく使いそうだな、と思ったメソッドは #inside#run です。

  • #inside は指定したディレクトリ配下で、ブロックで渡した Ruby コードを実行してくれます。
  • #run はシェルコマンドを実行します。:capture オプションの有無によって、バッククォートまたは system メソッドでコマンドを実行します。

以下に、サンプルコードを示します。

Thorfile
class App < Thor
  include Thor::Actions
  class_option :verbose, aliases: 'v', type: :boolean, default: false

  desc 'work', 'Sample command in workdir'
  def work
    inside 'workdir', verbose: options[:verbose] do
      pwd = run 'pwd', capture: true # `pwd`
      say "PWD: #{pwd}"
      ret = run 'false' # system('false')
      say "RESULT: #{ret}"
    end
  end
end

実行すると、下のようになります。

% thor app:work -v
      inside  workdir
         run    pwd from "./workdir"
  PWD: /home/quiche/my/repos/tutorials/thor/action/workdir
         run    false from "./workdir"
  RESULT: false

その他の使い方については、Thor::Actions の RubyDoc を確認ください。

参考:

Thor::Shell::Basic

端末に対する入出力や、対話操作用の便利な機能を提供してくれます。
便利そうだな、と思ったのは #ask, #yes?, #no?, #say あたり。
#say については上の方でも触れました。

  • #ask は端末にテキストを表示して、ユーザからの入力を受け取ります。
  • #yes?, #no?#ask を wrap して、それぞれ入力が y|yes, n|no なら true, でなければ false となるものです。

サンプルコードを以下に示します:

Thorfile
class App < Thor
  desc 'dialog', 'Sample dialog'
  def dialog name='...'
    say "Hello, #{name}", :bold
    if yes? 'Do you know how useful thor is?', :bold
      say 'Wondeful!', [:magenta, :bold]
    else
      say %(It's a shame.), [:blue, :bold]
    end

    say ''
    answer = ask 'Then, tell me the name of who you fell in love with for the first time:', :bold
    if answer.length.nonzero?
      say "#{answer} ... OK. I remember.", [:magenta, :bold]
    else
      say %(OK. I'll ask you again later), [:blue, :bold]
    end

    say ''
    say 'Bye!', :bold
  end
end

実行すると下のようになります。

% thor app:dialog MyName
Hello, MyName
Do you know how useful thor is? y
Wondeful!

Then, tell me the name of who you fell in love with for the first time: Oh, yes!
Oh, yes! ... OK. I remember.

Bye!

% thor app:dialog
Hello, ...
Do you know how useful thor is? NOOOOO
It's a shame.

Then, tell me the name of who you fell in love with for the first time: 
OK. I'll ask you again later

Bye!

(※本当は色が付いたりします。)

その他詳細については、RubyDoc を参照して下さい。

結びに

試しながら使っているところですが、タスクに柔軟に引数を渡したい場合には使いやすそうです。
成熟度としては Rake や Capistrano ほどではないかもしれませんが、それなりに便利な機能も揃っていると思います。

12
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
10