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;