Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

Ruby | CLI | Be Easy to Use | Thorを利用した使いやすいCommand suitインターフェース

概要

書籍 Build Awesome Command-Line Applications in Ruby2

Be Easy to Use

詳細

「OptionParserを利用した使いやすいCLIインターフェース(http://d.hatena.ne.jp/tbpg/20140526/1401109608)」で紹介したOptionParserは
シンプルなCLIツールを作るには十分ですが、サブコマンドを持つようなCommand suitを作るには不十分です。

そこで、thor gem を利用してサブコマンドを持つCLIアプリケーションを作成します。
※書籍では、著者のプロダクトであるGLIを用いた例を紹介していました。

サンプル仕様

※書籍に載っていたものとは全く別のものです。自分独自の仕様で考えました。

Personの関連情報を出力するコマンド群を作成する。
Personの情報は persons.txt にcsv形式で保存されている。

firstname lastname age
kazuo tanaka 34
ken sato 54
ichiro suzuki 45

Personの情報を取得し、
姓を出力する
名を出力する
姓名を出力する
年齢を出力する
名を元に persons.txt からデータを検索し、結果を表示する
コマンドを作成します。

Command Long-From Option 内容
fir firstname 名を出力する
l lastname 姓を出力する
fu fullname 姓名を出力する
a age 年齢を出力する
fin find_by_firstname 姓で検索する

サンプルコード

require 'csv'
require 'thor'
require 'pp'

class PersonViewer < Thor
  package_name "person_viewer"
  VERSION = "0.0.1"
  class_option :help, type: :boolean, aliases: '-h', desc: 'help message.'
  class_option :version, type: :boolean, desc: 'version'
  PERSONALS = './personals.csv'

  desc "view firstname", "view firstname"
  option :upcase, type: :boolean, aliases: '-u'
  option :downcase, type: :boolean, aliases: '-d'
  def firstname
    table = CSV.table(PERSONALS)
    table.each do |row|
      row[:firstname] = row[:firstname].upcase if options[:upcase]
      row[:firstname] = row[:firstname].downcase if options[:downcase]
      puts row[:firstname]
    end
  end

  desc "find by firstname [FIRSTNAME]", "find by firstname [FIRSTNAME]"
  def find_by_firstname(text = '')
    target = CSV.table(PERSONALS).select { |row|row[:firstname] =~ /#{text}/ }
    pp target
  end

  desc "view lastname", "view lastname"
  option :upcase, type: :boolean, aliases: '-u'
  option :downcase, type: :boolean, aliases: '-d'
  def lastname
    table = CSV.table(PERSONALS)
    table.each do |row|
      row[:lastname] = row[:lastname].upcase if options[:upcase]
      row[:lastname] = row[:lastname].downcase if options[:downcase]
      puts row[:lastname]
    end
  end

  desc "view fullname", "view fullname"
  option :upcase, type: :boolean, aliases: '-u'
  option :downcase, type: :boolean, aliases: '-d'
  def fullname
    table = CSV.table(PERSONALS)
    table.each do |row|
      tmp_fullname = "#{row[:firstname]} #{row[:lastname]}"
      tmp_fullname = tmp_fullname.upcase if options[:upcase]
      tmp_fullname = tmp_fullname.downcase if options[:downcase]
      puts tmp_fullname
    end
  end

  desc "view age", "view age"
  option :upcase, type: :boolean, aliases: '-u'
  option :downcase, type: :boolean, aliases: '-d'
  def age
    table = CSV.table(PERSONALS)
    table.each { |row|puts row[:age] }
  end

  desc 'version', 'version'
  def version
    puts VERSION
  end
end

PersonViewer.start(ARGV)

サンプル出力

ヘルプ,バージョンの表示

$ ruby personal.rb
person_viewer commands:
  personal.rb find by firstname [FIRSTNAME]  # find by firstname [FIRSTNAME]
  personal.rb help [COMMAND]                 # Describe available commands or...
  personal.rb version                        # version
  personal.rb view age                       # view age
  personal.rb view firstname                 # view firstname
  personal.rb view fullname                  # view fullname
  personal.rb view lastname                  # view lastname

Options:
  -h, [--help], [--no-help]        # help message.
      [--version], [--no-version]  # version

$ ruby personal.rb v
0.0.1

姓、名、姓名、年齢の表示

$ ruby personal.rb fir
kazuo
ken
ichiro
$ ruby personal.rb l
tanaka
sato
suzuki
$ ruby personal.rb fu
kazuo tanaka
ken sato
ichiro suzuki
$ ruby personal.rb a
34
54
45

姓の表示にオプションを併用(大文字化、小文字化)

$ ruby personal.rb fu -u
KAZUO TANAKA
KEN SATO
ICHIRO SUZUKI

サブコマンド+引数を利用した検索

$ ruby personal.rb fin ken
[#<CSV::Row firstname:"ken" lastname:"sato" age:54>]
$ ruby personal.rb fin k
[#<CSV::Row firstname:"kazuo" lastname:"tanaka" age:34>,
 #<CSV::Row firstname:"ken" lastname:"sato" age:54>]