准备知识:boost 的 命令行参数解析配置选项 program_options
看代码,习惯性先将程序运行起来,然后根据文档操作,看看功能。有了感官上的认识后,再思考深层次的问题。
经常问自己一个这样的问题:程序如何实现这样的功能的?
cleos是 eos的命令行工具,我们可以通过这个工具查看、操作区块链内的信息。eos不同与bitcoin的rpc。
准备工作
先将节点程序运行起来
cd eos/programs/nodeos
./nodeos -e -p eosio --plugin eosio::chain_api_plugin --plugin eosio::history_api_plugin
参考qt调试eos 将eos/programs/cleos/cleos 作为调试程序 get info 作为参数
代码解析
proprams/cleos/CLI11.hpp
CLI 命令空间
重点是App,Option两个类, 三个步骤 1.构建一颗命令树; 2.解析用户输入命令,标示在树上; 3.用http调用节点。
CLI::App app{"Command Line Interface to EOSIO Client"}; 1.创建对象
app.require_subcommand();
// Get subcommand, 2.命令
auto get = app.add_subcommand("get", localized("Retrieve various items and information from the blockchain"), false);
get->require_subcommand();
// get account 3.子命令
string accountName;
bool print_json;
auto getAccount = get->add_subcommand("account", localized("Retrieve an account from the blockchain"), false);
getAccount->add_option("name", accountName, localized("The name of the account to retrieve"))->required(); 4.选项
getAccount->add_flag("--json,-j", print_json, localized("Output in JSON format") );5.标示
getAccount->set_callback([&]() { get_account(accountName, print_json); });
选择树枝,树叶,形成一个路径。这个路径就是一条命令 如 cleos get info 获取区块链信息。
类App,命令
class App final {
friend Option;
friend detail::AppFriend;
protected:
// This library follows the Google style guide for member names ending in underscores
这个库遵循谷歌风格的成员名字,以下划线结尾。
/// @name Basics
///@{
/// Subcommand name or program name (from parser) 命令名字
std::string name_{"program"};
/// Description of the current program/subcommand
std::string description_;
/// If true, allow extra arguments (ie, don't throw an error). 是否允许额外的参数
bool allow_extras_{false};
/// If true, return immediatly on an unrecognised option (implies allow_extras)
bool prefix_command_{false};
/// This is a function that runs when complete. Great for subcommands. Can throw. 回调
std::function<void()> callback_;
///@}
/// @name Options
///@{
/// The list of options, stored locally 选项列表
std::vector<Option_p> options_;
/// A pointer to the help flag if there is one 帮助
Option *help_ptr_{nullptr};
///@}
/// @name Parsing
///@{
using missing_t = std::vector<std::pair<detail::Classifer, std::string>>;
/// Pair of classifier, string for missing options. (extra detail is removed on returning from parse)
///
/// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator.
missing_t missing_;
/// This is a list of pointers to options with the orignal parse order
std::vector<Option *> parse_order_;
///@}
/// @name Subcommands 子命令
///@{
/// Storage for subcommand list
std::vector<App_p> subcommands_; 存放子命令
/// If true, the program name is not case sensitive
bool ignore_case_{false};
/// Allow subcommand fallthrough, so that parent commands can collect commands after subcommand.
bool fallthrough_{false};
/// A pointer to the parent if this is a subcommand
App *parent_{nullptr};
/// True if this command/subcommand was parsed 这个很重要,表示当前命令里,有这个命令。
bool parsed_{false};
/// -1 for 1 or more, 0 for not required, # for exact number required 是否需要子命令
int require_subcommand_ = 0;
///@}
/// @name Config 配置
///@{
/// The name of the connected config file
std::string config_name_;
/// True if ini is required (throws if not present), if false simply keep going.
bool config_required_{false};
/// Pointer to the config option
Option *config_ptr_{nullptr};
这个很重要,表示当前命令里,有这个命令。
bool parsed_{false};
/// -1 for 1 or more, 0 for not required, # for exact number required 是否需要子命令
int require_subcommand_ = 0;
///@}
/// @name Config 配置
///@{
/// The name of the connected config file
std::string config_name_;
/// True if ini is required (throws if not present), if false simply keep going.
bool config_required_{false};
/// Pointer to the config option
Option *config_ptr_{nullptr};
Option类,命令的选项
class Option {
friend App;
protected:
/// @name Names
///@{
/// A list of the short names (`-a`) without the leading dashes 短名称
std::vector<std::string> snames_;
/// A list of the long names (`--a`) without the leading dashes 长名称
std::vector<std::string> lnames_;
/// A positional name
std::string pname_;
/// If given, check the environment for this option 检查环境
std::string envname_;
///@}
/// @name Help
///@{
/// The description for help strings
std::string description_;
/// A human readable default value, usually only set if default is true in creation 默认值
std::string defaultval_;
/// A human readable type value, set when App creates this
std::string typeval_;
/// The group membership
std::string group_{"Options"};
/// True if this option has a default 是否需要默认值
bool default_{false};
///@}
/// @name Configuration 配置
///@{
/// True if this is a required option 是否必须
bool required_{false};
/// The number of expected values, 0 for flag, -1 for unlimited vector
int expected_{1};
/// A private setting to allow args to not be able to accept incorrect expected values
bool changeable_{false};
/// Ignore the case when matching (option, not value)
bool ignore_case_{false};
/// A list of validators to run on each value parsed
std::vector<std::function<bool(std::string)>> validators_;
/// A list of options that are required with this option
std::set<Option *> requires_;
/// A list of options that are excluded with this option
std::set<Option *> excludes_;
///@}
/// @name Other
///@{
/// Remember the parent app
App *parent_;
/// Options store a callback to do all the work
callback_t callback_; 回掉
///@}
/// @name Parsing results
///@{
/// Results of parsing
results_t results_;
/// Whether the callback has run (needed for INI parsing)
bool callback_run_{false};
/eos/program/cleos/main.cpp 的main函数
// Get subcommand
auto get = app.add_subcommand("get", localized("Retrieve various items and information from the blockchain"), false);
get->require_subcommand();需要子命令
// get info
get->add_subcommand("info", localized("Get current blockchain information"))->set_callback([] {
std::cout << fc::json::to_pretty_string(get_info()) << std::endl;
}); 命令名字,说明,回调函数
get命令,子命令info,回调函数,set_callback
// get account
string accountName;
bool print_json;
auto getAccount = get->add_subcommand("account", localized("Retrieve an account from the blockchain"), false);
getAccount->add_option("name", accountName, localized("The name of the account to retrieve"))->required(); 必须的选项
getAccount->add_flag("--json,-j", print_json, localized("Output in JSON format") );
getAccount->set_callback([&]() { get_account(accountName, print_json); });
get命令 ,子命令account, 必选项name, 标示flag,回调set_callback
/// Add option for flag
Option *add_flag(std::string name, std::string description = "") {
CLI::callback_t fun = [](CLI::results_t) { return true; };
Option *opt = add_option(name, fun, description, false); 由此看来,flag是通过option实现的
if(opt->get_positional())
throw IncorrectConstruction("Flags cannot be positional");
opt->set_custom_option("", 0);
return opt;
}
flag通过Option实现
main函数前面都在构建App命令树,最后这几行代码开始解析用户输入的命令。
try {
app.parse(argc, argv); 把输入的参数传入,如: cleos get info 命令, argc为3,argv中存储了 cleos,get,info
} catch (const CLI::ParseError &e) {
return app.exit(e);
跟踪进去后 _parse,_parse_single,_parse_subcommand,嵌套循环调用解析所有子命令。
/// Parses the command line - throws errors
/// This must be called after the options are in but before the rest of the program. 倒序
std::vector<std::string> parse(int argc, char **argv) {
name_ = argv[0];
std::vector<std::string> args;
for(int i = argc - 1; i > 0; i--){
args.emplace_back(argv[i]);
}
return parse(args);
}
/// The real work is done here. Expects a reversed vector.
/// Changes the vector to the remaining options.
std::vector<std::string> &parse(std::vector<std::string> &args) {
_validate();
_parse(args);
run_callback();调用回调函数
return args;
}
void _parse(std::vector<std::string> &args) {
parsed_ = true; 标记这个命令用户输入了
bool positional_only = false;
while(!args.empty()) {
_parse_single(args, positional_only);
}
_parse_single,命令类型判断
/// Parse "one" argument (some may eat more than one), delegate to parent if fails, add to missing if missing from
/// master
void _parse_single(std::vector<std::string> &args, bool &positional_only) {
detail::Classifer classifer = positional_only ? detail::Classifer::NONE : _recognize(args.back());
switch(classifer) {
case detail::Classifer::POSITIONAL_MARK:
missing()->emplace_back(classifer, args.back());
args.pop_back();
positional_only = true;
break;
case detail::Classifer::SUBCOMMAND:
_parse_subcommand(args);子命令
break;
解析子命令
void _parse_subcommand(std::vector<std::string> &args) {
if(_count_remaining_required_positionals() > 0)
return _parse_positional(args);
for(const App_p &com : subcommands_) {
if(com->check_name(args.back())) {
args.pop_back();
com->_parse(args);调用_parse循环解析
return;
}
}
if(parent_ != nullptr)
return parent_->_parse_subcommand(args);
else
throw HorribleError("Subcommand " + args.back() + " missing");
}
在&parse里面有一行 run_callback(); 调用回调函数。在解析完命令后执行。
/// Internal function to run (App) callback, top down
void run_callback() {
pre_callback();
if(callback_)
callback_();
for(App *subc : get_subcommands()) {
subc->run_callback();执行回调函数
}
}
执行回调函数,如执行的是 cleos get info ,在下面的std::cout 打断点。当执行回调函数时程序执行这行代码。
// get info
get->add_subcommand("info", localized("Get current blockchain information"))->set_callback([] {
std::cout << fc::json::to_pretty_string(get_info()) << std::endl;
});
App的父子关系,qt调试时截图,name_ 为 get , 子命令info 在 subcommands_ 中