警告
この記事は個人用の覚書なんだってば!目的
最近ちょっとしたスクリプトを書くことが多い。大規模なもんじゃなくて、特定の操作をcrontabで定期的に実行するスクリプトとか、モジュールとかちょっとしたクライアントスクリプトとか。何も考えずにべたっと一枚に書いてもいいんだけど、ちょっと規模が大きくなるとわかりやすく書きたくなってくる。
なので既存のよく使われているスクリプトはどのような風に実行しているか調査してみた。
chef-knife
require "chef/application/knife"
Chef::Application::Knife.new.run
ここが実際たたかれるポイント
def run
Mixlib::Log::Formatter.show_time = false
validate_and_parse_options
quiet_traps
Chef::Knife.run(ARGV, options)
exit 0
ここではconfファイルやら引数で渡されたoptionをパースして実際のknifeの処理をしている部分に渡している
def self.run(args, options = {})
subcommand_class = subcommand_class_from(args)
subcommand_class.options = options.merge!(subcommand_class.options)
subcommand_class.load_deps
instance = subcommand_class.new(args)
instance.configure_chef
instance.run_with_pretty_exceptions
end
argsはコマンドライン引数から渡されているのでknife node list
みたいな引数が渡され、該当するコマンドのクラスのインスタンスが作成され、run_with_retty_exceptions
が呼ばれているような感じがする。
サブコマンドはすべてChef::Knife
クラスを継承しているよう(https://github.com/chef/chef/blob/master/lib/chef/knife/client_list.rb)
run_with_pretty_exceptions
でさらに run
が呼ばれており
このrun
関数の中身は各サブコマンドで定義する感じ
def run_with_pretty_exceptions(raise_exception = false)
Chef::LocalMode.with_server_connectivity do
run
end
end
実際のsub_commandは
def run
output(format_list_for_display(Chef::ApiClientV1.list))
end
こんな感じで各コマンド毎の処理がわかりやすく記述されている。
command パターン
前置きが長くて何を書きたかったか忘れそうなんだけど、knifeはcommandベースのスクリプトなんで実行をrunに集約して各サブコマンドごとの処理をサブコマンド用のクラスのrun
関数に集約できるcommnadパターンぽい書き方はわかりやすくて良いかもしれない。
最小のサンプルプログラム
何らかしらのクライアントプログラムを書くときに参考にできそうな気がするのでコマンドベースのクライアントコードを書くときは多分下のようなファイル構造になるのかな?
例えば、special <subcommand>
みたいなプログラムを書きたいとき
special
├── lib
│ ├── application.rb
│ ├── command
│ │ └── ls.rb
│ └── command.rb
└── special.rb
で中身
#!/usr/bin/ruby
require_relative 'lib/application'
Application.new.run
require_relative 'command'
class Application
def initialize
end
def run
#ここらへんで渡すoptsとかを取得したりargvのvalidateとかするといいかも
Command.run(ARGV[0])
end
end
require_relative 'command/ls'
class Command
def self.run(sub_command, opts ={})
instance = nil
if sub_command == 'ls'
instance = Ls.new
else
puts sub_command + ' not found'
exit 1
end
instance.run
end
def initialize()
end
end
require_relative '../command'
class Command
class Ls < Command
def run()
puts `ls`
end
end
end