#ifndef MEOW_USAGE_H__ #define MEOW_USAGE_H__ #include "utility.h" #include #include #include #include #include extern "C" { #include } 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 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); } 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 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_, "", value_type_), "", 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 Options; typedef Options::const_iterator OptionsIterator; String name_; Options options_; Strings usage_begin_; Strings usage_end_; Strings proc_arguments_; public: /*! * @brief constructor * * 所有說明文字中 \a \ 都會被代換成空字串 */ Usage() { } /*! * @brief constructor * * 所有說明文字中 \a "" 都會被代換成空字串 \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_)); } /*! * @brief 新增一段usage document於每個選項逐條說明之後 * * @param [in] des 要新增的usage document */ void usageEndAdd(String const& des) { usage_end_.push_back(stringReplace(des, "", 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__