385 lines
12 KiB
C++
385 lines
12 KiB
C++
|
|
/*
|
|||
|
|
* Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved.
|
|||
|
|
*
|
|||
|
|
* This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit).
|
|||
|
|
*
|
|||
|
|
* Use of this source code is governed by MIT license that can be found in the
|
|||
|
|
* LICENSE file in the root of the source tree. All contributing project authors
|
|||
|
|
* may be found in the AUTHORS file in the root of the source tree.
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
#ifndef SRC_UTIL_CMD_H_
|
|||
|
|
#define SRC_UTIL_CMD_H_
|
|||
|
|
|
|||
|
|
#include <map>
|
|||
|
|
#include <mutex>
|
|||
|
|
#include <string>
|
|||
|
|
#include <memory>
|
|||
|
|
#include <vector>
|
|||
|
|
#include <iostream>
|
|||
|
|
#include <functional>
|
|||
|
|
#include "mini.h"
|
|||
|
|
|
|||
|
|
namespace toolkit{
|
|||
|
|
|
|||
|
|
class Option {
|
|||
|
|
public:
|
|||
|
|
using OptionHandler = std::function<bool(const std::shared_ptr<std::ostream> &stream, const std::string &arg)>;
|
|||
|
|
|
|||
|
|
enum ArgType {
|
|||
|
|
ArgNone = 0,//no_argument,
|
|||
|
|
ArgRequired = 1,//required_argument,
|
|||
|
|
ArgOptional = 2,//optional_argument
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
Option() = default;
|
|||
|
|
|
|||
|
|
Option(char short_opt, const char *long_opt, enum ArgType type, const char *default_value, bool must_exist,
|
|||
|
|
const char *des, const OptionHandler &cb) {
|
|||
|
|
_short_opt = short_opt;
|
|||
|
|
_long_opt = long_opt;
|
|||
|
|
_type = type;
|
|||
|
|
if (type != ArgNone) {
|
|||
|
|
if (default_value) {
|
|||
|
|
_default_value = std::make_shared<std::string>(default_value);
|
|||
|
|
}
|
|||
|
|
if (!_default_value && must_exist) {
|
|||
|
|
_must_exist = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
_des = des;
|
|||
|
|
_cb = cb;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool operator()(const std::shared_ptr<std::ostream> &stream, const std::string &arg) {
|
|||
|
|
return _cb ? _cb(stream, arg) : true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private:
|
|||
|
|
friend class OptionParser;
|
|||
|
|
bool _must_exist = false;
|
|||
|
|
char _short_opt;
|
|||
|
|
enum ArgType _type;
|
|||
|
|
std::string _des;
|
|||
|
|
std::string _long_opt;
|
|||
|
|
OptionHandler _cb;
|
|||
|
|
std::shared_ptr<std::string> _default_value;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
class OptionParser {
|
|||
|
|
public:
|
|||
|
|
using OptionCompleted = std::function<void(const std::shared_ptr<std::ostream> &, mINI &)>;
|
|||
|
|
|
|||
|
|
OptionParser(const OptionCompleted &cb = nullptr, bool enable_empty_args = true) {
|
|||
|
|
_on_completed = cb;
|
|||
|
|
_enable_empty_args = enable_empty_args;
|
|||
|
|
_helper = Option('h', "help", Option::ArgNone, nullptr, false, "打印此信息",
|
|||
|
|
[this](const std::shared_ptr<std::ostream> &stream,const std::string &arg)->bool {
|
|||
|
|
static const char *argsType[] = {"无参", "有参", "选参"};
|
|||
|
|
static const char *mustExist[] = {"选填", "必填"};
|
|||
|
|
static std::string defaultPrefix = "默认:";
|
|||
|
|
static std::string defaultNull = "null";
|
|||
|
|
|
|||
|
|
std::stringstream printer;
|
|||
|
|
size_t maxLen_longOpt = 0;
|
|||
|
|
auto maxLen_default = defaultNull.size();
|
|||
|
|
|
|||
|
|
for (auto &pr : _map_options) {
|
|||
|
|
auto &opt = pr.second;
|
|||
|
|
if (opt._long_opt.size() > maxLen_longOpt) {
|
|||
|
|
maxLen_longOpt = opt._long_opt.size();
|
|||
|
|
}
|
|||
|
|
if (opt._default_value) {
|
|||
|
|
if (opt._default_value->size() > maxLen_default) {
|
|||
|
|
maxLen_default = opt._default_value->size();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for (auto &pr : _map_options) {
|
|||
|
|
auto &opt = pr.second;
|
|||
|
|
//打印短参和长参名
|
|||
|
|
if (opt._short_opt) {
|
|||
|
|
printer << " -" << opt._short_opt << " --" << opt._long_opt;
|
|||
|
|
} else {
|
|||
|
|
printer << " " << " " << " --" << opt._long_opt;
|
|||
|
|
}
|
|||
|
|
for (size_t i = 0; i < maxLen_longOpt - opt._long_opt.size(); ++i) {
|
|||
|
|
printer << " ";
|
|||
|
|
}
|
|||
|
|
//打印是否有参
|
|||
|
|
printer << " " << argsType[opt._type];
|
|||
|
|
//打印默认参数
|
|||
|
|
std::string defaultValue = defaultNull;
|
|||
|
|
if (opt._default_value) {
|
|||
|
|
defaultValue = *opt._default_value;
|
|||
|
|
}
|
|||
|
|
printer << " " << defaultPrefix << defaultValue;
|
|||
|
|
for (size_t i = 0; i < maxLen_default - defaultValue.size(); ++i) {
|
|||
|
|
printer << " ";
|
|||
|
|
}
|
|||
|
|
//打印是否必填参数
|
|||
|
|
printer << " " << mustExist[opt._must_exist];
|
|||
|
|
//打印描述
|
|||
|
|
printer << " " << opt._des << std::endl;
|
|||
|
|
}
|
|||
|
|
throw std::invalid_argument(printer.str());
|
|||
|
|
});
|
|||
|
|
(*this) << _helper;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
OptionParser &operator<<(Option &&option) {
|
|||
|
|
int index = 0xFF + (int) _map_options.size();
|
|||
|
|
if (option._short_opt) {
|
|||
|
|
_map_char_index.emplace(option._short_opt, index);
|
|||
|
|
}
|
|||
|
|
_map_options.emplace(index, std::forward<Option>(option));
|
|||
|
|
return *this;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
OptionParser &operator<<(const Option &option) {
|
|||
|
|
int index = 0xFF + (int) _map_options.size();
|
|||
|
|
if (option._short_opt) {
|
|||
|
|
_map_char_index.emplace(option._short_opt, index);
|
|||
|
|
}
|
|||
|
|
_map_options.emplace(index, option);
|
|||
|
|
return *this;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void delOption(const char *key) {
|
|||
|
|
for (auto &pr : _map_options) {
|
|||
|
|
if (pr.second._long_opt == key) {
|
|||
|
|
if (pr.second._short_opt) {
|
|||
|
|
_map_char_index.erase(pr.second._short_opt);
|
|||
|
|
}
|
|||
|
|
_map_options.erase(pr.first);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void operator ()(mINI &all_args, int argc, char *argv[], const std::shared_ptr<std::ostream> &stream);
|
|||
|
|
|
|||
|
|
private:
|
|||
|
|
bool _enable_empty_args;
|
|||
|
|
Option _helper;
|
|||
|
|
std::map<char, int> _map_char_index;
|
|||
|
|
std::map<int, Option> _map_options;
|
|||
|
|
OptionCompleted _on_completed;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
class CMD : public mINI {
|
|||
|
|
public:
|
|||
|
|
virtual ~CMD() = default;
|
|||
|
|
|
|||
|
|
virtual const char *description() const {
|
|||
|
|
return "description";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void operator()(int argc, char *argv[], const std::shared_ptr<std::ostream> &stream = nullptr) {
|
|||
|
|
this->clear();
|
|||
|
|
std::shared_ptr<std::ostream> coutPtr(&std::cout, [](std::ostream *) {});
|
|||
|
|
(*_parser)(*this, argc, argv, stream ? stream : coutPtr);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool hasKey(const char *key) {
|
|||
|
|
return this->find(key) != this->end();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
std::vector<variant> splitedVal(const char *key, const char *delim = ":") {
|
|||
|
|
std::vector<variant> ret;
|
|||
|
|
auto &val = (*this)[key];
|
|||
|
|
split(val, delim, ret);
|
|||
|
|
return ret;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void delOption(const char *key) {
|
|||
|
|
if (_parser) {
|
|||
|
|
_parser->delOption(key);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected:
|
|||
|
|
std::shared_ptr<OptionParser> _parser;
|
|||
|
|
|
|||
|
|
private:
|
|||
|
|
void split(const std::string &s, const char *delim, std::vector<variant> &ret) {
|
|||
|
|
size_t last = 0;
|
|||
|
|
auto index = s.find(delim, last);
|
|||
|
|
while (index != std::string::npos) {
|
|||
|
|
if (index - last > 0) {
|
|||
|
|
ret.push_back(s.substr(last, index - last));
|
|||
|
|
}
|
|||
|
|
last = index + strlen(delim);
|
|||
|
|
index = s.find(delim, last);
|
|||
|
|
}
|
|||
|
|
if (s.size() - last > 0) {
|
|||
|
|
ret.push_back(s.substr(last));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
class CMDRegister {
|
|||
|
|
public:
|
|||
|
|
static CMDRegister &Instance();
|
|||
|
|
|
|||
|
|
void clear() {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lck(_mtx);
|
|||
|
|
_cmd_map.clear();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void registCMD(const char *name, const std::shared_ptr<CMD> &cmd) {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lck(_mtx);
|
|||
|
|
_cmd_map.emplace(name, cmd);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void unregistCMD(const char *name) {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lck(_mtx);
|
|||
|
|
_cmd_map.erase(name);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
std::shared_ptr<CMD> operator[](const char *name) {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lck(_mtx);
|
|||
|
|
auto it = _cmd_map.find(name);
|
|||
|
|
if (it == _cmd_map.end()) {
|
|||
|
|
throw std::invalid_argument(std::string("CMD not existed: ") + name);
|
|||
|
|
}
|
|||
|
|
return it->second;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void operator()(const char *name, int argc, char *argv[], const std::shared_ptr<std::ostream> &stream = nullptr) {
|
|||
|
|
auto cmd = (*this)[name];
|
|||
|
|
if (!cmd) {
|
|||
|
|
throw std::invalid_argument(std::string("CMD not existed: ") + name);
|
|||
|
|
}
|
|||
|
|
(*cmd)(argc, argv, stream);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void printHelp(const std::shared_ptr<std::ostream> &streamTmp = nullptr) {
|
|||
|
|
auto stream = streamTmp;
|
|||
|
|
if (!stream) {
|
|||
|
|
stream.reset(&std::cout, [](std::ostream *) {});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
std::lock_guard<std::recursive_mutex> lck(_mtx);
|
|||
|
|
size_t maxLen = 0;
|
|||
|
|
for (auto &pr : _cmd_map) {
|
|||
|
|
if (pr.first.size() > maxLen) {
|
|||
|
|
maxLen = pr.first.size();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for (auto &pr : _cmd_map) {
|
|||
|
|
(*stream) << " " << pr.first;
|
|||
|
|
for (size_t i = 0; i < maxLen - pr.first.size(); ++i) {
|
|||
|
|
(*stream) << " ";
|
|||
|
|
}
|
|||
|
|
(*stream) << " " << pr.second->description() << std::endl;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void operator()(const std::string &line, const std::shared_ptr<std::ostream> &stream = nullptr) {
|
|||
|
|
if (line.empty()) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
std::vector<char *> argv;
|
|||
|
|
size_t argc = getArgs((char *) line.data(), argv);
|
|||
|
|
if (argc == 0) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
std::string cmd = argv[0];
|
|||
|
|
std::lock_guard<std::recursive_mutex> lck(_mtx);
|
|||
|
|
auto it = _cmd_map.find(cmd);
|
|||
|
|
if (it == _cmd_map.end()) {
|
|||
|
|
std::stringstream ss;
|
|||
|
|
ss << " 未识别的命令\"" << cmd << "\",输入 \"help\" 获取帮助.";
|
|||
|
|
throw std::invalid_argument(ss.str());
|
|||
|
|
}
|
|||
|
|
(*it->second)((int) argc, &argv[0], stream);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private:
|
|||
|
|
size_t getArgs(char *buf, std::vector<char *> &argv) {
|
|||
|
|
size_t argc = 0;
|
|||
|
|
bool start = false;
|
|||
|
|
auto len = strlen(buf);
|
|||
|
|
for (size_t i = 0; i < len; ++i) {
|
|||
|
|
if (buf[i] != ' ' && buf[i] != '\t' && buf[i] != '\r' && buf[i] != '\n') {
|
|||
|
|
if (!start) {
|
|||
|
|
start = true;
|
|||
|
|
if (argv.size() < argc + 1) {
|
|||
|
|
argv.resize(argc + 1);
|
|||
|
|
}
|
|||
|
|
argv[argc++] = buf + i;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
buf[i] = '\0';
|
|||
|
|
start = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return argc;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private:
|
|||
|
|
std::recursive_mutex _mtx;
|
|||
|
|
std::map<std::string, std::shared_ptr<CMD> > _cmd_map;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
//帮助命令(help),该命令默认已注册
|
|||
|
|
class CMD_help : public CMD {
|
|||
|
|
public:
|
|||
|
|
CMD_help() {
|
|||
|
|
_parser = std::make_shared<OptionParser>([](const std::shared_ptr<std::ostream> &stream, mINI &) {
|
|||
|
|
CMDRegister::Instance().printHelp(stream);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const char *description() const override {
|
|||
|
|
return "打印帮助信息";
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
class ExitException : public std::exception {};
|
|||
|
|
|
|||
|
|
//退出程序命令(exit),该命令默认已注册
|
|||
|
|
class CMD_exit : public CMD {
|
|||
|
|
public:
|
|||
|
|
CMD_exit() {
|
|||
|
|
_parser = std::make_shared<OptionParser>([](const std::shared_ptr<std::ostream> &, mINI &) {
|
|||
|
|
throw ExitException();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const char *description() const override {
|
|||
|
|
return "退出shell";
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
//退出程序命令(quit),该命令默认已注册
|
|||
|
|
#define CMD_quit CMD_exit
|
|||
|
|
|
|||
|
|
//清空屏幕信息命令(clear),该命令默认已注册
|
|||
|
|
class CMD_clear : public CMD {
|
|||
|
|
public:
|
|||
|
|
CMD_clear() {
|
|||
|
|
_parser = std::make_shared<OptionParser>([this](const std::shared_ptr<std::ostream> &stream, mINI &args) {
|
|||
|
|
clear(stream);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const char *description() const {
|
|||
|
|
return "清空屏幕输出";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private:
|
|||
|
|
void clear(const std::shared_ptr<std::ostream> &stream) {
|
|||
|
|
(*stream) << "\x1b[2J\x1b[H";
|
|||
|
|
stream->flush();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
#define GET_CMD(name) (*(CMDRegister::Instance()[name]))
|
|||
|
|
#define CMD_DO(name,...) (*(CMDRegister::Instance()[name]))(__VA_ARGS__)
|
|||
|
|
#define REGIST_CMD(name) CMDRegister::Instance().registCMD(#name,std::make_shared<CMD_##name>());
|
|||
|
|
|
|||
|
|
}//namespace toolkit
|
|||
|
|
#endif /* SRC_UTIL_CMD_H_ */
|