KengoSawa2の技術的ななにか

IT屋さんのようなKengoSawa2がなんかそれっぽい事を書いていくblogです

Qt5.2で登場したQCommandLineParserの使い方について

こんにちは、Qt使い始めて約半年のKengoSawa2です。
クラスの使い方レベルの記事で恐縮なのですが、Qt5.2で登場した「QCommandLineParser」について、簡単に紹介したいと思います。

自己紹介

とある映像編集業の会社に所属している謎のSEです。
現在はWindows用のファイルコピーアプリ「FastCopy」の移植をやっています。

"RapidCopy" MacOSX用高速ファイルコピーソフト

移植の過程でGUIコーディングを楽にするためにQtを使うことを決め、
その御縁からQtユーザ会に参加させて頂いてます。

今回紹介する「QCommandLinerParser」もRapidCopyのCLI起動時の解析のために使用しました。

実行環境

今回の実行環境はMac OS X 10.8.5 + Qt5.3.1で説明しています。
他プラットフォーム(特にwindows)では出力例に関しては結果が異なる可能性がありますので、
ご注意ください。

QCommandLineParserの概要

CommandLineParserはQt5.2から登場したコマンドラインにおける引数チェック、オプションチェック目的に特化したクラスです。

https://qt-project.org/doc/qt-5-snapshot/qcommandlineparser.html

C言語でのコマンドライン解析は地味ですが、意外と労力が要ります。
#更に言うとくだらないバグも作りこみやすい:)
それらを簡単に扱うためのクラスです。

QCommandLineParserの使い方の流れ

QCommandLineParserには使用するためのお作法、おおまかな流れがあります。
先に以下の流れをざっくり理解しておくと良いでしょう。

  1. QCommandLineParserクラスのインスタンスを作成し、アプリケーションの静的な情報を設定する
  2. オプションの情報設定
  3. コマンドライン解析の実行
  4. 解析結果の判定
  5. 2.で設定したオプション情報を1つずつ取り出す

なにはともあれまずコード

流れがなんとなくわかった所で、まずはサンプルコードを眺めてみましょう。
下記のコードはRapidCopyの解析処理の一部を簡略化して説明用に単純にしたものです。

bool MainWindow::CommandLineExecV(int argc, QStringList argv)
{
	int argerr = 0;         //コマンドエラーとして終了する際のリターン値

	//コマンドオプション
	QString CMD_STR       = "cmd";
	QString BUFSIZE_STR   = "bufsize";
	QString ESTIMATE_STR  = "estimate";
	QString VERIFY_STR    = "verify";
	QString SPEED_STR     = "speed";

	QCommandLineParser parser;

	//--help,-hをQCommandLineParserで自動で処理するように依頼
	parser.addHelpOption();

	//オプション以外の純粋な引数についての指定と、解説欄の設定
	parser.addPositionalArgument("path1 path2 ...","Source path to copy.");

	//--cmdオプションの情報設定
	QCommandLineOption cmdOpt(QStringList() << CMD_STR,                //オプション文字列
	                          "Specify operation mode.(Default: dif)", //help表示の際のオプション説明
	                          "noe|dif|upd|for|syn|ver|mov|del",       //オプション指定例
	                          "dif");                                  //デフォルトの指定

	//--bufsizeオプションの情報設定
	QCommandLineOption bufOpt(QStringList() << BUFSIZE_STR,
	                          "Specify the size(MB) of the main buffer for Read/Write opration.",
	                          "num",
	                          "1");
	//--estimateオプションの情報設定
	//引数が不要なオプションを設定したい場合には第三引数と第四引数を省略します。
	QCommandLineOption isestimateOpt(QStringList() << ESTIMATE_STR,
	                                "Estimate complete time.");

	//--verifyオプションの情報設定
	QCommandLineOption isverifyOpt(QStringList() << VERIFY_STR,
	                              "Verify written files data.",
	                              "md5|sh1|xxh|s22|s25|s32|s35|fal");
	//--speedオプションの情報設定
	QCommandLineOption speedOpt(QStringList() << SPEED_STR,
	                            "Specify speed control level.",
	                            "full|autoslow|1-9|suspend");

	//設定したオプション情報をparserに追加
	parser.addOption(cmdOpt);
	parser.addOption(bufOpt);
	parser.addOption(isestimateOpt);
	parser.addOption(isverifyOpt);
	parser.addOption(speedOpt);

	//コマンドラインの事前解析
	//実際のパース処理を行うprocess()では情報設定済以外のオプションが
	//存在すると内部でexit()をコールしてしまうため、事前にparse()してチェックしています。

	//parseがfalse=不明なオプションが設定されている
	if(!parser.parse(argv)){
		//知らないオプションが指定されていたので、対象のエラーオプションを取り出して表示。
		QString error_str("illegal option --");
		for(int i=0;i<parser.unknownOptionNames().count();i++){
			error_str.append("\"" + parser.unknownOptionNames().at(i) + "\"");
		}
		error_str.append("\n");
		QTextStream(stderr) << error_str;
		argerr = -1;
		goto end;
	}
	//コマンドライン解析の実行
	parser.process(argv);

	//解析後の各オプションの設定有無を判定
	if(parser.isSet(cmdOpt)){
		//cmdOptの内容を取得
		QString cmdopt = parser.value(cmdOpt);
		//cmdoptの内容チェック、必要な処理の実装をここに
	}

	if(parser.isSet(bufOpt)){
		//以下略
	}
	if(parser.isSet(isestimateOpt)){
		//以下略
	}
	if(parser.isSet(isverifyOpt)){
		//以下略
	}
	if(parser.isSet(speedOpt)){
		//以下略
	}

end:
	//ここまでのコマンド解析でエラーが一つでもあれば終了
	if(argerr == -1){
		//helpを表示してexit()する
		parser.showHelp(argerr);
	}
	return(true);
}

