Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

How To

非傳統語法
響應文件
Winmain 命令行
選項組和隱藏選項
定制驗證器
Unicode 支持
允許未知選項

本節描述本庫如何在特定情況下使用。

非傳統語法

有時候,標準的命令行語法是不夠的。例如,gcc 編譯器有 "-frtti" 和 -fno-rtti" 選項,這種語法不能直接支持。

對於這種情況,程序庫允許用戶提供一個 附加分析器 -- 一個在每個命令行元素被程序庫處理之前就調用的函數。如果附加分析器認可該語法,則返回選項名和選項值。上述例子可以用以下代碼處理:

pair<string, string> reg_foo(const string& s)
{
if (s.find("-f") == 0) {
if (s.substr(2, 3) == "no-")
return make_pair(s.substr(5), string("false"));
else
return make_pair(s.substr(2), string("true"));
} else {
return make_pair(string(), string());
}
}

這就是附加分析器的定義。在分析命令行時,我們這樣傳遞附加分析器:

store(command_line_parser(ac, av).options(desc).extra_parser(reg_foo)
.run(), vm);

完整的例子請見 "example/custom_syntax.cpp" 文件。

響應文件

有些操作系統對於命令行長度只有很低的限制。對付這些限制的常用方法是使用 response files響應文件。響應文件是採用與命令行相同語法的配置文件。如果命令行指定了響應文件的名字,則導入該文件並進行命令行分析。程序庫沒有提供對響應文件的直接支持,所以你需要編寫額外的代碼。

首先,你需要為響應文件定義一個選項:

("response-file", value<string>(), 
"can be specified with '@name', too")

其次,你需要一個附加分析器,以支持指定響應文件的標準語法: "@file":

pair<string, string> at_option_parser(string const&s)
{
if ('@' == s[0])
return std::make_pair(string("response-file"), s.substr(1));
else
return pair<string, string>();
}

最手,如果找到了 "response-file" 選項,你需要導入文件並將它傳給命令行分析器。這部分是最難的。我們要使用 Boost.Tokenizer 庫,該庫可用但有點限制。你也可考慮 Boost.StringAlgo. 代碼如下:

if (vm.count("response-file")) {
// 導入文件並分解記號
ifstream ifs(vm["response-file"].as<string>().c_str());
if (!ifs) {
cout << "Could no open the response file\n";
return 1;
}
// 將整個文件讀入到 string
stringstream ss;
ss << ifs.rdbuf();
// 分解文件內容
char_separator<char> sep(" \n\r");
tokenizer<char_separator<char> > tok(ss.str(), sep);
vector<string> args;
copy(tok.begin(), tok.end(), back_inserter(args));
// 分析文件並保存選項
store(command_line_parser(args).options(desc).run(), vm);
}

完整的例子請見 "example/response_file.cpp" 文件。

Winmain 命令行

在 Windows 操作系統,GUI 應用以單個字符串接收命令行,而不分解為多個元素。由於這個原因,命令行分析器不能直接使用。有一些編譯器可以得到分解的命令行,但不清楚是否所有編譯器在所有版本的操作系統上都支持相同的機制。split_winmain 函數是本庫提供的可移植方案。

下面是一個用例:

vector<string> args = split_winmain(lpCmdLine);
store(command_line_parser(args).options(desc).run(), vm);

該函數有一個對於 wchar_t 字符串的重載,因此也可用於 Unicode 應用。

選項組和隱藏選項

用單個 options_description 實例處理所有程序選項有以下問題:

  • 有些選項只用於特定的源,如配置文件。

  • 用戶傾向於生成結構化的幫助信息。

  • 有些選項不應出現在生成的幫助信息中。

為了解決以上問題,本庫允許程序員創建多個 options_description 實例,併合並為不同的組合。在以下例子中定義了三組選項:一個用於命令行指定,另兩個用於特定的程序模塊,其中只有一個會在生成的幫助信息中顯示。

每個組都使用標準語法定義。但是,你應為每個 options_description 實例取一個合理的名字:

options_description general("General options");
general.add_options()
("help", "produce a help message")
("help-module", value<string>(),
"produce a help for a given module")
("version", "output the version number")
;

options_description gui("GUI options");
gui.add_options()
("display", value<string>(), "display to use")
;

options_description backend("Backend options");
backend.add_options()
("num-threads", value<int>(), "the initial number of threads")
;

聲明了選項組之後,我們把它們合併為兩個組合。第一個包含所有選項,用於分析。第二個則用於 "--help" 選項。

// 聲明一個選項描述實例,包含所有選項
options_description all("Allowed options");
all.add(general).add(gui).add(backend);

// 聲明一個選項描述實例,包含顯示給用戶的選項
options_description visible("Allowed options");
visible.add(general).add(gui);

剩下就是分析和處理選項:

variables_map vm;
store(parse_command_line(ac, av, all), vm);

if (vm.count("help"))
{
cout << visible;
return 0;
}
if (vm.count("help-module")) {
const string& s = vm["help-module"].as<string>();
if (s == "gui") {
cout << gui;
} else if (s == "backend") {
cout << backend;
} else {
cout << "Unknown module '"
<< s << "' in the --help-module option\n";
return 1;
}
return 0;
}
if (vm.count("num-threads")) {
cout << "The 'num-threads' options was set to "
<< vm["num-threads"].as<int>() << "\n";
}

