2
* (C) 2015,2017 Jack Lloyd
4
* Botan is released under the Simplified BSD License (see license.txt)
7
#ifndef BOTAN_ARGPARSE_H_
8
#define BOTAN_ARGPARSE_H_
14
#include <botan/parsing.h>
15
#include "cli_exceptions.h"
22
Argument_Parser(const std::string& spec,
23
const std::vector<std::string>& extra_flags = {},
24
const std::vector<std::string>& extra_opts = {});
26
void parse_args(const std::vector<std::string>& params);
28
bool flag_set(const std::string& flag) const;
30
bool has_arg(const std::string& opt_name) const;
31
std::string get_arg(const std::string& option) const;
33
std::string get_arg_or(const std::string& option, const std::string& otherwise) const;
35
size_t get_arg_sz(const std::string& option) const;
37
std::vector<std::string> get_arg_list(const std::string& what) const;
41
std::vector<std::string> m_spec_args;
42
std::set<std::string> m_spec_flags;
43
std::map<std::string, std::string> m_spec_opts;
44
std::string m_spec_rest;
46
// set in parse_args()
47
std::map<std::string, std::string> m_user_args;
48
std::set<std::string> m_user_flags;
49
std::vector<std::string> m_user_rest;
52
bool Argument_Parser::flag_set(const std::string& flag_name) const
54
return m_user_flags.count(flag_name) > 0;
57
bool Argument_Parser::has_arg(const std::string& opt_name) const
59
return m_user_args.count(opt_name) > 0;
62
std::string Argument_Parser::get_arg(const std::string& opt_name) const
64
auto i = m_user_args.find(opt_name);
65
if(i == m_user_args.end())
67
// this shouldn't occur unless you passed the wrong thing to get_arg
68
throw CLI_Error("Unknown option " + opt_name + " used (program bug)");
73
std::string Argument_Parser::get_arg_or(const std::string& opt_name, const std::string& otherwise) const
75
auto i = m_user_args.find(opt_name);
76
if(i == m_user_args.end() || i->second.empty())
83
size_t Argument_Parser::get_arg_sz(const std::string& opt_name) const
85
const std::string s = get_arg(opt_name);
89
return static_cast<size_t>(std::stoul(s));
91
catch(std::exception&)
93
throw CLI_Usage_Error("Invalid integer value '" + s + "' for option " + opt_name);
97
std::vector<std::string> Argument_Parser::get_arg_list(const std::string& what) const
99
if(what != m_spec_rest)
101
throw CLI_Error("Unexpected list name '" + what + "'");
106
void Argument_Parser::parse_args(const std::vector<std::string>& params)
108
std::vector<std::string> args;
109
for(auto const& param : params)
111
if(param.find("--") == 0)
114
const auto eq = param.find('=');
116
if(eq == std::string::npos)
118
const std::string opt_name = param.substr(2, std::string::npos);
120
if(m_spec_flags.count(opt_name) == 0)
122
if(m_spec_opts.count(opt_name))
124
throw CLI_Usage_Error("Invalid usage of option --" + opt_name +
129
throw CLI_Usage_Error("Unknown flag --" + opt_name);
132
m_user_flags.insert(opt_name);
136
const std::string opt_name = param.substr(2, eq - 2);
137
const std::string opt_val = param.substr(eq + 1, std::string::npos);
139
if(m_spec_opts.count(opt_name) == 0)
141
throw CLI_Usage_Error("Unknown option --" + opt_name);
144
m_user_args.insert(std::make_pair(opt_name, opt_val));
150
args.push_back(param);
154
bool seen_stdin_flag = false;
156
for(auto const& arg : m_spec_args)
158
if(arg_i >= args.size())
160
// not enough arguments
161
throw CLI_Usage_Error("Invalid argument count, got " +
162
std::to_string(args.size()) +
164
std::to_string(m_spec_args.size()));
167
m_user_args.insert(std::make_pair(arg, args[arg_i]));
169
if(args[arg_i] == "-")
173
throw CLI_Usage_Error("Cannot specify '-' (stdin) more than once");
175
seen_stdin_flag = true;
181
if(m_spec_rest.empty())
183
if(arg_i != args.size())
185
throw CLI_Usage_Error("Too many arguments");
190
m_user_rest.assign(args.begin() + arg_i, args.end());
193
// Now insert any defaults for options not supplied by the user
194
for(auto const& opt : m_spec_opts)
196
if(m_user_args.count(opt.first) == 0)
198
m_user_args.insert(opt);
203
Argument_Parser::Argument_Parser(const std::string& spec,
204
const std::vector<std::string>& extra_flags,
205
const std::vector<std::string>& extra_opts)
207
class CLI_Error_Invalid_Spec : public CLI_Error
210
explicit CLI_Error_Invalid_Spec(const std::string& bad_spec)
211
: CLI_Error("Invalid command spec '" + bad_spec + "'") {}
214
const std::vector<std::string> parts = Botan::split_on(spec, ' ');
216
if(parts.size() == 0)
218
throw CLI_Error_Invalid_Spec(spec);
221
for(size_t i = 1; i != parts.size(); ++i)
223
const std::string s = parts[i];
225
if(s.empty()) // ?!? (shouldn't happen)
227
throw CLI_Error_Invalid_Spec(spec);
230
if(s.size() > 2 && s[0] == '-' && s[1] == '-')
234
auto eq = s.find('=');
236
if(eq == std::string::npos)
238
m_spec_flags.insert(s.substr(2, std::string::npos));
242
m_spec_opts.insert(std::make_pair(s.substr(2, eq - 2), s.substr(eq + 1, std::string::npos)));
248
if(m_spec_rest.empty() && s.size() > 2)
250
m_spec_rest = s.substr(1, std::string::npos);
254
throw CLI_Error_Invalid_Spec(spec);
260
if(!m_spec_rest.empty()) // rest arg wasn't last
262
throw CLI_Error_Invalid_Spec(spec);
265
m_spec_args.push_back(s);
269
for(std::string flag : extra_flags)
270
m_spec_flags.insert(flag);
271
for(std::string opt : extra_opts)
272
m_spec_opts.insert(std::make_pair(opt, ""));