Carthageコードリーディング::3日目
さて3日目。
前回は「VersionCommand」まわりの実装を見た。 今回は実際にコマンドを実行している周りを見ていく。
エントリポイント(復習)
1日目でも触れたが、エントリポイントであるmain.swift
の以下でhelp
コマンドをデフォルトに実行している。
registry.main(defaultVerb: helpCommand.verb) { error in fputs(error.description + "\n", stderr) }
CommandRegistry::main
mainメソッドの中身を見ると、以下のようになっている。
@noreturn public func main(defaultVerb defaultVerb: String, errorHandler: ClientError -> ()) { main(arguments: Process.arguments, defaultVerb: defaultVerb, errorHandler: errorHandler) }
標準のProcess.arguments
を使ってコマンドライン引数を取得した上で、別のメソッドに転送している。
ちなみにProcess.arguments
は[String]
を返し、最初の要素はコマンド自身の名前となる。
つまりcarthage help
とした場合は、["carthage", "help"]
という結果が得られるはずである。
転送先のメソッドを見てみると・・・ちょっと長い。
@noreturn public func main(arguments arguments: [String], defaultVerb: String, errorHandler: ClientError -> ()) { assert(arguments.count >= 1) var arguments = arguments // Extract the executable name. let executableName = arguments.remove(at: 0) let verb = arguments.first ?? defaultVerb if arguments.count > 0 { // Remove the command name. arguments.remove(at: 0) } switch runCommand(verb, arguments: arguments) { case .Success?: exit(EXIT_SUCCESS) case let .Failure(error)?: switch error { case let .UsageError(description): fputs(description + "\n", stderr) case let .CommandError(error): errorHandler(error) } exit(EXIT_FAILURE) case nil: if let subcommandExecuted = executeSubcommandIfExists(executableName, verb: verb, arguments: arguments) { exit(subcommandExecuted) } fputs("Unrecognized command: '\(verb)'. See `\(executableName) help`.\n", stderr) exit(EXIT_FAILURE) } }
端折ってもいいけれど一応順番に見ていく。 (今後、繰り返しになりそうであれば割愛していく感じで)
コマンド名と引数の取得
assert(arguments.count >= 1) var arguments = arguments // Extract the executable name. let executableName = arguments.remove(at: 0)
まずassert
でarguments
の要素数が1以上であることを確認している。
前述したが、引数無しで$ carthage
と打った場合でもcarthage
が格納されるはずなのでこれは正当なassert。
そして次に「コマンド名」と「コマンド引数」に分離すべく、arguments
をvar
で宣言しなおしている。
(ここで同じ名前で宣言しなおしているのは賛否ありそうだが、あえて新しい名前をつけるシチュエーションでもなかろう)
そして最初の引数であるはずのcarthage
を除外して、executableName
に入れている。
(最初、これは使わないから_
でいいかと思ったのだが、あとで使っているようだ)
let verb = arguments.first ?? defaultVerb if arguments.count > 0 { // Remove the command name. arguments.remove(at: 0) }
ここでverb
にコマンド名、あるいはデフォルトであたえたhelpCommand.verb
(すなわちhelp
)が入る。
つまり$ carthage version
であればversion
、
$ carthage
であればhelp
がデフォルトコマンドとして格納されるわけだ。
そしてarguments
の最初の要素を削って、コマンドの本来の引数としている。
すなわち$ carthage update --platform mac
であれば、["--platform", "mac"]
となるはずだ。
実行
次に実際にコマンドを実行しているようだ。
switch runCommand(verb, arguments: arguments) { case .Success?: exit(EXIT_SUCCESS) case let .Failure(error)?: switch error { case let .UsageError(description): fputs(description + "\n", stderr) case let .CommandError(error): errorHandler(error) } exit(EXIT_FAILURE) case nil: if let subcommandExecuted = executeSubcommandIfExists(executableName, verb: verb, arguments: arguments) { exit(subcommandExecuted) } fputs("Unrecognized command: '\(verb)'. See `\(executableName) help`.\n", stderr) exit(EXIT_FAILURE) }
runCommand(verb, arguments: arguments)
メソッドで先ほど解決した、コマンド名と引数を渡している。
そしてその結果(成功 or 失敗 or nil
?)で処理結果を分離している。
そのまま読んでみると、以下のようになるだろうか。
- 成功:プロセス終了
- 失敗(引数の指定が正しくない):usage(正しい使い方)を出力して終了
- 失敗(コマンド失敗):登録されたエラーハンドラを実行
- nil:サブコマンドがあれば実行し、なければエラーとする
nilのケースが良くわからなかった。 これは「Commandantモジュール」の中なので、Carthageでは使用していない部分の可能性もある。
追記:
直後に分かったが登録されていないコマンドを与えた場合にnil
のケースになるようだ。
$ carthage foo $ Unrecognized command: 'foo'. See `carthage help`.
CommandRegistry::runCommand
runCommandの中身に飛ぶと、以下のようになっている。
public func runCommand(verb: String, arguments: [String]) -> Result<(), CommandantError<ClientError>>? { return self[verb]?.run(ArgumentParser(arguments)) }
self[verb]
でsubscriptを呼び出して、CommandWrapper
を取得している。
public subscript(verb: String) -> CommandWrapper<ClientError>? { return commandsByVerb[verb] }
commandsByVerb
は以下のように宣言されており、register
メソッドを呼び出した際に更新されている。
private var commandsByVerb: [String: CommandWrapper<ClientError>] = [:] public func register<C: CommandType where C.ClientError == ClientError, C.Options.ClientError == ClientError>(command: C) { commandsByVerb[command.verb] = CommandWrapper(command) }
つまりcommandsByVerb
はコマンド名をキーに、CommandoType
のラッパーを値として格納するDictionaryだ。
これによってコマンド名に対応するCommandWrapper
(とやら)を取得して、.run(ArgumentParser(arguments))
を呼び出している。
ArgumentParser
は大体想像するとおりのこと(つまり引数文字列のパース)をやっていると思うので、とりあえず放置する。
CommandWrapper
まずコード全体を見てみる。
public struct CommandWrapper<ClientError: ClientErrorType> { public let verb: String public let function: String public let run: ArgumentParser -> Result<(), CommandantError<ClientError>> public let usage: () -> CommandantError<ClientError>? /// Creates a command that wraps another. private init<C: CommandType where C.ClientError == ClientError, C.Options.ClientError == ClientError>(_ command: C) { verb = command.verb function = command.function run = { (arguments: ArgumentParser) -> Result<(), CommandantError<ClientError>> in let options = C.Options.evaluate(.Arguments(arguments)) if let remainingArguments = arguments.remainingArguments { return .Failure(unrecognizedArgumentsError(remainingArguments)) } switch options { case let .Success(options): return command .run(options) .mapError(CommandantError.CommandError) case let .Failure(error): return .Failure(error) } } usage = { () -> CommandantError<ClientError>? in return C.Options.evaluate(.Usage).error } } }
これもちょっとややこしそうだ。
verb、function
とりあえずイニシャライザを覗いてみると、、、
verb
とfunction
は、単にCommandTypeの値を転記しているだけのようだ。
verb = command.verb function = command.function
run
run
はArgumentParser
を受け取ってResult
を返すクロージャが格納されている。
run = { (arguments: ArgumentParser) -> Result<(), CommandantError<ClientError>> in let options = C.Options.evaluate(.Arguments(arguments)) if let remainingArguments = arguments.remainingArguments { return .Failure(unrecognizedArgumentsError(remainingArguments)) } switch options { case let .Success(options): return command .run(options) .mapError(CommandantError.CommandError) case let .Failure(error): return .Failure(error) } }
個人的にはクロージャではなくて普通にメソッドでも良いと感じたのだが、何かメリットがあるのだろうか?
とりあえずオプション引数のパースを試みて、成功したらcommand.run(options)
で実行し、
失敗した場合は.Failure
で失敗を返しているようだ。(まぁその辺の詳細はおいおい)
command.run(options)
で実行される具体例は、前回見た以下となる。
public func run(options: NoOptions<CarthageError>) -> Result<(), CarthageError> { let versionString = NSBundle(identifier: CarthageKitBundleIdentifier)?.objectForInfoDictionaryKey("CFBundleShortVersionString") as! String carthage.println(versionString) return .Success(()) }
これでコマンド引数のパースから、前回のVersionCommand
のrun()
までつながった。
usage
usage
も同じようにクロージャが格納されている。
usage = { () -> CommandantError<ClientError>? in return C.Options.evaluate(.Usage).error }
CommandType
に紐づくOptions
で.Usage
を評価させて、.error
を返しているらしい。
難しく見えるけれど、要はオプション引数のusage(使い方)を返す処理のようだ。
また新しいテーマに入ってしまうので深追いはやめておくが、参考までにHelpCommand
のevaluate
は以下のようになっている。
public static func evaluate(m: CommandMode) -> Result<HelpOptions, CommandantError<ClientError>> { return create <*> m <| Argument(defaultValue: "", usage: "the command to display help for") }
まとめ
とりあえず今日はここまで! なんかCarthageというよりも依存ライブラリに対するコードリーディングになっている気もするが・・・。
続く・・・のかもしれない。