2017-05-16 31 views
3

我想端口舊的命令行工具來boost::program_options。該工具用於許多第三方腳本,其中一些我不能更新,因此更改命令行界面(CLI)不是我的方式。升壓程序選項,空字符串處理

我有一個位置參數,幾個標誌和常規參數。但是我遇到了ranges的爭論。它應該如下:

> my_too.exe -ranges 1,2,4-7,4 some_file.txt # args[ranges]="1,2,4-7,4" 
> my_too.exe -ranges -other_param some_file.txt # args[ranges]="" 
> my_too.exe -ranges some_file.txt    # args[ranges]="" 

基本上,我想boost::po停止,如果其他參數滿足或者類型不匹配解析參數值。有沒有一種方法可以實現正好這種行爲?

我嘗試使用implicit_value,但它不工作,因爲它需要CLI方式變更(需要參數用鑰匙進行調整):

> my_too.exe -ranges="1,2-3,7" some_file.txt 

我試着用multitoken, zero_tokens伎倆,但它不」當位置參數被滿足或參數不匹配時停止。

> my_tool.exe -ranges 1,2-4,7 some_file.txt # args[ranges]=1,2-4,7,some_file.txt 

任何想法?

回答

2

這並不簡單,但您需要的語法很奇怪,當然需要一些手動調整,例如,用於multitoken語法的驗證程序,以識別「額外」參數。

我讓自己開始最酷的部分:

./a.out 1st_positional --foo yes off false yes file.txt --bar 5 -- another positional 
parsed foo values: 1, 0, 0, 1, 
parsed bar values: 5 
parsed positional values: 1st_positional, another, positional, file.txt, 

如此看來,即使有關選項非常奇怪的組合工作。它還處理:

./a.out 1st_positional --foo --bar 5 -- another positional 
./a.out 1st_positional --foo file.txt --bar 5 -- another positional 

解決方案

您可以識別的值運行command_line_parser,使用前store後手動篡改。

下面是粗草案。它在--foomultitoken選項末尾處理一個額外的令牌。它調用自定義驗證並將最後一個違規標記移動到位置參數。我在代碼之後描述的幾個警告。我故意留下了一些調試cout,所以任何人都可以輕鬆地使用它。

因此,這裏的draft

#include <vector> 
#include <boost/program_options/options_description.hpp> 
#include <boost/program_options/parsers.hpp> 
#include <boost/program_options/variables_map.hpp> 
#include <boost/program_options/positional_options.hpp> 
#include <boost/program_options/option.hpp> 
#include <algorithm> 

using namespace boost::program_options; 

#include <iostream> 
using namespace std; 

// A helper function to simplify the main part. 
template<class T> 
ostream& operator<<(ostream& os, const vector<T>& v) 
{ 
    copy(v.begin(), v.end(), ostream_iterator<T>(os, ", ")); 
    return os; 
} 

bool validate_foo(const string& s) 
{ 
    return s == "yes" || s == "no"; 
} 

int main(int ac, char* av[]) 
{ 
    try { 
     options_description desc("Allowed options"); 
     desc.add_options() 
     ("help", "produce a help message") 
     ("foo", value<std::vector<bool>>()->multitoken()->zero_tokens()) 
     ("bar", value<int>()) 
     ("positional", value<std::vector<string>>()) 
     ; 

     positional_options_description p; 
     p.add("positional", -1); 

     variables_map vm; 
     auto clp = command_line_parser(ac, av).positional(p).options(desc).run(); 

     // ---------- Crucial part ----------- 
     auto foo_itr = find_if(begin(clp.options), end(clp.options), [](const auto& opt) { return opt.string_key == string("foo"); }); 
     if (foo_itr != end(clp.options)) { 
      auto& foo_opt = *foo_itr; 

      cout << foo_opt.string_key << '\n'; 
      std::cout << "foo values: " << foo_opt.value << '\n'; 

      if (!validate_foo(foo_opt.value.back())) {          // [1] 
       auto last_value = foo_opt.value.back(); //consider std::move 
       foo_opt.value.pop_back(); 

       cout << "Last value of foo (`" << last_value << "`) seems wrong. Let's take care of it.\n"; 

       clp.options.emplace_back(string("positional"), vector<string>{last_value}); // [2] 
      } 
     } 
     // ~~~~~~~~~~ Crucial part ~~~~~~~~~~~~ 

     auto pos = find_if(begin(clp.options), end(clp.options), [](const auto& opt) { return opt.string_key == string("positional"); }); 
     if (pos != end(clp.options)) { 
      auto& pos_opt = *pos; 
      cout << "positional pos_key: " << pos_opt.position_key << '\n'; 
      cout << "positional string_key: " << pos_opt.string_key << '\n'; 
      cout << "positional values: " << pos_opt.value << '\n'; 
      cout << "positional original_tokens: " << pos_opt.original_tokens << '\n'; 
     } 

     store(clp, vm); 
     notify(vm); 

     if (vm.count("help")) { 
      cout << desc; 
     } 
     if (vm.count("foo")) { 
      cout << "parsed foo values: " 
       << vm["foo"].as<vector<bool>>() << "\n"; 
     } 
     if (vm.count("bar")) { 
      cout << "parsed bar values: " 
       << vm["bar"].as<int>() << "\n"; 
     }   
     if (vm.count("positional")) { 
      cout << "parsed positional values: " << 
       vm["positional"].as< vector<string> >() << "\n"; 
     } 
    } 
    catch(exception& e) { 
     cout << e.what() << "\n"; 
    } 
} 

所以我看到的問題是:

  1. 自定義驗證應該是相同的選項類型所使用的解析器之一。如您所見,program_optionsvalidate_foo更容許bool。你可以製作最後一個標記false,它會被錯誤地移動。我不知道如何取出圖書館使用的驗證器,因此我提供了一個粗俗的定製版本。

  2. 添加一個條目到basic_parsed_options::option是相當棘手。它基本上與對象的內部狀態混淆。正如人們所看到的,我已經制作了一個相當基本的版本,例如它複製value,但僅留下original_tokens矢量在數據結構中產生差異。其他領域也保持原樣。

  3. 如果您沒有在其他地方的命令行中記錄positional參數,可能會發生奇怪的事情。這意味着command_line_parser將在basic_parsed_options::option中創建一個條目,並且該代碼將使用相同的string_key添加另一個條目。我不確定後果,但它確實適用於我使用的奇怪例子。

解決問題1,可以讓它變成一個很好的解決方案。我猜其他的東西是用於診斷的。 (雖然不是100%!)。人們還可以通過其他方式識別違規令牌,或者在循環中識別違規令牌。

您可以刪除冒犯的令牌並將它們存儲在一邊,但將其留在boost_options仍然使用它的驗證例程,這可能很好。 (你可以嘗試改變positionalvalue<std::vector<int>>()

+0

看起來像解析結果的後處理,對吧?只是'ac' /'av'破解會更簡單嗎? – ivaigult

+0

你想如何破解它?在常規的'program_options'流程之上唯一的東西是「關鍵部分」,如果你刪除調試,你有5-10行。本質上它是黑客'argc','argv';增強標記後完成。 – luk32