在分析命令行時,所有選項都可用。但是 "--help" 信息不包含 "Backend options" 組 -- 該組中的選項被隱藏。用戶可以通過傳入 "--help-module backend" 選項來顯式強制選項組的顯示。完整的例子請見 "example/option_groups.cpp" 文件。

定制驗證器

缺省地,選項值從字符串到C++類型的轉換使用 iostreams 來執行,但有時候會不太方便。本庫允許用戶為特定類定制轉換的方法。用戶要提供 validate 函數的合適重載。

我們首先定義一個簡單的類:

struct magic_number {
public:
magic_number(int n) : n(n) {}
int n;
};

然後重載 validate 函數:

void validate(boost::any& v, 
const std::vector<std::string>& values,
magic_number* target_type, int)
{
static regex r("\\d\\d\\d-(\\d\\d\\d)");

using namespace boost::program_options;

// 確定之前沒有對 'a' 賦值
validators::check_first_occurrence(v);
// 從 'values' 提取第一個串。如果有多於一個串則為錯誤,拋出異常
const string& s = validators::get_single_string(values);

// 進行 regex 匹配並將相應部分轉換為 int.
smatch match;
if (regex_match(s, match, r)) {
v = any(magic_number(lexical_cast<int>(match[1])));
} else {
throw validation_error("invalid value");
}
}

該函數帶四個參數。第一個為選項值的存儲,本例中要麼為空,要麼包含一個 magic_number 實例。第二個為下一個選項的字符串列表。剩下的兩個參數對於缺乏模板偏特化和函數模板部分分類的編譯器是需要的。

函數首先檢查我們是否試圖對同一個選項重複賦值。然後檢查傳入的單個字符串。接著字符串在 Boost.Regex 庫的幫助下進行驗證。如果測試通過,分析得到的值被保存在變量 v 中。

完整的例子請見 "example/regex.cpp" 文件。

Unicode 支持

將本庫使用於 Unicode, 你需要:

  • 對 Unicode 輸入使用 Unicode-aware 的分析器

  • 要求選項在需要時支持 Unicode

多數分析器都有 Unicode 版本。例如,parse_command_line 函數有一個重載接受 wchar_t 字符串,替代原先的 char.

即使有些分析器是 Unicode-aware 的,但並不意味你需要修改所有選項的定義。事實上,對於多數選項,如整型的,並不影響。要使用 Unicode 你需要一些 Unicode-aware 選項。這些選項與原先的不同在於它們接受 wstring 輸入,並使用寬字符流來處理它。創建一個 Unicode-aware 選項非常容易:只要使用 wvalue 函數替代通常  value 就行了。

當一個 ascii 分析器傳遞數據給一個 ascii 選項時,或一個 Unicode 分析器傳遞數據給一個 Unicode 選項時,數據不會改變。即 ascii 選項將看到一個本地8位編碼的字符串,而 Unicode 選項將看到 Unicode 輸入的字符串。

如果將 Unicode 數據傳遞給 ascii 選項或反之,會如何?本庫會自動執行從 Unicode 到本地8位編碼的轉換。例如:如果命令行是 ascii 的,而你使用 wstring 選項,則 ascii 輸入將被轉換為 Unicode.

為了執行轉換,本庫使用了來自於 global locale 的 codecvt<wchar_t, char> locale facet。如果你想處理使用本地8位編碼(不要使用 7-bit ascii 字集)的字符串,你的應用程序要以這樣開始:

locale::global(locale(""));

這就設置了轉換 facet 依照於用戶選擇的 locale.

明智的做法是檢查一下在你的實現上對 C++ locale 的支持狀態。快速的測試包括以下三步:

  1. 進入 "test" 目錄,構建 "test_convert" 程序。

  2. 在環境中設置某個非-ascii locale. 例如在 Linux 上,你可執行:

    $ export LC_CTYPE=ru_RU.KOI8-R

  3. 運行 "test_convert" 程序,以一些選定編碼的非-ascii 字符串作為參數。如果你看到一串 Unicode 編碼,則一切OK。否則,表示你的系統上的 locale 支持可能已被破壞。

允許未知選項

通常,本庫在遇到未知選項時會拋出異常。這種行為可以被改變。例如,你的程序中只有一部分使用 Program_options, 而你希望將未能識別的選項傳遞給程序的其它部分,或者甚至是其它程序。

要允許在命令行中使用未登記的選項,你需要使用 basic_command_line_parser 類來分析 (而不是 parse_command_line) ,並且調用該類的 allow_unregistered 方法:

parsed_options parsed = 
command_line_parser(argv, argc).options(desc).allow_unregistered().run();

對於每一個看起來像選項,但又沒有已知名字的記號,將產生一個 basic_option 實例並增加到結果中。該實例的 string_keyvalue 域中包含了對該記號進行語法分析的結果,unregistered 域將被設置為 true, 而 original_tokens 域則包含出現在命令行中的記號。

如果你想進一步傳遞這個未能識別的選項,可以使用 collect_unrecognized 函數。該函數收集所有未能識別的原始記號,以及所有位置選項(可選)。即是說,如果你的代碼只處理少數選項,而不處理位置選項,則你可以這樣使用函數:

vector<string> to_pass_further = collect_arguments(parsed.option, include_positional);

Copyright © 2002-2004 Vladimir Prus

PrevUpHomeNext