欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

eos代码阅读笔记02- 命令行工具cleos

程序员文章站 2022-07-14 19:20:04
...

准备知识: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); });

eos代码阅读笔记02- 命令行工具cleos

选择树枝,树叶,形成一个路径。这个路径就是一条命令 如  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_ 中

eos代码阅读笔记02- 命令行工具cleos