Heresy 在之前已經介紹過 Boost Program Options 的基本使用,以及一些 options_description 設定上的進階用法了。而接下來這篇,則是大概來講一下在「分析」這一部分的功能。
下面就是在之前的範例裡面,這部分的程式碼:
BPO::variables_map mVMap;
BPO::store( BPO::parse_command_line( argc, argv, bOptions ), mVMap );
BPO::notify( mVMap );
基本介紹
這邊除了宣告出 mVMap 這個物件外,實際上是有三個的動作:
BPO::basic_parsed_options<char> mParsedOpt
= BPO::parse_command_line( argc, argv, bOptions );
BPO::store( mParsedOpt, mVMap );
BPO::notify( mVMap );
首先第一步是先透過 Boost Program Options 的 parse_command_line() 這個函式,去針對所指定的 options_description、也就是 bOptions,來分析透過命令提示字元輸入產生的 argc 和 argv;而在分析完成後,他會回傳一個 basic_parsed_options<char> 的物件,裡面儲存了分析完成的選項資料。
而接下來呢,則是在透過 store() 這個函式,把分析完的選項(mParsedOpt),寫入到指定的 variables_map 裡(這邊就是 mVMap);當寫入完成後,則是在透過 notify() 這個函式,讓 mVMap 完成「通知」的動作、對分析完的選項做最後的處理。
而後面 store() 和 notify() 兩個步驟,基本上沒有什麼可以控制的功能。不過在第一個部分,也就是產生 basic_parsed_options<char> 的物件的過程,實際上還是有一些其他可以使用的功能的~這一篇主要就是針對這邊來做說明了。
錯誤處理
在開始講其他功能之前,先來講一下 Boost Program Options 的錯誤處理。
一般來說,Boost Program Options 在執行階段時,最有可能出問題的地方,就是在針對使用者輸入的選項作分析的時候;因為使用者輸入的值可能是千奇百怪、和預設的格式不相符的,所以如果沒有額外做處理,就很有可能在這個階段,產生例外狀況而導致程式中斷。
而為了避免程式直接中斷、同時也可以告訴使用者他的參數哪裡有問題,所以在這邊最好是加上 C try / catch、也就是「Exceptions」的處理機制(參考)。像其實在上一篇的「必要選項」這一部分,Heresy 就已經有在分析的部分,加上 try / catch,來捕捉可能產生的例外狀況了~
基本上,C Exception 的處理機制,就是靠 ctach() 不同型別例外資料,來做不同的錯誤處理;而 Boost Program Options 也定義了許多種不同的「error」型別(參考),讓使用者可以做處理。下面就是 Boost Program Options 提供的各種例外錯誤的型別:
class error;
class too_many_positional_options_error;
class invalid_command_line_style;
class reading_file;
class error_with_option_name;
class multiple_values;
class multiple_occurrences;
class required_option;
class error_with_no_option_name;
class unknown_option;
class ambiguous_option;
class invalid_syntax;
class invalid_config_file_syntax;
class invalid_command_line_syntax;
class validation_error;
class invalid_option_value;
class invalid_bool_value;
基本上,這些不同的錯誤型別,都是繼承自「error」這個型別,而 error 則又是繼承自 std::logic_error(他又繼承自 std::exception,參考),所以一般在處理的時候,最簡單的方法就是透過呼叫他的 what() 這個函式,來取得錯誤訊息的字串。
所以,最簡單的處理方法,就是:
try
{
BPO::store( BPO::parse_command_line( argc, argv, bOptions ), mVMap );
BPO::notify( mVMap );
}
catch( BPO::error e )
{
cerr << e.what() << endl;
return 1;
}
上面這樣的寫法,就是會去接所有 Boost Program Options 的例外錯誤,並把錯誤訊息輸出、然後結束程式。
不過這樣並不是很好的用法,因為在這樣的寫法下,如果給了一個沒有定義的選項的話,他會出現下面這樣的訊息:
unrecognised option '�nonical_option%'
錯誤訊息的確是看的出來,錯誤的原因是使用者給了一個無法識別的選項,但是重點的選項名稱,他卻只有顯示「�nonical_option%」,讓人無法知道到底是哪個選項錯了。
而要處理這個問題,比較好的方法,就是針對這樣的錯誤,獨立去處理;像是在這個例子來說,這種錯誤是被歸類成「有選項名稱的錯誤」,所以就需要在錯誤處理的地方,針對「error_with_option_name」這類的例外錯誤來做處理,也就是要把程式改成:
try
{
BPO::store( BPO::parse_command_line( argc, argv, bOptions ), mVMap );
BPO::notify( mVMap );
}
catch( BPO::error_with_option_name e )
{
cerr << e.what() << endl;
return 1;
}
catch( BPO::error e )
{
cerr << e.what() << endl;
return 1;
}
這樣一來,有選項名稱的錯誤,透過 what() 得到的錯誤訊息,就會真的把選項名稱列出來了~像下面就是一個這種錯誤的例子:
unrecognised option '--ERR'
但是這樣修改,也不能解決所有的問題。比如說如果是遇到使用者給的選項的值無法處理(例如是要給一個整數,但是使用者給字串),這時候除了會要輸出選項名稱外,使用者輸入的值也會要輸出,所以單用「error_with_option_name」也是不夠的,而需要另外去針對「validation_error」這個類型的錯誤來做處理,才能取得完整的錯誤訊息。
雖然這樣看起來,要完整地處理 Boost Program Options 的例外錯誤,應該必須針對他所提供的每一種錯誤型別,都各自處理,整個下來會很繁瑣;不過這樣應該是可以針對所有可能的錯誤,做各自、完整的處理的!
但是雖說如此,其實很多時候並不需要這麼完整地處理啊…Heresy 還是希望它可以簡化一點啊~至少,透過 error 的 what() 來取得錯誤訊息的時候,能直接把錯誤訊息完成就好了。 ^^"
另外,這邊的錯誤處理如果希望可以寫一次,用在很多地方的話,應該可以參考之前的《使用 Macro 和 Lambda 簡化 Exception Handling》一文。
Command Line Parser 的其他功能
除了上面所使用的 parse_command_line() 之外,Boost Program Options 還有提供其他的 parser,可以用來根據指定的 options_description、分析所給的選項資料。
像之前的例子裡面所使用的 parse_command_line() 這個函式,實際上就是使用 Boost Program Options 的 basic_command_line_parser 這個 template 類別(文件),來實作的簡易版本;而如果需要做進一步的一些控制的話,實際上也可以直接使用 basic_command_line_parser<>。
如果要使用 basic_command_line_parser<> 的話,一般是會直接使用重新定義過,對應一般字元的 command_line_parser 或是對應寬字元的 wcommand_line_parser;前者實際上就是 basic_command_line_parser<char>,而後者則是 basic_command_line_parser<wchar_t>。
而最簡單的使用狀況,大概就是下面這樣:
//store( parse_command_line( argc, argv, bOptions ), mVMap );
store( command_line_parser( argc, argv ).options( bOptions ).run(), mVMap );
實際上如果都展開的話,就是:
command_line_parser bCMLParser( argc, argv );
parsed_options mParsedOpt = bCMLParser.options( bOptions ).run();
store( mParsedOpt, mVMap );
這邊,就是先建立一個 command_line_parser 的物件 bCMLParser,在建立的同時,就把命令提示字元的參數(argc、argv)傳進去(這邊也可以用一個字串陣列)。
接下來則是透過 options() 這個函式,來指定設定好的 options_description(這邊就是 bOptions);而他會把自己的參考回傳,所以接下來可以直接呼叫 run() 這個函式,要求 bCMLParser 開始分析輸入的參數。
而分析完成後,他就會回傳分析好的結果,也就是型別是 parsed_options(這個型別實際上就是前面提過的 basic_parsed_options<char>)的物件、mParsedOpt;這個得到的結果,和直接呼叫 parse_command_line() 是相同的。
在得到 parsed_options 後,接下來就是和前面的流程一樣,透過 store() 這個函式,把分析完的選項(mParsedOpt),寫入到指定的 variables_map 裡(這邊就是 mVMap)了~當然,最後也要記得去呼叫 notify() 這個函式,才算全部完成。
而直接使用 basic_command_line_parser 有什麼好處呢?它主要提供了幾個功能:
-
允許未登記的選項
一般沒有特別設定的狀況下,如果給了沒有設定在 options_description 裡的選項的話,會在分析時產生例外狀況而結束分析;如果希望使用者可以輸入有定義的以外的參數的話,則可以透過 allow_unregistered() 這個函式來做設定,讓 parser 接受沒有設定的參數。
而這些無法識別的選項也都會存在分析完的結果裡,可以很簡單地透過 collect_unrecognized() 這個函式(參考),把他擷取出來變成字串的陣列,看自己要怎麼做額外的處理;下面就是一個使用的例子:
command_line_parser bCMLParser( argc, argv );
parsed_options mParsedOpt
= bCMLParser.options( bOptions ).allow_unregistered().run();
vector<string> vNDefOpts
= collect_unrecognized( mParsedOpt.options, include_positional ); -
可以把沒有名稱的選項資料,根據位置做對應
一般的選項都包含了名稱、和值兩個項目(不過根據設定,值可能可以省略),但是實際上還有另一種命令提示字元的型態,是不給選項的名稱、直接給值的;而這種使用方法,通常會根據所給的順序來做處理,例如:第一個參數是輸入的檔案、第二個參數是輸出的檔案,類似這樣。
而為了處理這類的應用,Boost Program Options 也有提供 positional_options_description,可以用來把沒有名稱的參數,根據位置對應成指定的選項的值(參考)。不過由於例子講起來會比較繁瑣,所以在這邊就先跳過,之後有機會再寫了。
-
可以設定命令提示字元的選項風格(style)
Boost Program Options 在命令提示字元下,預設是使用「—」當作前置、然後可以用「–」加上一個字元,來當作簡短的寫法;如果有需要的話,也可以透過 basic_command_line_parser 的 style() 這個函式,做一些調整、設定。
他可以用的選項,基本上是透過 command_line_style::style_t 這個列舉型別來做設定;不過由於預設的 default_style 基本上已經算是包含了大部分的參數,所以如果要自己再做調整,主要就是把本來允許的功能關閉了。而其他可能可以追加的,包括了:
- 透過 case_insensitive 來讓他無視大小寫
- 透過 allow_slash_for_short 讓他可以接受以「/」開頭的短選項。
- 透過 allow_long_disguise 讓長選項名稱也可以只有單一個「–」開頭
完整的列表和說明,可以參考官方文件(連結),有興趣的可以自己玩看看,Heresy 這邊就先不多提了。
-
可以設定額外的處理方式
基本上,basic_command_line_parser 已經提供了相當完整的分析功能了。不過如果真的覺得不夠,想要自己追加特別的處理的話,也可以透過他提供的 extra_parser() 這個函式,給他一個型別符合 function1<pair<string, string>, const string&> 形式的 function object(接受一個 const string& 型別的資料做輸入,分析後拆成名稱和值、回傳 pair<string, string>),讓他在分析時去呼叫這個 function object、進行自己要做的額外處理。
不過由於這也是相當進階的功能了,所以之後有機會的話再講吧。
-
定額外的風格處理
可以透過他提供的 extra_style_parser() 這個函式,來設定一個額外的風格處理方法。他接受的 function object 是 function1<vector<option>, vector<string>&> 的形式。認真講,Heresy 覺得要用到的機會應該不大,這邊就暫時不提了。