#ifndef MEOW_USAGE_H__
#define MEOW_USAGE_H__
#include "utility.h"
#include <cstdlib>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
extern "C" {
#include <unistd.h>
}
namespace meow {
/*!
* @brief 管理參數設置, 自訂usage document, 分析argc, argv
*
* \b Usage 是用來分析argc, argv和輸出usage document的class. \n
* argc, argv的部份, 有以下規則
* - \b -c 其中 \a c 可以代換成一個字符, 這種選像可能是 \b 有設置 或 \b 沒設置
* - \b -c \a value 附加一個 \a value , 這種選項可以是 \b 選擇性 或
* \b 必要的 , 另外可以給定value的預設值以及哪些value是可接受
* - \a value 其他, 一律視為 \b process \b arguments
*
* @author cathook
*/
class Usage {
private:
typedef std::string String;
typedef std::vector<String> Strings;
//! 存 (value, description)
class Value {
private:
String value_;
String description_;
public:
Value() {
}
Value(String const& value, String const& description) {
value_ = value;
description_ = stringReplace(description, "<value>", value);
}
String usage() const {
return stringPrintf("%8s%s : %s\n",
" ", value_.c_str(), description_.c_str());
}
String value() const {
return value_;
}
bool operator==(Value const& b) const {
return (value_ == b.value_);
}
};
typedef std::vector<Value> Values;
//! 存 option, 其中可能有value可能沒有
class Option {
private:
Strings values_;
Values values_accept_;
String value_default_;
String value_type_;
String description_;
bool has_value_;
bool has_setup_;
bool must_setup_;
public:
Option() {
}
Option(String const& description) {
has_setup_ = false;
has_value_ = false;
description_ = description;
must_setup_ = false;
}
Option(String const& description,
String const& type,
String const& default_value,
bool must) {
has_setup_ = false;
has_value_ = true;
description_ = description;
value_type_ = type;
value_default_ = default_value;
must_setup_ = must;
}
Strings const& values() const {
return values_;
}
String value(size_t index) const {
if (!has_value_) return "";
if (!has_setup_ || index >= values_.size()) return value_default_;
return values_[index];
}
ssize_t valueAdd(String const& value) {
if (!has_value_) {
has_setup_ = true;
return 0;
}
if (values_accept_.size() > 0 &&
std::find(values_accept_.begin(), values_accept_.end(),
Value(value, "")) == values_accept_.end())
return -1;
values_.push_back(value);
has_setup_ = true;
return values_.size() - 1;
}
bool valueAcceptAdd(String const& value, String const& description) {
if (!has_value_) return false;
if (std::find(values_accept_.begin(), values_accept_.end(),
Value(value, "")) == values_accept_.end()){
values_accept_.push_back(Value(value, description));
}
return true;
}
bool valueAcceptChk(String const& value){
if (!has_value_) return false;
if (values_accept_.size() == 0) return true;
return (std::find(values_accept_.begin(), values_accept_.end(),
Value(value, "")) != values_accept_.end());
}
bool hasSetup() const{ return has_setup_; }
bool hasValue() const{ return has_value_; }
bool chkSetup() const{ return !(must_setup_ && !has_setup_); }
String usage(unsigned char opt, bool detail) const {
String ret(stringPrintf("-%c ", opt));
if (!detail) {
if (has_value_) ret += value_type_;
if (!must_setup_) ret = "[" + ret + "]";
} else {
if (has_value_) {
ret += value_type_ + " ";
String default_string("");
if (value_default_ != "")
default_string = "defalut='" + value_default_ + "'";
String optional_string("");
if (!must_setup_)
optional_string = "optional";
String tmp;
if (default_string.size() + optional_string.size() > 0) {
if (default_string.size() > 0 && optional_string.size() > 0) {
ret += "(" + optional_string + ", " + default_string + ")";
} else {
ret += "(" + optional_string + default_string + ")";
}
}
}
ret += "\n";
String accept_string;
for (size_t i = 0; i < values_accept_.size(); i++) {
if (i > 0)
accept_string += (i + 1 < values_accept_.size()
? ", " : " or ");
accept_string += "'" + values_accept_[i].value() + "'";
}
if (accept_string.size() == 0) accept_string = "... (anything)";
ret += " " + stringReplace(stringReplace(description_,
"<type>",
value_type_),
"<values>",
accept_string) + "\n";
for (size_t i = 0; i < values_accept_.size(); i++) {
ret += values_accept_[i].usage();
}
ret += "\n";
}
return ret;
}
};
typedef std::map<unsigned char, Option> Options;
typedef Options::const_iterator OptionsIterator;
String name_;
Options options_;
Strings usage_begin_;
Strings usage_end_;
Strings proc_arguments_;
public:
/*!
* @brief constructor
*
* 所有說明文字中 \a \<name\> 都會被代換成空字串
*/
Usage() {
}
/*!
* @brief constructor
*
* 所有說明文字中 \a "<name>" 都會被代換成空字串 \b name
*/
Usage(String const& name) {
name_ = name;
}
/*!
* @brief constructor
*
* 將另一個usage原封不動的複製過來
*/
Usage(Usage const& usage) {
name_ = usage.name_;
options_ = usage.options_;
usage_begin_ = usage.usage_begin_;
usage_end_ = usage.usage_end_;
proc_arguments_ = usage.proc_arguments_;
}
/*!
* @brief 將另一個usage的設置匯入
*
* @param [in] usage 另一個usage
* @return \c true/false 表示 \b 是否成功
*/
bool import(Usage const& usage) {
for (OptionsIterator
it = usage.options_.begin(); it != usage.options_.end(); ++it) {
if (options_.find(it->first) != options_.end())
return false;
}
for (OptionsIterator
it = usage.options_.begin(); it != usage.options_.end(); ++it) {
options_[it->first] = it->second;
}
for (size_t i = 0; i < usage.usage_begin_.size(); ++i)
usage_begin_.push_back(usage.usage_begin_[i]);
for (size_t i = 0; i < usage.usage_end_.size(); ++i)
usage_end_.push_back(usage.usage_end_[i]);
return true;
}
/*!
* @brief 將另一個usage的選項設置加進來
*
* @param [in] usage 另一個usage
* @return \c true/false 表 \b 是否成功
*/
bool update(Usage const& usage) {
for (OptionsIterator
it = usage.options_.begin(); it != usage.options_.end(); ++it) {
if (options_.find(it->first) == options_.end()) continue;
for(size_t i = 0, I = it->second.values().size(); i < I; i++){
options_[it->first].valueAdd(it->second.value(i));
}
}
return true;
}
/*!
* @brief 新增一個沒有額外選項的選項
*
* @param [in] opt 指定字符
* @param [in] des 即description, 用來解釋這個選項的意義用的
* @return \c true/false 表 \b 是否成功
*/
bool optionAdd(unsigned char opt, String const& des) {
if (options_.find(opt) != options_.end()) return false;
options_[opt] = Option(des);
return true;
}
/*!
* @brief 新增一個有額外選項的選項
*
* @param [in] opt 指定字符
* @param [in] des 即description, 用來解釋這個選項的意義用的
* @param [in] val_type 表示額外選項的型態, 寫在USAGE裡面給人看用的
* @param [in] val_default 預設值, 若為空字串則當作沒有預設值
* @param [in] must 表示是否一定要設定
* @return \c true/false 表 \b 是否成功
*/
bool optionAdd(unsigned char opt, String const& des,
String const& val_type,
String const& val_default,
bool must) {
if (options_.find(opt) != options_.end()) return false;
options_[opt] = Option(des, val_type, val_default, must);
return true;
}
/*!
* @brief 針對-(opt)新增一個可接受的額外選項
*
* @param [in] opt 指定字符
* @param [in] val 額外選項
* @param [in] des 關於此額外選項的說明
* @return \c true/false 表 \b 是否成功
*/
bool optionValueAcceptAdd(unsigned char opt,
String const& val,
String const& des) {
if (options_.find(opt) == options_.end()) return false;
return options_[opt].valueAcceptAdd(val, des);
}
/*!
* @brief 回傳是否有設定此選項
*
* @param [in] opt 指定字符
* @return \c true/false 表 \b 是否有設定此選項
*/
bool hasOptionSetup(unsigned char opt) const {
return (options_.find(opt) != options_.end() &&
options_.find(opt)->second.hasSetup());
}
/*!
* @brief 回傳參數 \b -(opt) 被設置幾次
*
* @param [in] opt 指定字符
* @return 回傳次數
*/
size_t optionValuesSize(unsigned char opt) const {
if(options_.find(opt) == options_.end()) return 0;
return options_.find(opt)->second.values().size();
}
/*!
* @brief 回傳參數 \b -(opt) 的第 \b index 個額外選項
*
* @param [in] opt 指定字符
* @param [in] index 第幾個
* @return 回傳參數 \b -(opt) 的第 \b index 個額外選項
*/
String optionValue(unsigned char opt, size_t index) const {
if (options_.find(opt) == options_.end()) {
return String();
}
return options_.find(opt)->second.value(index);
}
/*!
* @brief 取得有幾個process arguments
*
* @return 有幾個process arguments
*/
size_t procArgsSize() const {
return proc_arguments_.size();
}
/*!
* @brief 取得第i個process argument
*
* @param [in] index 第幾個
* @return 回傳第 \a index 個 \b process \b argument
*/
String procArg(size_t index) const {
if (index >= proc_arguments_.size()) {
return String();
}
return proc_arguments_[index];
}
/*!
* @brief 取得process arguments array
*
* @return 一個 \c std::vector , 包含所有 \b Process \b arguments
*/
Strings const& procArgs() const{
return proc_arguments_;
}
/*!
* @brief 新增一段usage document於每個選項逐條說明之前
*
* @param [in] des 要新增的usage document
*/
void usageBeginAdd(String const& des) {
usage_begin_.push_back(stringReplace(des, "<name>", name_));
}
/*!
* @brief 新增一段usage document於每個選項逐條說明之後
*
* @param [in] des 要新增的usage document
*/
void usageEndAdd(String const& des) {
usage_end_.push_back(stringReplace(des, "<name>", name_));
}
/*!
* @brief 回傳usage string
*
* @return \b usage \b string
*/
String usage() const{
Usage::String out = stringPrintf("USAGE\n %s", name_.c_str());
for (OptionsIterator
it = options_.begin(); it != options_.end(); ++it)
out += " " + it->second.usage(it->first, false);
out += "\n\nDESCRIPTION\n";
for (size_t i = 0; i < usage_begin_.size(); ++i) {
out += " " + usage_begin_[i] + "\n\n";
}
for (OptionsIterator
it = options_.begin(); it != options_.end(); ++it) {
out += it->second.usage(it->first, true);
}
for (size_t i = 0; i < usage_end_.size(); ++i) {
out += " " + usage_end_[i] + "\n\n";
}
return out;
}
/*!
* @brief 給定argc, argv, 將各參數設置
* @param [in] argc,argv
* @param [out] errmsg 將錯誤訊息寫到這裡
* (若給定NULL pointer, 則會把錯誤訊息忽略)
* @return \c true/false \b 成功與否 (否的話代表有錯誤的設定值在其中)
*/
bool arguments(int argc, char** argv, String* errmsg){
opterr = 0;
String s;
OptionsIterator it;
String zzz;
String& err = (errmsg == NULL ? zzz : *errmsg);
for (it = options_.begin(); it != options_.end(); ++it) {
s += (char)(it->first);
if (it->second.hasValue()) s += ":";
}
bool succ = true;
for (int opt; (opt = getopt(argc, argv, s.c_str())) != -1; ) {
if (options_.find(opt) == options_.end()) {
if(options_.find(optopt) == options_.end()){
err += stringPrintf("Unknown option '-%c'\n", optopt);
}else{
err += stringPrintf("No specify argument to '-%c'\n",
optopt);
}
succ = false;
continue;
}
if (options_[opt].valueAdd(optarg == NULL ? "" : optarg) < 0) {
err += stringPrintf("Option argument '%s' to '-%c' is not allowed\n"
, optarg, opt);
succ = false;
continue;
}
}
for (it = options_.begin(); it != options_.end(); it++) {
if (it->second.chkSetup() == false) {
err += stringPrintf("No specify argument to '-%c'\n",
it->first);
succ = false;
continue;
}
}
for (int i = optind; i < argc; i++) {
proc_arguments_.push_back(String(argv[i]));
}
return succ;
}
};
} // meow
#endif // MEOW_USAGE_H__