/* * (C) 2015,2017 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #ifndef BOTAN_CLI_ARGPARSE_H_ #define BOTAN_CLI_ARGPARSE_H_ #include #include #include #include #include #include "cli_exceptions.h" namespace Botan_CLI { class Argument_Parser final { public: Argument_Parser(const std::string& spec, const std::vector& extra_flags = {}, const std::vector& extra_opts = {}); void parse_args(const std::vector& params); bool flag_set(const std::string& flag) const; bool has_arg(const std::string& opt_name) const; std::string get_arg(const std::string& option) const; std::string get_arg_or(const std::string& option, const std::string& otherwise) const; size_t get_arg_sz(const std::string& option) const; std::vector get_arg_list(const std::string& what) const; private: // set in constructor std::vector m_spec_args; std::set m_spec_flags; std::map m_spec_opts; std::string m_spec_rest; // set in parse_args() std::map m_user_args; std::set m_user_flags; std::vector m_user_rest; }; bool Argument_Parser::flag_set(const std::string& flag_name) const { return m_user_flags.count(flag_name) > 0; } bool Argument_Parser::has_arg(const std::string& opt_name) const { return m_user_args.count(opt_name) > 0; } std::string Argument_Parser::get_arg(const std::string& opt_name) const { auto i = m_user_args.find(opt_name); if(i == m_user_args.end()) { // this shouldn't occur unless you passed the wrong thing to get_arg throw CLI_Error("Unknown option " + opt_name + " used (program bug)"); } return i->second; } std::string Argument_Parser::get_arg_or(const std::string& opt_name, const std::string& otherwise) const { auto i = m_user_args.find(opt_name); if(i == m_user_args.end() || i->second.empty()) { return otherwise; } return i->second; } size_t Argument_Parser::get_arg_sz(const std::string& opt_name) const { const std::string s = get_arg(opt_name); try { return static_cast(std::stoul(s)); } catch(std::exception&) { throw CLI_Usage_Error("Invalid integer value '" + s + "' for option " + opt_name); } } std::vector Argument_Parser::get_arg_list(const std::string& what) const { if(what == m_spec_rest) return m_user_rest; return Botan::split_on(get_arg(what), ','); } void Argument_Parser::parse_args(const std::vector& params) { std::vector args; for(auto const& param : params) { if(param.find("--") == 0) { // option const auto eq = param.find('='); if(eq == std::string::npos) { const std::string opt_name = param.substr(2, std::string::npos); if(m_spec_flags.count(opt_name) == 0) { if(m_spec_opts.count(opt_name)) { throw CLI_Usage_Error("Invalid usage of option --" + opt_name + " without value"); } else { throw CLI_Usage_Error("Unknown flag --" + opt_name); } } m_user_flags.insert(opt_name); } else { const std::string opt_name = param.substr(2, eq - 2); const std::string opt_val = param.substr(eq + 1, std::string::npos); if(m_spec_opts.count(opt_name) == 0) { throw CLI_Usage_Error("Unknown option --" + opt_name); } m_user_args.insert(std::make_pair(opt_name, opt_val)); } } else { // argument args.push_back(param); } } if(flag_set("help")) return; if(args.size() < m_spec_args.size()) { // not enough arguments throw CLI_Usage_Error("Invalid argument count, got " + std::to_string(args.size()) + " expected " + std::to_string(m_spec_args.size())); } bool seen_stdin_flag = false; size_t arg_i = 0; for(auto const& arg : m_spec_args) { m_user_args.insert(std::make_pair(arg, args[arg_i])); if(args[arg_i] == "-") { if(seen_stdin_flag) { throw CLI_Usage_Error("Cannot specify '-' (stdin) more than once"); } seen_stdin_flag = true; } ++arg_i; } if(m_spec_rest.empty()) { if(arg_i != args.size()) { throw CLI_Usage_Error("Too many arguments"); } } else { m_user_rest.assign(args.begin() + arg_i, args.end()); } // Now insert any defaults for options not supplied by the user for(auto const& opt : m_spec_opts) { if(m_user_args.count(opt.first) == 0) { m_user_args.insert(opt); } } } Argument_Parser::Argument_Parser(const std::string& spec, const std::vector& extra_flags, const std::vector& extra_opts) { class CLI_Error_Invalid_Spec final : public CLI_Error { public: explicit CLI_Error_Invalid_Spec(const std::string& bad_spec) : CLI_Error("Invalid command spec '" + bad_spec + "'") {} }; const std::vector parts = Botan::split_on(spec, ' '); if(parts.size() == 0) { throw CLI_Error_Invalid_Spec(spec); } for(size_t i = 1; i != parts.size(); ++i) { const std::string s = parts[i]; if(s.empty()) // ?!? (shouldn't happen) { throw CLI_Error_Invalid_Spec(spec); } if(s.size() > 2 && s[0] == '-' && s[1] == '-') { // option or flag auto eq = s.find('='); if(eq == std::string::npos) { m_spec_flags.insert(s.substr(2, std::string::npos)); } else { m_spec_opts.insert(std::make_pair(s.substr(2, eq - 2), s.substr(eq + 1, std::string::npos))); } } else if(s[0] == '*') { // rest argument if(m_spec_rest.empty() && s.size() > 2) { m_spec_rest = s.substr(1, std::string::npos); } else { throw CLI_Error_Invalid_Spec(spec); } } else { // named argument if(!m_spec_rest.empty()) // rest arg wasn't last { throw CLI_Error_Invalid_Spec(spec); } m_spec_args.push_back(s); } } for(std::string flag : extra_flags) m_spec_flags.insert(flag); for(std::string opt : extra_opts) m_spec_opts.insert(std::make_pair(opt, "")); } } #endif