2020年9月7日月曜日

Ruby thor で CLI ツールの開発に入門してみる

概要

Thor は Ruby で CLI ツールを作成することができるライブラリです
今回は簡単な使い方を紹介します

環境

  • macOS 10.15.6
  • Ruby 2.7.1p83
    • thor 1.0.1

インストール

  • bundle init
  • vim Gemfile
gem "thor"
  • bundle config path vendor
  • bundle install

Hello thor

とりあえず動作させてみましょう
Thor クラスを継承したクラス内でメソッドを定義するだけでそれを CLI から呼び出すことができます
また help コマンド時の説明も desc などの DSL を使うことで定義できます

require "thor"

class MyCLI < Thor
  desc "hello NAME", "say hello to NAME"
  def hello(name)
    puts "Hello #{name}"
  end
end

MyCLI.start(ARGV)

これで help を表示すると以下のように表示されます

  • bundle exec ruby app.rb help
Commands:
  app.rb hello NAME                 # say hello to NAME
  app.rb help [COMMAND]             # Describe available commands or one specific command

hello メソッドを実行してみるとちゃんと実行されているのが確認できると思います

  • bundle exec ruby app.rb hello hawksnowlog

=> Hello hawksnowlog

可変長引数を受け取る

先程の例だと引数を 1 つ以上指定するとエラーになります
可変長にすることで引数をいくつでも受け取ることができるようになります

require "thor"

class MyCLI < Thor
  desc "team NAME member1 member2", "show team NAME and members"
  def team(name, *members)
    puts "Team #{name}"
    puts "Members #{members.join(',')}"
  end
end

MyCLI.start(ARGV)

こんな感じで実行することができます

  • ruby app.rb team number 1 2 3 4
Team number
Members 1,2,3,4`

オプションを指定する

--hoge のような感じでオプションを指定することができます

require "thor"

class MyCLI < Thor
  desc "print NAME", "print NAME and optional age"
  option :age
  def print(name)
    puts "Name #{name}"
    puts "Age #{options[:age]}"
  end
end

MyCLI.start(ARGV)
  • bundle exec ruby app.rb print hawksnowlog --age 10

クラスオプション

クラス内で共通のオプションを持たせたい場合は class_option を使います
定義したすべてのメソッドでオプションを使うことができます

require "thor"

class MyCLI < Thor
  class_option :verbose, :type => :boolean

  desc "hello NAME", "say hello to NAME"
  def hello(name)
    puts "Hello #{name}"
    puts "Bye!" if options[:verbose]
  end
end

MyCLI.start(ARGV)
  • bundle exec ruby app.rb hello hawk --verbose

Long Description

help commad とすることで詳細を表示することができます
long_desc を使います

require "thor"

class MyCLI < Thor
  desc "hello NAME", "say hello to NAME"
  long_desc <<-LONGDESC
      \u001b[31maaaaaaaaaaaaaaaaaa\u001b[0m

      bbbbbbbbbbbbbbbb  

      > $ bundle exec ruby app.rb hello "hawksnowlog"
  LONGDESC
  def hello(name)
    puts "Hello #{name}"
  end
end

MyCLI.start(ARGV)

ANSI カラーコードも使えました

  • bundle exec ruby app.rb help hello

サブコマンド

git remote addgit remote remove のようにサブコマンドを定義することもできます
例えば name というコマンドに対するサブコマンドを定義したい場合は以下のようにします
subcommand を使って更にサブコマンド用のクラスを定義する感じになります

require "thor"

class Show < Thor
  desc "print", "Print my name"
  def print
    puts "hawksnowlog"
  end
end

class MyCLI < Thor
  desc "name SUBCOMMAND", "Operations for your name"
  subcommand "name", Show
end

MyCLI.start(ARGV)

Thorfile

thor はタスクランナーとして使うこともできます
Thorfile というファイルに同じように実行したいコマンドを定義しておくと rake のようにタスクとして実行することができます

  • vim Thorfile
require "thor"

class Task < Thor
  class << self
    def exit_on_failure?
      true
    end
  end

  desc "hello NAME", "say hello to NAME"
  def hello(name)
    puts "Hello #{name}"
  end
end

exit_on_failure? はおまじないだと思ってください
これがないと Deprecation warning: Thor exit with status 0 on errors. To keep this behavior, you must define exit_on_failure? in Thor::Sandbox::Task という警告が表示されてしまいます
タスクの一覧を確認する場合は thor -T を使います

  • bundle exec thor -T
task
----
thor task:hello NAME  # say hello to NAME

あとはタスクを指定して実行するだけです
こちらの使い方のほうが thor っぽいというか rake っぽくも使えるので良いかなと思います

  • bundle exec thor task:hello hawksnowlog

最後に

Ruby の thor を使って CLI ツールを作成する方法を紹介しました
引数の制御やオプション、サブコマンドなど必要な機能は一通り揃っているかなと思います
よくタスクランナーの使い方が紹介されているイメージですが元は CLI ビルドツールになります

参考サイト

0 件のコメント:

コメントを投稿