ちょっとしたCLIツールを自作しようと思い色々と調べてみたところ、RubyのThorというgemを使用することで非常に簡単にCLIツールを作成できるようだったので試してみました。
本記事ではRubyでThorを使用してCLIツールを作成し、コマンドとして実行するまでの手順についてざっくりとまとめていきます。
開発環境
- OS: macOS Sierra 10.12.6
- Ruby: 2.5.1(rbenvでインストール)
手順
Step 1. gemパッケージの雛形を作成
$ gem install bundler
でbundlerをインストールした後(インストール済みであれば省略)、$ bundle gem {gemパッケージ名} -b
でgemパッケージの雛形を作成します。
以下の例では、str_convert_utils
という名称でgemを作成していきます。
# bundlerをインストール
$ gem install bundler
# gemパッケージの雛形を作成
$ bundle gem str_convert_utils -b
Creating gem 'str_convert_utils'...
create str_convert_utils/Gemfile
create str_convert_utils/lib/str_convert_utils.rb
create str_convert_utils/lib/str_convert_utils/version.rb
create str_convert_utils/str_convert_utils.gemspec
create str_convert_utils/Rakefile
create str_convert_utils/README.md
create str_convert_utils/bin/console
create str_convert_utils/bin/setup
create str_convert_utils/.gitignore
create str_convert_utils/exe/str_convert_utils
Initializing git repo in /PATH/TO/str_convert_utils
Step 2. gemspecファイルを修正
{gemパッケージ名}.gemspec
を修正します。
spec.summary
とspec.description
にTODO
またはFIXME
の文言を含んでいる場合や、spec.homepage
がURIとして不正な形式である場合、後ほど$ bundle install
を実行する際にエラーとなるので修正しておきます。
lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "str_convert_utils/version"
Gem::Specification.new do |spec|
spec.name = "str_convert_utils"
spec.version = StrConvertUtils::VERSION
...
- spec.summary = %q{TODO: Write a short summary, because RubyGems requires one.} # この行を修正
- spec.description = %q{TODO: Write a longer description or delete this line.} # この行を修正
- spec.homepage = "TODO: Put your gem's website or public repo URL here." # この行を修正
+ spec.summary = "string convert utilities"
+ spec.description = "string convert utilities (e.g. snake, camelize, etc.)"
+ spec.homepage = "https://github.com/akisame338/str_convert_utils"
...
spec.add_development_dependency "bundler", "~> 1.16"
spec.add_development_dependency "rake", "~> 10.0"
+
+ spec.add_dependency "thor" # この行を追加
end
Step 3. CLIクラスを作成
続いて、lib/{gemパッケージ名}/cli.rb
を新規作成します。
Thor
を継承したクラスでpublicメソッドを定義することで、当該メソッドをコマンドとして扱うことができます。
メソッド上部のdesc
の部分については、desc "{usage}", "{description}"
と定義することで、ヘルプにて使い方({usage}
部分)と説明({description}
部分)を表示できるようになります。
以下の例では、こちらの記事を参考にしてsnake_caseをCamelCaseに変換して出力するcamelize
コマンドとCamelCaseをsnake_caseに変換して出力するsnake
コマンドを定義しています。
require "str_convert_utils"
require "thor"
module StrConvertUtils
class CLI < Thor
desc "camelize {snake_case_string}", "convert {snake_case_string} to {camelCaseString}"
def camelize(str)
puts str.split("_").map{|w| w[0] = w[0].upcase; w}.join
end
desc "snake {CamelCaseString}", "convert {CamelCaseString} to {snake_case_string}"
def snake(str)
puts str
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
.tr("-", "_")
.downcase
end
end
end
Step 4. 実行部分の実装
lib/{gemプロジェクト名}.rb
を修正して、Step. 3で作成したlib/{gemパッケージ名}/cli.rb
を読み込むようにします。
require "str_convert_utils/version"
+require "str_convert_utils/cli" # この行を追加
module StrConvertUtils
# Your code goes here...
また、exe/{gemプロジェクト名}
(実行ファイル)を修正して、{gemパッケージのモジュール名}::{CLIクラス名}.start
を実行するようにします。
この実行ファイルはbin/
とexe/
のどちらに置くべきなのか?と少し悩みましたが、こちらの記事によると
rubygems が bin を見てることはないし、bin は setup や console といった開発の際に使うファイルを置きたい。あなたが作った executables なコマンドは exe で面倒見ようよ。って感じ。
とのことなので、exe/
以下に置いておけばよさそうです。(gemspecファイルの初期設定もspec.bindir = "exe"
となっているので)
#!/usr/bin/env ruby
require "str_convert_utils"
+
+StrConvertUtils::CLI.start # この行を追加
Step 5. コマンド実行
$ bundle install
を実行してThor
等のインストールが完了したら、あとは$ bundle exec exe/{gemパッケージ名} {コマンド名}
でコマンドが実行できるようになります!
# Thor等のgemをインストール
$ bundle install
# コマンド一覧
$ bundle exec exe/str_convert_utils
Commands:
str_convert_utils camelize {snake_case_string} # convert {snake_case_string} to {CamelCaseString}
str_convert_utils help [COMMAND] # Describe available commands or one specific command
str_convert_utils snake {CamelCaseString} # convert {CamelCaseString} to {snake_case_string}
# camelizeコマンド実行
$ bundle exec exe/str_convert_utils camelize snake_case_string
SnakeCaseString
# snakeコマンド実行
$ bundle exec exe/str_convert_utils snake CamelCaseString
camel_case_string
Step 6. より便利にコマンド実行できるようにする
Step. 5までの手順で一応CLIツールとして使えるようになりましたが、このままだと毎回gemパッケージのディレクトリに移動して$ bundle exec ...
としなければコマンドが実行できないため少し不便です。そこで、作成したgemパッケージをインストールして$ {gemパッケージ名} {コマンド名}
としてコマンド実行できるようにします。
作成したgemをインストールできるようにするにはRubyGems.orgにアップロードして公開するのが一般的らしいですが、今回はこちらの記事を参考にして、RubyGems.orgを使用せずに作成したgemパッケージをインストールしてみます。
specific_installというgemを使用するとgithubに公開しているgemをグローバルな場所(rbenvを使用している場合は~/.rbenv/shims
以下)にインストールできるそうなので、こちらを使用して作成したgemパッケージをインストールします。
これによって、どのディレクトリにいても今回作成したgemパッケージのコマンドを実行できるようになります!
# specific_installをインストール
$ gem install specific_install
# 自作gemパッケージをグローバルな場所にインストール
$ gem specific_install -l https://github.com/akisame338/str_convert_utils.git
# camelizeコマンド実行
$ str_convert_utils camelize snake_case_string
SnakeCaseString
# snakeコマンド実行
$ str_convert_utils snake CamelCaseString
camel_case_string