1 /* Copyright (C) 2012-2021 by László Nagy 2 This file is part of Bear. 3 4 Bear is a tool to generate compilation database for clang tooling. 5 6 Bear is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 Bear is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #include "libflags/Flags.h" 21 22 #include <cstring> 23 #include <iostream> 24 #include <optional> 25 #include <set> 26 #include <tuple> 27 #include <utility> 28 29 #include <fmt/format.h> 30 31 namespace { 32 33 constexpr char QUERY_GROUP[] = "query options"; 34 35 std::optional<std::tuple<const char**, const char**>> take(const flags::Option & option,const char ** const begin,const char ** const end)36 take(const flags::Option& option, const char** const begin, const char** const end) noexcept 37 { 38 return (option.arguments < 0) 39 ? std::optional(std::make_tuple(begin, end)) 40 : (begin + option.arguments > end) 41 ? std::nullopt 42 : std::optional(std::make_tuple(begin, begin + option.arguments)); 43 } 44 order_by_relevance(const flags::OptionMap & options,const std::optional<std::string_view> & group)45 std::list<flags::OptionValue> order_by_relevance(const flags::OptionMap& options, const std::optional<std::string_view>& group) 46 { 47 std::list<flags::OptionValue> result; 48 std::copy_if(std::begin(options), std::end(options), 49 std::back_inserter(result), 50 [&group](auto& option) { return option.second.group_name == group && option.second.arguments >= 0; }); 51 std::copy_if(std::begin(options), std::end(options), 52 std::back_inserter(result), 53 [&group](auto& option) { return option.second.group_name == group && option.second.arguments < 0; }); 54 return result; 55 } 56 group_by(const flags::OptionMap & options)57 std::list<std::list<flags::OptionValue>> group_by(const flags::OptionMap& options) 58 { 59 // find out what are the option groups. 60 std::set<std::optional<std::string_view>> groups; 61 for (const auto& [_, option] : options) { 62 groups.emplace(option.group_name); 63 } 64 std::list<std::list<flags::OptionValue>> result; 65 // insert to the result list. 66 for (auto& group : groups) { 67 result.emplace_back(order_by_relevance(options, group)); 68 } 69 return result; 70 } 71 format_parameters(const flags::Option & option)72 std::string format_parameters(const flags::Option& option) 73 { 74 switch (option.arguments) { 75 case 0: 76 return ""; 77 case 1: 78 return " <arg>"; 79 case 2: 80 return " <arg0> <arg1>>"; 81 case 3: 82 return " <arg0> <arg1> <arg2>"; 83 default: 84 return " ..."; 85 } 86 } 87 format_options(std::ostream & os,const std::list<flags::OptionValue> & options)88 void format_options(std::ostream& os, const std::list<flags::OptionValue>& options) 89 { 90 for (auto& it : options) { 91 const auto& [flag, option] = it; 92 93 const std::string parameters = format_parameters(option); 94 const std::string short_help = option.required 95 ? fmt::format(" {0}{1}", flag, parameters) 96 : fmt::format(" [{0}{1}]", flag, parameters); 97 os << short_help; 98 } 99 } 100 format_options_long(std::ostream & os,const std::list<flags::OptionValue> & main_options)101 void format_options_long(std::ostream& os, const std::list<flags::OptionValue>& main_options) 102 { 103 for (auto& it : main_options) { 104 const auto& [flag, option] = it; 105 106 const std::string flag_name = fmt::format(" {0}{1}", flag, format_parameters(option)); 107 const size_t flag_size = flag_name.length(); 108 109 // print flag name 110 os << flag_name; 111 // decide if the help text goes into the same line or not 112 if (flag_size > 22) { 113 os << std::endl 114 << std::string(15, ' '); 115 } else { 116 os << std::string(23 - flag_size, ' '); 117 } 118 os << option.help; 119 // print default value if exists 120 if (option.default_value) { 121 os << " (default: " << option.default_value.value() << ')'; 122 } 123 os << std::endl; 124 } 125 } 126 } 127 128 namespace flags { 129 Arguments(std::string_view program,Arguments::Parameters && parameters)130 Arguments::Arguments(std::string_view program, Arguments::Parameters&& parameters) 131 : program_(program) 132 , parameters_(parameters) 133 { } 134 as_bool(const std::string_view & key) const135 rust::Result<bool> Arguments::as_bool(const std::string_view& key) const 136 { 137 return rust::Ok(parameters_.find(key) != parameters_.end()); 138 } 139 as_string(const std::string_view & key) const140 rust::Result<std::string_view> Arguments::as_string(const std::string_view& key) const 141 { 142 if (auto values = parameters_.find(key); values != parameters_.end()) { 143 return (values->second.size() == 1) 144 ? (rust::Ok(values->second.front())) 145 : rust::Result<std::string_view>( 146 rust::Err(std::runtime_error( 147 fmt::format("Parameter \"{0}\" is not a single string.", key)))); 148 } 149 return rust::Result<std::string_view>( 150 rust::Err(std::runtime_error( 151 fmt::format("Parameter \"{0}\" is not available.", key)))); 152 } 153 as_string_list(const std::string_view & key) const154 rust::Result<std::vector<std::string_view>> Arguments::as_string_list(const std::string_view& key) const 155 { 156 if (auto values = parameters_.find(key); values != parameters_.end()) { 157 return rust::Ok(values->second); 158 } 159 return rust::Result<std::vector<std::string_view>>( 160 rust::Err(std::runtime_error( 161 fmt::format("Parameter \"{0}\" is not available.", key)))); 162 } 163 operator <<(std::ostream & os,const Arguments & args)164 std::ostream& operator<<(std::ostream& os, const Arguments& args) 165 { 166 os << '{'; 167 os << "program: " << args.program_ << ", arguments: ["; 168 for (auto arg_it = args.parameters_.begin(); arg_it != args.parameters_.end(); ++arg_it) { 169 if (arg_it != args.parameters_.begin()) { 170 os << ", "; 171 } 172 os << '{' << arg_it->first << ": ["; 173 for (auto param_it = arg_it->second.begin(); param_it != arg_it->second.end(); ++param_it) { 174 if (param_it != arg_it->second.begin()) { 175 os << ", "; 176 } 177 os << *param_it; 178 } 179 os << "]}"; 180 } 181 os << "]}"; 182 return os; 183 } 184 Parser(std::string_view name,std::string_view version,std::initializer_list<OptionValue> options)185 Parser::Parser(std::string_view name, std::string_view version, std::initializer_list<OptionValue> options) 186 : name_(name) 187 , version_(version) 188 , options_(options) 189 , commands_() 190 { 191 options_.insert({ VERBOSE, { 0, false, "run in verbose mode", std::nullopt, std::nullopt } }); 192 options_.insert({ HELP, { 0, false, "print help and exit", std::nullopt, { QUERY_GROUP } } }); 193 options_.insert({ VERSION, { 0, false, "print version and exit", std::nullopt, { QUERY_GROUP } } }); 194 } 195 Parser(std::string_view name,std::initializer_list<OptionValue> options)196 Parser::Parser(std::string_view name, std::initializer_list<OptionValue> options) 197 : name_(name) 198 , version_() 199 , options_(options) 200 , commands_() 201 { 202 options_.insert({ VERBOSE, { 0, false, "run in verbose mode", std::nullopt, std::nullopt } }); 203 options_.insert({ HELP, { 0, false, "print help and exit", std::nullopt, { QUERY_GROUP } } }); 204 } 205 Parser(std::string_view name,std::string_view version,std::initializer_list<Parser> commands)206 Parser::Parser(std::string_view name, std::string_view version, std::initializer_list<Parser> commands) 207 : name_(name) 208 , version_(version) 209 , options_() 210 , commands_(commands) 211 { 212 options_.insert({ HELP, { 0, false, "print help and exit", std::nullopt, { QUERY_GROUP } } }); 213 options_.insert({ VERSION, { 0, false, "print version and exit", std::nullopt, { QUERY_GROUP } } }); 214 } 215 parse(const int argc,const char ** argv) const216 rust::Result<Arguments> Parser::parse(const int argc, const char** argv) const 217 { 218 if (argc < 1 || argv == nullptr) { 219 return rust::Err(std::runtime_error("Empty argument list.")); 220 } 221 222 if (!commands_.empty() && argc >= 2) { 223 const std::string_view command = argv[1]; 224 const auto sub_command = std::find_if(commands_.begin(), commands_.end(), 225 [&command](auto candidate) { return candidate.name_ == command; }); 226 if (sub_command != commands_.end()) { 227 return sub_command->parse(argc - 1, argv + 1) 228 .map<Arguments>([&sub_command](auto arguments) { 229 arguments.parameters_[COMMAND] = {sub_command->name_}; 230 return arguments; 231 }); 232 } 233 } 234 235 std::string_view program(argv[0]); 236 Arguments::Parameters parameters; 237 238 const char** const args_end = argv + argc; 239 for (const char** args_it = ++argv; args_it != args_end;) { 240 // find which option is it. 241 if (auto option = options_.find(*args_it); option != options_.end()) { 242 // take the required number of arguments if founded. 243 if (const auto params = take(option->second, args_it + 1, args_end); params) { 244 const auto& [begin, end] = params.value(); 245 auto args = std::vector<std::string_view>(begin, end); 246 247 if (auto it = parameters.find(option->first); parameters.end() != it) { 248 std::copy(args.begin(), args.end(), std::back_inserter(it->second)); 249 } else { 250 parameters.emplace(option->first, args); 251 } 252 253 args_it = end; 254 } else { 255 return rust::Err(std::runtime_error( 256 fmt::format("Not enough parameters for: \"{0}\"", *args_it))); 257 } 258 } else { 259 return rust::Err(std::runtime_error( 260 fmt::format("Unrecognized parameter: \"{0}\"", *args_it))); 261 } 262 } 263 // add default values to the parameters as it would given by the user. 264 for (const auto& [flag, option] : options_) { 265 if (option.default_value.has_value() && parameters.find(flag) == parameters.end()) { 266 std::vector<std::string_view> args = { option.default_value.value() }; 267 parameters.emplace(flag, args); 268 } 269 } 270 // if this is not a help or version query, then validate the parameters strict. 271 if (parameters.find(HELP) == parameters.end() && parameters.find(VERSION) == parameters.end()) { 272 for (const auto& [flag, option] : options_) { 273 // check if the parameter is required, but not present. 274 if (option.required && parameters.find(flag) == parameters.end()) { 275 return rust::Err(std::runtime_error( 276 fmt::format("Parameter is required, but not given: \"{0}\"", flag))); 277 } 278 } 279 } 280 return rust::Ok(Arguments(program, std::move(parameters))); 281 } 282 parse_or_exit(int argc,const char ** argv) const283 rust::Result<Arguments> Parser::parse_or_exit(int argc, const char** argv) const 284 { 285 auto sub_command = [this](const std::string_view &name) -> const Parser * { 286 const auto it = std::find_if(commands_.begin(), commands_.end(), 287 [&name](auto command) { return command.name_ == name; }); 288 return (it != commands_.end()) ? &(*it) : nullptr; 289 }; 290 291 return parse(argc, argv) 292 // print error if anything bad happens. 293 .on_error([this](auto error) { 294 std::cerr << error.what() << std::endl; 295 print_usage(nullptr, std::cerr); 296 exit(EXIT_FAILURE); 297 }) 298 // if parsing success, check for the `--help` and `--version` flags 299 .on_success([this, &sub_command](auto args) { 300 // print version message and exit zero 301 if (args.as_bool(VERSION).unwrap_or(false)) { 302 print_version(std::cout); 303 exit(EXIT_SUCCESS); 304 } 305 // print help message and exit zero 306 if (args.as_bool(HELP).unwrap_or(false)) { 307 if (const auto command = args.as_string(COMMAND); command.is_ok()) { 308 print_help(sub_command(command.unwrap()), std::cout); 309 } else { 310 print_help(nullptr, std::cout); 311 } 312 exit(EXIT_SUCCESS); 313 } 314 }); 315 } 316 print_help(const Parser * sub_command,std::ostream & os) const317 void Parser::print_help(const Parser *sub_command, std::ostream& os) const { 318 print_usage(sub_command, os); 319 320 const Parser &parser = (sub_command != nullptr) ? *sub_command : *this; 321 // print commands if exists. 322 if (!parser.commands_.empty()) { 323 os << std::endl << "commands" << std::endl; 324 for (const auto& command : parser.commands_) { 325 os << " " << command.name_ << std::endl; 326 } 327 } 328 // print options 329 const std::list<std::list<flags::OptionValue>> options = group_by(parser.options_); 330 for (const auto& group : options) { 331 os << std::endl; 332 if (auto group_name = group.front().second.group_name; group_name) { 333 os << group_name.value() << std::endl; 334 } 335 format_options_long(os, group); 336 } 337 } 338 print_usage(const Parser * sub_command,std::ostream & os) const339 void Parser::print_usage(const Parser *sub_command, std::ostream& os) const { 340 os << "Usage: " << name_; 341 // check for the given command 342 if (sub_command != nullptr) { 343 os << " " << sub_command->name_; 344 const auto options = order_by_relevance(sub_command->options_, std::nullopt); 345 format_options(os, options); 346 } else { 347 if (!commands_.empty()) { 348 os << " <command>"; 349 } 350 const auto options = order_by_relevance(options_, std::nullopt); 351 format_options(os, options); 352 } 353 os << std::endl; 354 } 355 print_version(std::ostream & os) const356 void Parser::print_version(std::ostream& os) const 357 { 358 os << name_ << " " << version_ << std::endl; 359 } 360 } 361