Mercurial > irccd
comparison irccdctl/main.cpp @ 850:688f28dd3241
irccd: remove short options, closes #1673
While here, use a brand new home made option parsing.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 16 Jul 2019 20:49:36 +0200 |
parents | 06cc2f95f479 |
children | b92e9e438ec1 |
comparison
equal
deleted
inserted
replaced
849:64f8f82ab110 | 850:688f28dd3241 |
---|---|
67 /* | 67 /* |
68 * Configuration file parsing. | 68 * Configuration file parsing. |
69 * ------------------------------------------------------------------- | 69 * ------------------------------------------------------------------- |
70 */ | 70 */ |
71 | 71 |
72 [[noreturn]] | |
72 void usage() | 73 void usage() |
73 { | 74 { |
75 std::cerr << "usage: irccdctl plugin-config id [variable] [value]\n"; | |
76 std::cerr << " irccdctl plugin-info id\n"; | |
77 std::cerr << " irccdctl plugin-list\n"; | |
78 std::cerr << " irccdctl plugin-load name\n"; | |
79 std::cerr << " irccdctl plugin-reload plugin plugin-unload plugin\n"; | |
80 std::cerr << " irccdctl rule-add [-c channel] [-e event] [-i index] [-o origin] [-s server] accept|drop\n"; | |
81 std::cerr << " irccdctl rule-edit [-a accept|drop] [-c|C channel] [-e|E event] [-o|O origin] [-s|S server] index\n"; | |
82 std::cerr << " irccdctl rule-info index\n"; | |
83 std::cerr << " irccdctl rule-list\n"; | |
84 std::cerr << " irccdctl rule-move from to\n"; | |
85 std::cerr << " irccdctl rule-remove index\n"; | |
86 std::cerr << " irccdctl server-connect [-46s] [-n nickname] [-r realname] [-u username] [-p port] id hostname\n"; | |
87 std::cerr << " irccdctl server-disconnect [server]\n"; | |
88 std::cerr << " irccdctl server-info server\n"; | |
89 std::cerr << " irccdctl server-invite server target channel\n"; | |
90 std::cerr << " irccdctl server-join server channel [password]\n"; | |
91 std::cerr << " irccdctl server-kick server target channel [reason]\n"; | |
92 std::cerr << " irccdctl server-list\n"; | |
93 std::cerr << " irccdctl server-me server target message\n"; | |
94 std::cerr << " irccdctl server-message server target message\n"; | |
95 std::cerr << " irccdctl server-mode server target mode [limit] [user] [mask]\n"; | |
96 std::cerr << " irccdctl server-nick server nickname\n"; | |
97 std::cerr << " irccdctl server-notice server target message\n"; | |
98 std::cerr << " irccdctl server-part server channel [reason]\n"; | |
99 std::cerr << " irccdctl server-reconnect [server]\n"; | |
100 std::cerr << " irccdctl server-topic server channel topic\n"; | |
101 std::cerr << " irccdctl watch [-f native|json]\n"; | |
74 std::exit(1); | 102 std::exit(1); |
75 } | 103 } |
76 | 104 |
77 /* | 105 /* |
78 * read_connect_ip | 106 * read_connect_ip |
275 * parse_connect_ip | 303 * parse_connect_ip |
276 * ------------------------------------------------------------------ | 304 * ------------------------------------------------------------------ |
277 * | 305 * |
278 * Parse internet connection from command line. | 306 * Parse internet connection from command line. |
279 * | 307 * |
280 * -t ip | ipv6 | 308 * -h hostname or ip address |
281 * -h hostname or ip | 309 * -p port (can be a string) |
282 * -p port | 310 * -4 enable IPv4 (default) |
283 */ | 311 * -6 enable IPv6 (default) |
284 auto parse_connect_ip(std::string_view type, const option::result& options) -> std::unique_ptr<connector> | 312 */ |
285 { | 313 auto parse_connect_ip(const options::pack& result) -> std::unique_ptr<connector> |
286 option::result::const_iterator it; | 314 { |
287 | 315 const auto& [ _, options ] = result; |
288 // Host (-h or --host). | 316 const auto hostname = options.find('h'); |
289 if ((it = options.find("-h")) == options.end() && (it = options.find("--hostname")) == options.end()) | 317 const auto port = options.find('p'); |
318 | |
319 /* | |
320 * Both are to true by default, setting one disable the second unless | |
321 * it is also specified. | |
322 */ | |
323 bool ipv4 = true; | |
324 bool ipv6 = true; | |
325 | |
326 if (options.count('4')) | |
327 ipv6 = options.count('6'); | |
328 else if (options.count('6')) | |
329 ipv4 = options.count('4'); | |
330 | |
331 if (hostname == options.end() || hostname->second.empty()) | |
290 throw transport_error(transport_error::invalid_hostname); | 332 throw transport_error(transport_error::invalid_hostname); |
291 | 333 if (port == options.end() || port->second.empty()) |
292 const auto hostname = it->second; | |
293 | |
294 // Port (-p or --port). | |
295 if ((it = options.find("-p")) == options.end() && (it = options.find("--port")) == options.end()) | |
296 throw transport_error(transport_error::invalid_port); | 334 throw transport_error(transport_error::invalid_port); |
297 | 335 |
298 const auto port = it->second; | 336 return std::make_unique<ip_connector>(service, hostname->second, port->second, ipv4, ipv6); |
299 | |
300 // Type (-t or --type). | |
301 const auto ipv4 = type == "ip"; | |
302 const auto ipv6 = type == "ipv6"; | |
303 | |
304 return std::make_unique<ip_connector>(service, hostname, port, ipv4, ipv6); | |
305 } | 337 } |
306 | 338 |
307 /* | 339 /* |
308 * parse_connect_local | 340 * parse_connect_local |
309 * ------------------------------------------------------------------ | 341 * ------------------------------------------------------------------ |
310 * | 342 * |
311 * Parse local connection. | 343 * Parse local connection. |
312 * | 344 * |
313 * -P file | 345 * -P file |
314 */ | 346 */ |
315 auto parse_connect_local(const option::result& options) -> std::unique_ptr<connector> | 347 auto parse_connect_local([[maybe_unused]] const options::pack& options) -> std::unique_ptr<connector> |
316 { | 348 { |
317 #if !BOOST_OS_WINDOWS | 349 #if !BOOST_OS_WINDOWS |
318 option::result::const_iterator it; | 350 const auto& [ _, options ] = result; |
319 | 351 const auto path = options.find('P'); |
320 if ((it = options.find("-P")) == options.end() && (it = options.find("--path")) == options.end()) | 352 |
321 throw std::invalid_argument("missing path parameter (-P or --path)"); | 353 if (path == options.end() || path->second.empty()) |
354 throw transport_error(transport_error::invalid_path); | |
322 | 355 |
323 return std::make_unique<local_connector>(service, it->second); | 356 return std::make_unique<local_connector>(service, it->second); |
324 #else | 357 #else |
325 (void)options; | 358 throw transport_error(transport_error::not_supported); |
326 | |
327 throw std::invalid_argument("unix connection not supported on Windows"); | |
328 #endif | 359 #endif |
329 } | 360 } |
330 | 361 |
331 /* | 362 /* |
332 * parse_connect | 363 * parse_connect |
333 * ------------------------------------------------------------------ | 364 * ------------------------------------------------------------------ |
334 * | 365 * |
335 * Generic parsing of command line option for connection. | 366 * Generic parsing of command line option for connection. |
336 */ | 367 */ |
337 void parse_connect(const option::result& options) | 368 void parse_connect(const options::pack& options) |
338 { | 369 { |
339 assert(options.count("-t") > 0 || options.count("--type") > 0); | 370 const auto hflag = std::get<1>(options).count('h') > 0; |
340 | 371 const auto pflag = std::get<1>(options).count('P') > 0; |
341 auto it = options.find("-t"); | 372 |
342 | 373 if (hflag && pflag) |
343 if (it == options.end()) | 374 throw std::invalid_argument("-h and -P are mutually exclusive"); |
344 it = options.find("--type"); | 375 |
345 | 376 if (hflag) |
346 std::unique_ptr<connector> connector; | 377 ctl = std::make_unique<controller>(parse_connect_ip(options)); |
347 | 378 else if (pflag) |
348 if (it->second == "ip" || it->second == "ipv6") | 379 ctl = std::make_unique<controller>(parse_connect_local(options)); |
349 connector = parse_connect_ip(it->second, options); | 380 } |
350 else if (it->second == "unix") | 381 |
351 connector = parse_connect_local(options); | 382 auto parse(std::vector<std::string>& args) -> options::pack |
352 else | 383 { |
353 throw std::invalid_argument(str(format("invalid type given: %1%") % it->second)); | 384 options::pack result; |
354 | |
355 if (connector) | |
356 ctl = std::make_unique<controller>(std::move(connector)); | |
357 } | |
358 | |
359 auto parse(int& argc, char**& argv) -> option::result | |
360 { | |
361 // 1. Parse command line options. | |
362 option::options def{ | |
363 { "-c", true }, | |
364 { "--config", true }, | |
365 { "-h", true }, | |
366 { "--help", false }, | |
367 { "--hostname", true }, | |
368 { "-p", true }, | |
369 { "--port", true }, | |
370 { "-P", true }, | |
371 { "--path", true }, | |
372 { "-t", true }, | |
373 { "--type", true }, | |
374 { "-v", false }, | |
375 { "--verbose", false } | |
376 }; | |
377 | |
378 option::result result; | |
379 | 385 |
380 try { | 386 try { |
381 result = option::read(argc, argv, def); | 387 // 1. Collect the options before the command name. |
382 | 388 auto begin = args.begin(); |
383 if (result.count("--help") > 0 || result.count("-h") > 0) | 389 auto end = args.end(); |
384 usage(); | 390 |
385 // NOTREACHED | 391 result = options::parse(begin, end, "c:h:p:P:v!"); |
386 | 392 |
387 if (result.count("-v") != 0 || result.count("--verbose") != 0) | 393 for (const auto& [ opt, _ ] : std::get<1>(result)) |
388 verbose = true; | 394 if (opt == 'v') |
395 verbose = true; | |
396 | |
397 args.erase(args.begin(), begin); | |
389 } catch (const std::exception& ex) { | 398 } catch (const std::exception& ex) { |
390 std::cerr << "irccdctl: " << ex.what() << std::endl; | 399 std::cerr << "abort: " << ex.what() << std::endl; |
391 usage(); | 400 usage(); |
392 } | 401 } |
393 | 402 |
394 return result; | 403 return result; |
395 } | 404 } |
456 }); | 465 }); |
457 else | 466 else |
458 throw std::invalid_argument("no alias or command named " + name); | 467 throw std::invalid_argument("no alias or command named " + name); |
459 } | 468 } |
460 | 469 |
461 void init(int &argc, char **&argv) | 470 void init() |
462 { | 471 { |
463 sys::set_program_name("irccdctl"); | 472 sys::set_program_name("irccdctl"); |
464 | |
465 -- argc; | |
466 ++ argv; | |
467 | 473 |
468 for (const auto& f : cli::registry) { | 474 for (const auto& f : cli::registry) { |
469 auto c = f(); | 475 auto c = f(); |
470 | 476 |
471 commands.emplace(c->get_name(), std::move(c)); | 477 commands.emplace(c->get_name(), std::move(c)); |
496 | 502 |
497 service.run(); | 503 service.run(); |
498 service.reset(); | 504 service.reset(); |
499 } | 505 } |
500 | 506 |
501 void do_exec(int argc, char** argv) | 507 void do_exec(const std::vector<std::string>& args) |
502 { | 508 { |
503 std::vector<std::string> args; | |
504 | |
505 for (int i = 0; i < argc; ++i) | |
506 args.push_back(argv[i]); | |
507 | |
508 enqueue(args); | 509 enqueue(args); |
509 | 510 |
510 for (const auto& req : requests) { | 511 for (const auto& req : requests) { |
511 req(); | 512 req(); |
512 service.run(); | 513 service.run(); |
518 | 519 |
519 } // !irccd::ctl | 520 } // !irccd::ctl |
520 | 521 |
521 int main(int argc, char** argv) | 522 int main(int argc, char** argv) |
522 { | 523 { |
523 irccd::ctl::init(argc, argv); | 524 --argc; |
525 ++argv; | |
526 | |
527 // 0. Keep track of parsed arguments. | |
528 std::vector<std::string> cli(argc); | |
529 | |
530 for (int i = 0; i < argc; ++i) | |
531 cli[i] = argv[i]; | |
532 | |
533 irccd::ctl::init(); | |
524 | 534 |
525 // 1. Read command line arguments. | 535 // 1. Read command line arguments. |
526 const auto result = irccd::ctl::parse(argc, argv); | 536 const auto result = irccd::ctl::parse(cli); |
537 const auto& [ args, options ] = result; | |
527 | 538 |
528 /* | 539 /* |
529 * 2. Open optional config by command line or by searching it | 540 * 2. Open optional config by command line or by searching it |
530 * | 541 * |
531 * The connection to irccd is searched in the following order : | 542 * The connection to irccd is searched in the following order : |
533 * 1. From the command line if specified | 544 * 1. From the command line if specified |
534 * 2. From the configuration file specified by -c | 545 * 2. From the configuration file specified by -c |
535 * 3. From the configuration file searched through directories | 546 * 3. From the configuration file searched through directories |
536 */ | 547 */ |
537 try { | 548 try { |
538 if (result.count("-t") > 0 || result.count("--type") > 0) | 549 irccd::ctl::parse_connect(result); |
539 irccd::ctl::parse_connect(result); | 550 |
540 | 551 if (const auto it = options.find('c'); it != options.end()) |
541 auto it = result.find("-c"); | |
542 | |
543 if (it != result.end() || (it = result.find("--config")) != result.end()) | |
544 irccd::ctl::read(it->second); | 552 irccd::ctl::read(it->second); |
545 else { | 553 else if (const auto conf = irccd::config::search("irccdctl.conf")) |
546 if (auto conf = irccd::config::search("irccdctl.conf")) | 554 irccd::ctl::read(*conf); |
547 irccd::ctl::read(*conf); | |
548 } | |
549 } catch (const std::exception& ex) { | 555 } catch (const std::exception& ex) { |
550 std::cerr << "abort: " << ex.what() << std::endl; | 556 std::cerr << "abort: " << ex.what() << std::endl; |
551 return 1; | 557 return 1; |
552 } | 558 } |
553 | 559 |
554 if (argc <= 0) | 560 if (cli.size() <= 0) |
555 irccd::ctl::usage(); | 561 irccd::ctl::usage(); |
556 // NOTREACHED | 562 // NOTREACHED |
557 | 563 |
558 if (!irccd::ctl::ctl) { | 564 if (!irccd::ctl::ctl) { |
559 std::cerr << "abort: no connection specified" << std::endl; | 565 std::cerr << "abort: no connection specified" << std::endl; |
560 return 1; | 566 return 1; |
561 } | 567 } |
562 | 568 |
563 try { | 569 try { |
564 irccd::ctl::do_connect(); | 570 irccd::ctl::do_connect(); |
565 irccd::ctl::do_exec(argc, argv); | 571 irccd::ctl::do_exec(cli); |
566 } catch (const std::system_error& ex) { | 572 } catch (const std::system_error& ex) { |
567 std::cerr << "abort: " << ex.code().message() << std::endl; | 573 std::cerr << "abort: " << ex.code().message() << std::endl; |
568 return 1; | 574 return 1; |
569 } catch (const std::exception& ex) { | 575 } catch (const std::exception& ex) { |
570 std::cerr << "abort: " << ex.what() << std::endl; | 576 std::cerr << "abort: " << ex.what() << std::endl; |