A tool for adding anime to your anidb list.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

769 lines
23KB

  1. #include <stddef.h>
  2. #include <inttypes.h>
  3. #include <getopt.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <stdbool.h>
  8. #include <sys/types.h>
  9. #include <sys/stat.h>
  10. #include <unistd.h>
  11. #include <errno.h>
  12. #include <assert.h>
  13. //#include <toml.h>
  14. #include <limits.h>
  15. #include <time.h>
  16. #include <unistd.h>
  17. #include <ctype.h>
  18. #include "config.h"
  19. #include "error.h"
  20. #include "util.h"
  21. #include "uio.h"
  22. static int show_help(struct conf_entry *ce);
  23. static int show_subcomm_help(struct conf_entry *ce);
  24. static int config_parse_file();
  25. static enum error config_required_check();
  26. static int config_set_str(struct conf_entry *ce, char *arg);
  27. static int config_set_port(struct conf_entry *ce, char *arg);
  28. static int config_set_bool(struct conf_entry *ce, char *arg);
  29. //static int config_def_cachedb(struct conf_entry *ce);
  30. //static int config_action_write_config(struct conf_entry *ce);
  31. /* Everything not explicitly defined, is 0 */
  32. /* If an option only has a long name, the short name also has to be
  33. * defined. For example, a number larger than UCHAR_MAX */
  34. #define SUBCOMM_HELP { \
  35. .l_name = "help", .s_name = 'h', .has_arg = no_argument, \
  36. .action_func = show_subcomm_help, .in_args = true, \
  37. .type = OTYPE_ACTION, .handle_order = 0, \
  38. .h_desc = "Display the help for the subcommand and exit", }
  39. static struct conf_entry subcomm_def_help_opt = SUBCOMM_HELP;
  40. static struct conf_entry ed2k_subopts[] = {
  41. SUBCOMM_HELP,
  42. { .l_name = "link", .s_name = 'l', .has_arg = no_argument,
  43. .set_func = config_set_bool, .in_args = true,
  44. .type = OTYPE_B, .handle_order = 0, .value_is_set = true,
  45. .h_desc = "Print an ed2k link for the files", },
  46. };
  47. static struct conf_entry modify_add_subopts[] = {
  48. SUBCOMM_HELP,
  49. { .l_name = "watched", .s_name = 'w', .has_arg = no_argument,
  50. .set_func = config_set_bool, .in_args = true,
  51. .type = OTYPE_B, .handle_order = 0, .value_is_set = true,
  52. .h_desc = "Mark the episode as watched when adding files", },
  53. { .l_name = "wdate", .s_name = UCHAR_MAX + 4, .has_arg = required_argument,
  54. .set_func = config_set_str, .in_args = true,
  55. .type = OTYPE_S, .handle_order = 0,
  56. .h_desc = "Set the watched date when adding files", },
  57. };
  58. static struct conf_entry options[] = {
  59. { .l_name = "help", .s_name = 'h', .has_arg = no_argument,
  60. .action_func = show_help, .in_args = true,
  61. .type = OTYPE_ACTION, .handle_order = 0,
  62. .h_desc = "Display the help and exit", },
  63. { .l_name = "username", .s_name = 'u', .has_arg = required_argument,
  64. .set_func = config_set_str, .in_args = true, .in_file = true,
  65. .type = OTYPE_S, .handle_order = 1,
  66. .h_desc = "Sets the username for the login", },
  67. { .l_name = "password", .s_name = 'p', .has_arg = required_argument,
  68. .set_func = config_set_str, .in_args = true, .in_file = true,
  69. .type = OTYPE_S, .handle_order = 1,
  70. .h_desc = "Sets the password for the login", },
  71. { .l_name = "port", .s_name = 'P', .has_arg = required_argument,
  72. .set_func = config_set_port, .in_args = true, .in_file = true,
  73. .type = OTYPE_HU, .handle_order = 1, .value.hu = 29937,
  74. .value_is_set = true,
  75. .h_desc = "Sets port to use for API server communication", },
  76. { .l_name = "api-server", .s_name = UCHAR_MAX + 1, .has_arg = required_argument,
  77. .set_func = config_set_str, .in_args = true, .in_file = true,
  78. .type = OTYPE_S, .handle_order = 1, .value.s = "api.anidb.net:9000",
  79. .value_is_set = true,
  80. .h_desc = "Sets the API server address", },
  81. { .l_name = "api-key", .s_name = 'k', .has_arg = required_argument,
  82. .set_func = config_set_str, .in_args = true, .in_file = true,
  83. .type = OTYPE_S, .handle_order = 1,
  84. .h_desc = "Sets the api key used for encryption", },
  85. { .l_name = "save-session", .s_name = 's', .has_arg = no_argument,
  86. .set_func = config_set_bool, .in_args = true, .in_file = true,
  87. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  88. .h_desc = "not implemented", },
  89. { .l_name = "destroy-session", .s_name = 'S', .has_arg = no_argument,
  90. .set_func = config_set_bool, .in_args = true, .in_file = false,
  91. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  92. .h_desc = "not implemented", },
  93. { .l_name = "cachedb", .s_name = 'd', .has_arg = required_argument,
  94. .set_func = config_set_str, .in_args = true, .in_file = true,
  95. .type = OTYPE_S, .handle_order = 1, /*.default_func = config_def_cachedb*/
  96. .h_desc = "Sets the path for the cache database", },
  97. { .l_name = "debug", .s_name = 'D', .has_arg = no_argument,
  98. .set_func = config_set_bool, .in_args = true, .in_file = true,
  99. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  100. .h_desc = "Enable debug output", },
  101. /*### cmds ###*/
  102. { .l_name = "server-version", .s_name = UCHAR_MAX + 2,
  103. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  104. .type = OTYPE_SUBCOMMAND, .handle_order = 1, .value_is_set = true,
  105. .h_desc = "Request the server version",
  106. },
  107. { .l_name = "version", .s_name = 'v',
  108. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  109. .type = OTYPE_SUBCOMMAND, .handle_order = 1, .value_is_set = true,
  110. .h_desc = "Print the caniadd version", },
  111. { .l_name = "uptime", .s_name = UCHAR_MAX + 3,
  112. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  113. .type = OTYPE_SUBCOMMAND, .handle_order = 1, .value_is_set = true,
  114. .h_desc = "Request the uptime of the api servers", },
  115. { .l_name = "ed2k", .s_name = 'e',
  116. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  117. .type = OTYPE_SUBCOMMAND, .handle_order = 1,
  118. .h_desc = "Run an ed2k hash on the file arguments",
  119. .subopts = ed2k_subopts,
  120. .subopts_count = sizeof(ed2k_subopts) / sizeof(ed2k_subopts[0]),
  121. },
  122. { .l_name = "add", .s_name = 'a',
  123. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  124. .type = OTYPE_SUBCOMMAND, .handle_order = 1,
  125. .h_desc = "Add files to your anidb list",
  126. .subopts = modify_add_subopts,
  127. .subopts_count = sizeof(modify_add_subopts) / sizeof(modify_add_subopts[0]),
  128. },
  129. /* Arguments are either mylist id's, or file sizes and names
  130. * in the format '[watch_date/]<size>/<filename>'. The filename can't contain
  131. * '/' characters. */
  132. { .l_name = "modify", .s_name = 'W',
  133. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  134. .type = OTYPE_SUBCOMMAND, .handle_order = 1,
  135. .h_desc = "Modify files in your anidb list",
  136. .subopts = modify_add_subopts,
  137. .subopts_count = sizeof(modify_add_subopts) / sizeof(modify_add_subopts[0]),
  138. },
  139. /*{ .l_name = "stats", .s_name = UCHAR_MAX + 5,
  140. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  141. .type = OTYPE_B, .handle_order = 1, .value_is_set = true },*/
  142. };
  143. static const size_t options_count = sizeof(options) / sizeof(options[0]);
  144. /* Used in show_subcomm_help to output info about the subcommand */
  145. static struct conf_entry *current_subcommand = NULL;
  146. static char **opt_argv = NULL;
  147. static int opt_argc = 0;
  148. static void config_build_getopt_args(int opts_count, const struct conf_entry opts[opts_count],
  149. char out_sopt[opts_count * 2 + 1], struct option out_lopt[opts_count + 1],
  150. bool stop_at_1st_nonopt)
  151. {
  152. int i_sopt = 0, i_lopt = 0;
  153. if (stop_at_1st_nonopt) {
  154. /* Tell getopts to stop at the 1st non-option argument */
  155. out_sopt[i_sopt++] = '+';
  156. }
  157. for (int i = 0; i < opts_count; i++) {
  158. if (opts[i].type == OTYPE_SUBCOMMAND)
  159. continue;
  160. /* Short options */
  161. if (opts[i].s_name && opts[i].s_name <= UCHAR_MAX) {
  162. out_sopt[i_sopt++] = opts[i].s_name;
  163. if (opts[i].has_arg == required_argument)
  164. out_sopt[i_sopt++] = ':';
  165. assert(opts[i].has_arg == required_argument ||
  166. opts[i].has_arg == no_argument);
  167. }
  168. /* Long opts */
  169. if (opts[i].l_name) {
  170. assert(opts[i].s_name);
  171. out_lopt[i_lopt].name = opts[i].l_name;
  172. out_lopt[i_lopt].has_arg = opts[i].has_arg;
  173. out_lopt[i_lopt].flag = NULL;
  174. out_lopt[i_lopt].val = opts[i].s_name;
  175. i_lopt++;
  176. }
  177. }
  178. out_sopt[i_sopt] = '\0';
  179. memset(&out_lopt[i_lopt], 0, sizeof(struct option));
  180. }
  181. static int config_read_args(int argc, char **argv,
  182. int opts_count, struct conf_entry opts[opts_count],
  183. char sopt[opts_count * 2 + 1], struct option lopt[opts_count + 1], int level)
  184. {
  185. int optc, err = NOERR;
  186. optind = 1;
  187. //uio_debug("Before %d %s", argc, argv[0]);
  188. while ((optc = getopt_long(argc, argv, sopt,
  189. lopt, NULL)) >= 0) {
  190. bool handled = false;
  191. //uio_debug("Optc: %c", optc);
  192. for (int i = 0; i < opts_count; i++) {
  193. if (opts[i].handle_order != level) {
  194. /* Lie a lil :x */
  195. handled = true;
  196. continue;
  197. }
  198. if (optc == opts[i].s_name) {
  199. if (opts[i].type != OTYPE_ACTION)
  200. err = opts[i].set_func(&opts[i], optarg);
  201. else
  202. err = opts[i].action_func(&opts[i]);
  203. if (err != NOERR)
  204. goto end;
  205. opts[i].value_is_set = true;
  206. handled = true;
  207. break;
  208. }
  209. }
  210. if (handled)
  211. continue;
  212. if (optc == '?') {
  213. err = ERR_OPT_FAILED;
  214. goto end;
  215. } else {
  216. fprintf(stderr, "Unhandled option? '%c'\n", optc);
  217. err = ERR_OPT_UNHANDLED;
  218. goto end;
  219. }
  220. }
  221. end:
  222. return err;
  223. }
  224. static enum error config_required_check()
  225. {
  226. enum error err = NOERR;
  227. for (int i = 0; i < options_count; i++) {
  228. if (options[i].required && !options[i].value_is_set) {
  229. printf("Argument %s is required!\n", options[i].l_name);
  230. err = ERR_OPT_REQUIRED;
  231. }
  232. }
  233. return err;
  234. }
  235. static enum error config_parse_subcomm_subopts(struct conf_entry *subcomm)
  236. {
  237. char sopt[64];
  238. struct option lopt[32];
  239. enum error err;
  240. /* Set the global current subcommand pointer */
  241. current_subcommand = subcomm;
  242. config_build_getopt_args(subcomm->subopts_count, subcomm->subopts, sopt, lopt, false);
  243. //uio_debug("Parsing subconn %s", subcomm->l_name);
  244. //uio_debug("sopt: %s", sopt);
  245. /* Update args for next parsing and nonopts parsing for later */
  246. opt_argv = &opt_argv[optind];
  247. opt_argc = config_get_nonopt_count();
  248. /* argv[0] (which is the subcommand string) will be treated as the filename here, and skipped */
  249. err = config_read_args(opt_argc, opt_argv,
  250. subcomm->subopts_count, subcomm->subopts, sopt, lopt, 0);
  251. if (err == NOERR) {
  252. /* Mark subcommand as set */
  253. subcomm->value_is_set = true;
  254. subcomm->value.b = true;
  255. }
  256. current_subcommand = NULL;
  257. return err;
  258. }
  259. static enum error config_parse_subcommands()
  260. {
  261. const char *subcomm_str;
  262. enum error err = ERR_OPT_NOTFOUND;
  263. if (config_get_nonopt_count() <= 0) {
  264. return ERR_OPT_NO_SUBCOMMAND;
  265. }
  266. subcomm_str = config_get_nonopt(0);
  267. for (int i = 0; i < options_count; i++) {
  268. if (options[i].type != OTYPE_SUBCOMMAND)
  269. continue;
  270. if (strcmp(options[i].l_name, subcomm_str) != 0)
  271. continue;
  272. if (options[i].subopts == NULL) {
  273. /* If no suboptions is defined, define the default
  274. * help one here */
  275. options[i].subopts = &subcomm_def_help_opt;
  276. options[i].subopts_count = 1;
  277. }
  278. /* Found, parse subopts */
  279. err = config_parse_subcomm_subopts(&options[i]);
  280. }
  281. return err;
  282. }
  283. enum error config_parse(int argc, char **argv)
  284. {
  285. enum error err = NOERR;
  286. char sopt[options_count * 2 + 1];
  287. struct option lopt[options_count + 1];
  288. opt_argv = argv;
  289. opt_argc = argc;
  290. config_build_getopt_args(options_count, options, sopt, lopt, true);
  291. err = config_read_args(argc, argv, options_count, options, sopt, lopt, 0);
  292. if (err != NOERR)
  293. goto end;
  294. err = config_parse_file();
  295. if (err != NOERR)
  296. goto end;
  297. err = config_read_args(argc, argv, options_count, options, sopt, lopt, 1);
  298. if (err != NOERR)
  299. goto end;
  300. /* Set defaults for those, that didn't got set above */
  301. for (int i = 0; i < options_count; i++) {
  302. if (!options[i].value_is_set && options[i].type != OTYPE_ACTION &&
  303. options[i].default_func) {
  304. err = options[i].default_func(&options[i]);
  305. if (err != NOERR)
  306. goto end;
  307. options[i].value_is_set = true;
  308. }
  309. }
  310. err = config_read_args(argc, argv, options_count, options, sopt, lopt, 2);
  311. if (err != NOERR)
  312. goto end;
  313. err = config_required_check();
  314. if (err != NOERR)
  315. goto end;
  316. /* Now that all of the global arguments are parsed, do the subcommands */
  317. err = config_parse_subcommands();
  318. if (err != NOERR) {
  319. if (err == ERR_OPT_NO_SUBCOMMAND)
  320. printf("No subcommand given!\n\n");
  321. else if (err == ERR_OPT_NOTFOUND)
  322. printf("The given subcommand doesn't exist\n\n");
  323. if (err != ERR_OPT_EXIT) /* If err is this, then help() was already called */
  324. show_help(NULL);
  325. }
  326. end:
  327. if (err != NOERR)
  328. config_free();
  329. return err;
  330. }
  331. #if 0
  332. static int config_def_config_dir(struct conf_entry *ce)
  333. {
  334. char *dir;
  335. int len;
  336. const char *format = "%s/.config/" CONFIG_DIR_NAME;
  337. const char *home_env = getenv("HOME");
  338. if (!home_env) {
  339. /* Fix this at a later date with getuid and getpw */
  340. fprintf(stderr, "HOME environment variable not found!\n");
  341. return ERR_NOTFOUND;
  342. }
  343. len = snprintf(NULL, 0, format, home_env);
  344. if (len == -1) {
  345. int err = errno;
  346. fprintf(stderr, "Failed to call funky snpintf: %s\n", strerror(err));
  347. return err;
  348. }
  349. dir = malloc(len + 1);
  350. sprintf(dir, format, home_env);
  351. ce->value.s = dir;
  352. ce->value_is_dyn = true;
  353. return NOERR;
  354. }
  355. #endif
  356. #if 0
  357. static int config_def_cachedb(struct conf_entry *ce)
  358. {
  359. bool dh_free = false;
  360. const char *data_home = getenv("XDG_DATA_HOME");
  361. if (!data_home) {
  362. const char *home = util_get_home();
  363. if (!home)
  364. return ERR_OPT_FAILED;
  365. sprintf(NULL, "%s/.local/share", home);
  366. }
  367. return NOERR;
  368. }
  369. #endif
  370. static int config_set_str(struct conf_entry *ce, char *arg)
  371. {
  372. // TODO use realpath(3), when necessary
  373. ce->value.s = arg;
  374. return NOERR;
  375. }
  376. static int config_set_port(struct conf_entry *ce, char *arg)
  377. {
  378. long portval = strtol(arg, NULL, 10);
  379. /* A zero return will be invalid no matter if strtol succeeded or not */
  380. if (portval > UINT16_MAX || portval <= 0) {
  381. fprintf(stderr, "Invalid port value '%s'\n", arg);
  382. return ERR_OPT_INVVAL;
  383. }
  384. ce->value.hu = (uint16_t)portval;
  385. return NOERR;
  386. }
  387. static int config_set_bool(struct conf_entry *ce, char *arg)
  388. {
  389. ce->value.b = true;
  390. return NOERR;
  391. }
  392. static int show_subcomm_help(struct conf_entry *ce)
  393. {
  394. assert(current_subcommand);
  395. const struct conf_entry *subopts = current_subcommand->subopts;
  396. printf(
  397. "Usage: caniadd [OPTIONS] %s [SUBCOMMAND_OPTIONS]\n"
  398. "%s\n"
  399. "\n"
  400. "SUBCOMMAND_OPTIONS:\n",
  401. current_subcommand->l_name,
  402. current_subcommand->h_desc
  403. );
  404. for (size_t i = 0; i < current_subcommand->subopts_count; i++) {
  405. int printed = 0, pad;
  406. printed += printf(" ");
  407. if (subopts[i].l_name)
  408. printed += printf("--%s", subopts[i].l_name);
  409. if (subopts[i].s_name < UCHAR_MAX)
  410. printed += printf(", -%c", subopts[i].s_name);
  411. if (subopts[i].has_arg != no_argument)
  412. printed += printf(" arg");
  413. pad = 25 - printed;
  414. if (pad <= 0)
  415. pad = 1;
  416. printf("%*s%s", pad, "", subopts[i].h_desc);
  417. if (subopts[i].value_is_set) {
  418. printf(" Val: ");
  419. if (subopts[i].type == OTYPE_S)
  420. printed += printf("%s", subopts[i].value.s);
  421. else if (subopts[i].type == OTYPE_HU)
  422. printed += printf("%hu", subopts[i].value.hu);
  423. else if (subopts[i].type == OTYPE_B)
  424. printed += printf("%s", subopts[i].value.b ? "true" : "false");
  425. }
  426. printf("\n");
  427. }
  428. return ERR_OPT_EXIT;
  429. }
  430. static int show_help(struct conf_entry *ce)
  431. {
  432. printf(
  433. "Usage: caniadd [OPTIONS] SUBCOMMAND [SUBCOMMAND_OPTIONS]\n"
  434. "Caniadd will add files to an AniDB list, and possibly more.\n"
  435. "\n"
  436. "OPTIONS:\n"
  437. );
  438. for (size_t i = 0; i < options_count; i++) {
  439. if (options[i].type == OTYPE_SUBCOMMAND)
  440. continue;
  441. int printed = 0, pad;
  442. printed += printf(" ");
  443. if (options[i].l_name)
  444. printed += printf("--%s", options[i].l_name);
  445. if (options[i].s_name < UCHAR_MAX)
  446. printed += printf(", -%c", options[i].s_name);
  447. if (options[i].has_arg != no_argument)
  448. printed += printf(" arg");
  449. pad = 25 - printed;
  450. if (pad <= 0)
  451. pad = 1;
  452. printf("%*s%s", pad, "", options[i].h_desc);
  453. if (options[i].value_is_set) {
  454. printf(" Val: ");
  455. if (options[i].type == OTYPE_S)
  456. printed += printf("%s", options[i].value.s);
  457. else if (options[i].type == OTYPE_HU)
  458. printed += printf("%hu", options[i].value.hu);
  459. else if (options[i].type == OTYPE_B)
  460. printed += printf("%s", options[i].value.b ? "true" : "false");
  461. }
  462. printf("\n");
  463. }
  464. printf("\nSUBCOMMANDS:\n");
  465. for (size_t i = 0; i < options_count; i++) {
  466. if (options[i].type != OTYPE_SUBCOMMAND)
  467. continue;
  468. int printed = 0, pad;
  469. printed += printf(" %s", options[i].l_name);
  470. pad = 25 - printed;
  471. printf("%*s%s\n", pad, "", options[i].h_desc);
  472. }
  473. return ERR_OPT_EXIT;
  474. }
  475. static int config_parse_file()
  476. {
  477. // TODO implement this
  478. #if 0
  479. assert(conf.config_file_path);
  480. FILE *f = fopen(conf.config_file_path, "rb");
  481. char errbuf[200];
  482. toml_table_t *tml = toml_parse_file(f, errbuf, sizeof(errbuf));
  483. fclose(f);
  484. if (!tml) {
  485. fprintf(stderr, "Failed to parse config toml: %s\n", errbuf);
  486. return ERR_TOML_PARSE_ERROR;
  487. }
  488. toml_datum_t port = toml_int_in(tml, "port");
  489. if (port.ok)
  490. conf.port = (uint16_t)port.u.i;
  491. else
  492. fprintf(stderr, "Failed to parse port from config toml: %s\n", errbuf);
  493. toml_datum_t dldir = toml_string_in(tml, "default_download_dir");
  494. if (dldir.ok) {
  495. conf.default_download_dir = dldir.u.s;
  496. printf("%s\n", dldir.u.s);
  497. conf_dyn.default_download_dir = dldir.u.s;
  498. conf_dyn.default_download_dir = true;
  499. /* TODO is this always malloced?? if yes, remve dyn check */
  500. } else {
  501. fprintf(stderr, "Failed to parse download dir from config toml: %s\n", errbuf);
  502. }
  503. toml_free(tml);
  504. #endif
  505. return NOERR;
  506. }
  507. int config_free()
  508. {
  509. for (int i = 0; i < options_count; i++) {
  510. if (options[i].value_is_dyn) {
  511. free(options[i].value.s);
  512. options[i].value.s = NULL;
  513. options[i].value_is_dyn = false;
  514. options[i].value_is_set = false;
  515. }
  516. }
  517. return NOERR;
  518. }
  519. #if 0
  520. static int config_action_write_config(struct conf_entry *ce)
  521. {
  522. /* This is the success return here */
  523. int err = ERR_OPT_EXIT, plen;
  524. const char *config_dir;
  525. FILE *f = NULL;
  526. config_dir = config_get("config-dir");
  527. plen = snprintf(NULL, 0, "%s/%s", config_dir, CONFIG_FILE_NAME);
  528. char path[plen + 1];
  529. snprintf(path, plen + 1, "%s/%s", config_dir, CONFIG_FILE_NAME);
  530. for (int i = 0; i < 2; i++) {
  531. f = fopen(path, "wb");
  532. if (!f) {
  533. int errn = errno;
  534. if (errn == ENOENT && i == 0) {
  535. /* Try to create parent directory */
  536. if (mkdir(config_dir, 0755) == -1) {
  537. err = errno;
  538. fprintf(stderr, "Config mkdir failed: %s\n", strerror(err));
  539. goto end;
  540. }
  541. } else {
  542. err = errn;
  543. fprintf(stderr, "Config fopen failed: %s\n", strerror(err));
  544. goto end;
  545. }
  546. } else {
  547. break;
  548. }
  549. }
  550. for (int i = 0; i < options_count; i++) {
  551. if (!options[i].in_file)
  552. continue;
  553. fprintf(f, "%s = ", options[i].l_name);
  554. switch (options[i].type) {
  555. case OTYPE_S:
  556. // TODO toml escaping
  557. fprintf(f, "\"%s\"\n", options[i].value.s);
  558. break;
  559. case OTYPE_HU:
  560. fprintf(f, "%hu\n", options[i].value.hu);
  561. break;
  562. case OTYPE_B:
  563. fprintf(f, "%s\n", options[i].value.b ? "true" : "false");
  564. break;
  565. default:
  566. break;
  567. }
  568. }
  569. config_dump();
  570. end:
  571. if (f)
  572. fclose(f);
  573. return err;
  574. }
  575. #endif
  576. enum error config_get_inter(int cenf_count, const struct conf_entry cenf[cenf_count],
  577. const char *key, void **out)
  578. {
  579. enum error err = ERR_OPT_NOTFOUND;
  580. for (int i = 0; i < cenf_count; i++) {
  581. const struct conf_entry *cc = &cenf[i];
  582. if (strcmp(cc->l_name, key) == 0) {
  583. if (cc->value_is_set) {
  584. if (out)
  585. *out = (void**)&cc->value.s;
  586. err = NOERR;
  587. } else {
  588. err = ERR_OPT_UNSET;
  589. }
  590. break;
  591. }
  592. }
  593. return err;
  594. }
  595. enum error config_get(const char *key, void **out)
  596. {
  597. return config_get_inter(options_count, options, key, out);
  598. }
  599. enum error config_get_subopt(const char *subcomm, const char *key, void **out)
  600. {
  601. for (int i = 0; i < options_count; i++) {
  602. struct conf_entry *cc = &options[i];
  603. if (strcmp(cc->l_name, subcomm) == 0) {
  604. return config_get_inter(cc->subopts_count, cc->subopts, key, out);
  605. }
  606. }
  607. return ERR_OPT_NOTFOUND;
  608. }
  609. const char *config_get_nonopt(int index)
  610. {
  611. if (index >= config_get_nonopt_count())
  612. return NULL;
  613. return opt_argv[optind + index];
  614. }
  615. int config_get_nonopt_count()
  616. {
  617. return opt_argc - optind;
  618. }
  619. void config_dump()
  620. {
  621. for (int i = 0; i < options_count; i++) {
  622. if (options[i].type == OTYPE_ACTION)
  623. continue;
  624. printf("%s: ", options[i].l_name);
  625. if (!options[i].value_is_set) {
  626. printf("[UNSET (>.<)]\n");
  627. continue;
  628. }
  629. switch (options[i].type) {
  630. case OTYPE_S:
  631. printf("%s\n", options[i].value.s);
  632. break;
  633. case OTYPE_HU:
  634. printf("%hu\n", options[i].value.hu);
  635. break;
  636. case OTYPE_B:
  637. printf("%s\n", options[i].value.b ? "True" : "False");
  638. break;
  639. default:
  640. printf("Error :(\n");
  641. break;
  642. }
  643. }
  644. }