説明はコメント欄に入れちゃいましたので割愛します:)

この例ではCommandLineExecVという関数になっており、いわゆる「argcとargv」は上位から貰う構造になってますが、
Qt環境下ではQCoreApplication::arguments()を呼び出せばargv相当のものを簡単に取得できます。
argcに関してはQCoreApplication::arguments().count()で一発です。


ヘルプ及びエラーの実行例

MacOSX付属のターミナルでヘルプを実行してみた結果は以下のようになります。
f:id:seattlei:20141216131331p:plain

オプション設定していない文字列を適当に指定すると、以下のように不正引数を出力してからヘルプを表示して終了します。
f:id:seattlei:20141216132008p:plain

QCommandLineParserさんのヘルプ表示で困るところ

さて、なんとなく使い方がわかってきたQCommandLineParserですが、ちょっと困る点もあります。
それは

「ターミナル表示の一行80文字あたりを前提として折り返し位置が固定されている」ことです。

先ほどのヘルプ画像を見てみましょう。

f:id:seattlei:20141216132834p:plain

オレンジ色の部分がオプションの説明文字列なわけですが、なにやら窮屈です。
長い説明を入れてしまうと、行数が嵩んで見栄えが悪くなります。
これは緑色の部分の文字数が多いと、説明欄を圧迫するせいです。

試しに長いオプション指定例の文字数を削ってみましょう。
すると、。
f:id:seattlei:20141216133245p:plain

このように、説明欄が割と広くなります。
私が試した限り、QCommandLineParserではこのバランスの調整は出来ないようなので、
自分なりに妥協出来るところまで文字数を微調整して詰めるしかないようです。

その他あんなことこんなこと

QCommandLineParserにはその他にも

  • -vや--versionに自動で対応するためのaddVersionOption()
  • -lsn 等の指定を -l -s -n としてみなすか否かを設定するsetSingleDashWordOptionMode()
  • 複数の引数がそれぞれ違う意味を持つ場合に説明を追加するためのaddPositionalArgument()

などがあります。色々試してみてください。

まとめ

英語の苦手な私だと公式のexampleがパッと理解できなかったので、今回の記事を書いてみました。
初心者さんのCLI入門として活用して貰えたらいいなあと思っています。
最後になりますが、twitterでボヤいていたら助けて頂いた
QtChampionことTasuku Suzuki (@task_jp) | Twitter氏と
渋川よしき (@shibu_jp) | Twitter氏、ありがとうございました(._.)
はっぴーQtらいふ!!

12/17追記:parser.parse部分の処理に関して無駄に判定多かったので修正しました。QtChampionことTasuku Suzuki (@task_jp) | Twitterさん、ご指摘ありがとうございます!