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.

775 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/modifying 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/modifying 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. .h_desc_more =
  137. "The arguments are either mylist id's, a file/folder path, or file sizes and names\n"
  138. "in the format '[watch_date/]<size>/<filename>'. The filename can't contain '/' characters\n",
  139. .subopts = modify_add_subopts,
  140. .subopts_count = sizeof(modify_add_subopts) / sizeof(modify_add_subopts[0]),
  141. },
  142. /*{ .l_name = "stats", .s_name = UCHAR_MAX + 5,
  143. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  144. .type = OTYPE_B, .handle_order = 1, .value_is_set = true },*/
  145. };
  146. static const size_t options_count = sizeof(options) / sizeof(options[0]);
  147. /* Used in show_subcomm_help to output info about the subcommand */
  148. static struct conf_entry *current_subcommand = NULL;
  149. static char **opt_argv = NULL;
  150. static int opt_argc = 0;
  151. static void config_build_getopt_args(int opts_count, const struct conf_entry opts[opts_count],
  152. char out_sopt[opts_count * 2 + 1], struct option out_lopt[opts_count + 1],
  153. bool stop_at_1st_nonopt)
  154. {
  155. int i_sopt = 0, i_lopt = 0;
  156. if (stop_at_1st_nonopt) {
  157. /* Tell getopts to stop at the 1st non-option argument */
  158. out_sopt[i_sopt++] = '+';
  159. }
  160. for (int i = 0; i < opts_count; i++) {
  161. if (opts[i].type == OTYPE_SUBCOMMAND)
  162. continue;
  163. /* Short options */
  164. if (opts[i].s_name && opts[i].s_name <= UCHAR_MAX) {
  165. out_sopt[i_sopt++] = opts[i].s_name;
  166. if (opts[i].has_arg == required_argument)
  167. out_sopt[i_sopt++] = ':';
  168. assert(opts[i].has_arg == required_argument ||
  169. opts[i].has_arg == no_argument);
  170. }
  171. /* Long opts */
  172. if (opts[i].l_name) {
  173. assert(opts[i].s_name);
  174. out_lopt[i_lopt].name = opts[i].l_name;
  175. out_lopt[i_lopt].has_arg = opts[i].has_arg;
  176. out_lopt[i_lopt].flag = NULL;
  177. out_lopt[i_lopt].val = opts[i].s_name;
  178. i_lopt++;
  179. }
  180. }
  181. out_sopt[i_sopt] = '\0';
  182. memset(&out_lopt[i_lopt], 0, sizeof(struct option));
  183. }
  184. static int config_read_args(int argc, char **argv,
  185. int opts_count, struct conf_entry opts[opts_count],
  186. char sopt[opts_count * 2 + 1], struct option lopt[opts_count + 1], int level)
  187. {
  188. int optc, err = NOERR;
  189. /* Reinitialize getopt with 0 here */
  190. optind = 0;
  191. //uio_debug("Before %d %s", argc, argv[0]);
  192. while ((optc = getopt_long(argc, argv, sopt,
  193. lopt, NULL)) >= 0) {
  194. bool handled = false;
  195. //uio_debug("Optc: %c", optc);
  196. for (int i = 0; i < opts_count; i++) {
  197. if (opts[i].handle_order != level) {
  198. /* Lie a lil :x */
  199. handled = true;
  200. continue;
  201. }
  202. if (optc == opts[i].s_name) {
  203. if (opts[i].type != OTYPE_ACTION)
  204. err = opts[i].set_func(&opts[i], optarg);
  205. else
  206. err = opts[i].action_func(&opts[i]);
  207. if (err != NOERR)
  208. goto end;
  209. opts[i].value_is_set = true;
  210. handled = true;
  211. break;
  212. }
  213. }
  214. if (handled)
  215. continue;
  216. if (optc == '?') {
  217. err = ERR_OPT_FAILED;
  218. goto end;
  219. } else {
  220. fprintf(stderr, "Unhandled option? '%c'\n", optc);
  221. err = ERR_OPT_UNHANDLED;
  222. goto end;
  223. }
  224. }
  225. end:
  226. return err;
  227. }
  228. static enum error config_required_check()
  229. {
  230. enum error err = NOERR;
  231. for (int i = 0; i < options_count; i++) {
  232. if (options[i].required && !options[i].value_is_set) {
  233. printf("Argument %s is required!\n", options[i].l_name);
  234. err = ERR_OPT_REQUIRED;
  235. }
  236. }
  237. return err;
  238. }
  239. static enum error config_parse_subcomm_subopts(struct conf_entry *subcomm)
  240. {
  241. char sopt[64];
  242. struct option lopt[32];
  243. enum error err;
  244. /* Set the global current subcommand pointer */
  245. current_subcommand = subcomm;
  246. config_build_getopt_args(subcomm->subopts_count, subcomm->subopts, sopt, lopt, false);
  247. //uio_debug("Parsing subconn %s", subcomm->l_name);
  248. //uio_debug("sopt: %s", sopt);
  249. /* Update args for next parsing and nonopts parsing for later */
  250. opt_argv = &opt_argv[optind];
  251. opt_argc = config_get_nonopt_count();
  252. /* argv[0] (which is the subcommand string) will be treated as the filename here, and skipped */
  253. err = config_read_args(opt_argc, opt_argv,
  254. subcomm->subopts_count, subcomm->subopts, sopt, lopt, 0);
  255. if (err == NOERR) {
  256. /* Mark subcommand as set */
  257. subcomm->value_is_set = true;
  258. subcomm->value.b = true;
  259. }
  260. current_subcommand = NULL;
  261. return err;
  262. }
  263. static enum error config_parse_subcommands()
  264. {
  265. const char *subcomm_str;
  266. enum error err = ERR_OPT_NOTFOUND;
  267. if (config_get_nonopt_count() <= 0) {
  268. return ERR_OPT_NO_SUBCOMMAND;
  269. }
  270. subcomm_str = config_get_nonopt(0);
  271. for (int i = 0; i < options_count; i++) {
  272. if (options[i].type != OTYPE_SUBCOMMAND)
  273. continue;
  274. if (strcmp(options[i].l_name, subcomm_str) != 0)
  275. continue;
  276. if (options[i].subopts == NULL) {
  277. /* If no suboptions is defined, define the default
  278. * help one here */
  279. options[i].subopts = &subcomm_def_help_opt;
  280. options[i].subopts_count = 1;
  281. }
  282. /* Found, parse subopts */
  283. err = config_parse_subcomm_subopts(&options[i]);
  284. }
  285. return err;
  286. }
  287. enum error config_parse(int argc, char **argv)
  288. {
  289. enum error err = NOERR;
  290. char sopt[options_count * 2 + 1];
  291. struct option lopt[options_count + 1];
  292. opt_argv = argv;
  293. opt_argc = argc;
  294. config_build_getopt_args(options_count, options, sopt, lopt, true);
  295. err = config_read_args(argc, argv, options_count, options, sopt, lopt, 0);
  296. if (err != NOERR)
  297. goto end;
  298. err = config_parse_file();
  299. if (err != NOERR)
  300. goto end;
  301. err = config_read_args(argc, argv, options_count, options, sopt, lopt, 1);
  302. if (err != NOERR)
  303. goto end;
  304. /* Set defaults for those, that didn't got set above */
  305. for (int i = 0; i < options_count; i++) {
  306. if (!options[i].value_is_set && options[i].type != OTYPE_ACTION &&
  307. options[i].default_func) {
  308. err = options[i].default_func(&options[i]);
  309. if (err != NOERR)
  310. goto end;
  311. options[i].value_is_set = true;
  312. }
  313. }
  314. err = config_read_args(argc, argv, options_count, options, sopt, lopt, 2);
  315. if (err != NOERR)
  316. goto end;
  317. err = config_required_check();
  318. if (err != NOERR)
  319. goto end;
  320. /* Now that all of the global arguments are parsed, do the subcommands */
  321. err = config_parse_subcommands();
  322. if (err != NOERR) {
  323. if (err == ERR_OPT_NO_SUBCOMMAND)
  324. printf("No subcommand given!\n\n");
  325. else if (err == ERR_OPT_NOTFOUND)
  326. printf("The given subcommand doesn't exist\n\n");
  327. if (err != ERR_OPT_EXIT) /* If err is this, then help() was already called */
  328. show_help(NULL);
  329. }
  330. end:
  331. if (err != NOERR)
  332. config_free();
  333. return err;
  334. }
  335. #if 0
  336. static int config_def_config_dir(struct conf_entry *ce)
  337. {
  338. char *dir;
  339. int len;
  340. const char *format = "%s/.config/" CONFIG_DIR_NAME;
  341. const char *home_env = getenv("HOME");
  342. if (!home_env) {
  343. /* Fix this at a later date with getuid and getpw */
  344. fprintf(stderr, "HOME environment variable not found!\n");
  345. return ERR_NOTFOUND;
  346. }
  347. len = snprintf(NULL, 0, format, home_env);
  348. if (len == -1) {
  349. int err = errno;
  350. fprintf(stderr, "Failed to call funky snpintf: %s\n", strerror(err));
  351. return err;
  352. }
  353. dir = malloc(len + 1);
  354. sprintf(dir, format, home_env);
  355. ce->value.s = dir;
  356. ce->value_is_dyn = true;
  357. return NOERR;
  358. }
  359. #endif
  360. #if 0
  361. static int config_def_cachedb(struct conf_entry *ce)
  362. {
  363. bool dh_free = false;
  364. const char *data_home = getenv("XDG_DATA_HOME");
  365. if (!data_home) {
  366. const char *home = util_get_home();
  367. if (!home)
  368. return ERR_OPT_FAILED;
  369. sprintf(NULL, "%s/.local/share", home);
  370. }
  371. return NOERR;
  372. }
  373. #endif
  374. static int config_set_str(struct conf_entry *ce, char *arg)
  375. {
  376. // TODO use realpath(3), when necessary
  377. ce->value.s = arg;
  378. return NOERR;
  379. }
  380. static int config_set_port(struct conf_entry *ce, char *arg)
  381. {
  382. long portval = strtol(arg, NULL, 10);
  383. /* A zero return will be invalid no matter if strtol succeeded or not */
  384. if (portval > UINT16_MAX || portval <= 0) {
  385. fprintf(stderr, "Invalid port value '%s'\n", arg);
  386. return ERR_OPT_INVVAL;
  387. }
  388. ce->value.hu = (uint16_t)portval;
  389. return NOERR;
  390. }
  391. static int config_set_bool(struct conf_entry *ce, char *arg)
  392. {
  393. ce->value.b = true;
  394. return NOERR;
  395. }
  396. static int show_subcomm_help(struct conf_entry *ce)
  397. {
  398. assert(current_subcommand);
  399. const struct conf_entry *subopts = current_subcommand->subopts;
  400. printf(
  401. "Usage: caniadd [OPTIONS] %s [SUBCOMMAND_OPTIONS]\n"
  402. "%s\n"
  403. "%s"
  404. "\n"
  405. "SUBCOMMAND_OPTIONS:\n",
  406. current_subcommand->l_name,
  407. current_subcommand->h_desc,
  408. current_subcommand->h_desc_more ? current_subcommand->h_desc_more : ""
  409. );
  410. for (size_t i = 0; i < current_subcommand->subopts_count; i++) {
  411. int printed = 0, pad;
  412. printed += printf(" ");
  413. if (subopts[i].l_name)
  414. printed += printf("--%s", subopts[i].l_name);
  415. if (subopts[i].s_name < UCHAR_MAX)
  416. printed += printf(", -%c", subopts[i].s_name);
  417. if (subopts[i].has_arg != no_argument)
  418. printed += printf(" arg");
  419. pad = 25 - printed;
  420. if (pad <= 0)
  421. pad = 1;
  422. printf("%*s%s", pad, "", subopts[i].h_desc);
  423. if (subopts[i].value_is_set) {
  424. printf(" Val: ");
  425. if (subopts[i].type == OTYPE_S)
  426. printed += printf("%s", subopts[i].value.s);
  427. else if (subopts[i].type == OTYPE_HU)
  428. printed += printf("%hu", subopts[i].value.hu);
  429. else if (subopts[i].type == OTYPE_B)
  430. printed += printf("%s", subopts[i].value.b ? "true" : "false");
  431. }
  432. printf("\n");
  433. }
  434. return ERR_OPT_EXIT;
  435. }
  436. static int show_help(struct conf_entry *ce)
  437. {
  438. printf(
  439. "Usage: caniadd [OPTIONS] SUBCOMMAND [SUBCOMMAND_OPTIONS]\n"
  440. "Caniadd will add files to an AniDB list, and possibly more.\n"
  441. "\n"
  442. "OPTIONS:\n"
  443. );
  444. for (size_t i = 0; i < options_count; i++) {
  445. if (options[i].type == OTYPE_SUBCOMMAND)
  446. continue;
  447. int printed = 0, pad;
  448. printed += printf(" ");
  449. if (options[i].l_name)
  450. printed += printf("--%s", options[i].l_name);
  451. if (options[i].s_name < UCHAR_MAX)
  452. printed += printf(", -%c", options[i].s_name);
  453. if (options[i].has_arg != no_argument)
  454. printed += printf(" arg");
  455. pad = 25 - printed;
  456. if (pad <= 0)
  457. pad = 1;
  458. printf("%*s%s", pad, "", options[i].h_desc);
  459. if (options[i].value_is_set) {
  460. printf(" Val: ");
  461. if (options[i].type == OTYPE_S)
  462. printed += printf("%s", options[i].value.s);
  463. else if (options[i].type == OTYPE_HU)
  464. printed += printf("%hu", options[i].value.hu);
  465. else if (options[i].type == OTYPE_B)
  466. printed += printf("%s", options[i].value.b ? "true" : "false");
  467. }
  468. printf("\n");
  469. }
  470. printf("\nSUBCOMMANDS:\n");
  471. for (size_t i = 0; i < options_count; i++) {
  472. if (options[i].type != OTYPE_SUBCOMMAND)
  473. continue;
  474. int printed = 0, pad;
  475. printed += printf(" %s", options[i].l_name);
  476. pad = 25 - printed;
  477. printf("%*s%s\n", pad, "", options[i].h_desc);
  478. }
  479. return ERR_OPT_EXIT;
  480. }
  481. static int config_parse_file()
  482. {
  483. // TODO implement this
  484. #if 0
  485. assert(conf.config_file_path);
  486. FILE *f = fopen(conf.config_file_path, "rb");
  487. char errbuf[200];
  488. toml_table_t *tml = toml_parse_file(f, errbuf, sizeof(errbuf));
  489. fclose(f);
  490. if (!tml) {
  491. fprintf(stderr, "Failed to parse config toml: %s\n", errbuf);
  492. return ERR_TOML_PARSE_ERROR;
  493. }
  494. toml_datum_t port = toml_int_in(tml, "port");
  495. if (port.ok)
  496. conf.port = (uint16_t)port.u.i;
  497. else
  498. fprintf(stderr, "Failed to parse port from config toml: %s\n", errbuf);
  499. toml_datum_t dldir = toml_string_in(tml, "default_download_dir");
  500. if (dldir.ok) {
  501. conf.default_download_dir = dldir.u.s;
  502. printf("%s\n", dldir.u.s);
  503. conf_dyn.default_download_dir = dldir.u.s;
  504. conf_dyn.default_download_dir = true;
  505. /* TODO is this always malloced?? if yes, remve dyn check */
  506. } else {
  507. fprintf(stderr, "Failed to parse download dir from config toml: %s\n", errbuf);
  508. }
  509. toml_free(tml);
  510. #endif
  511. return NOERR;
  512. }
  513. int config_free()
  514. {
  515. for (int i = 0; i < options_count; i++) {
  516. if (options[i].value_is_dyn) {
  517. free(options[i].value.s);
  518. options[i].value.s = NULL;
  519. options[i].value_is_dyn = false;
  520. options[i].value_is_set = false;
  521. }
  522. }
  523. return NOERR;
  524. }
  525. #if 0
  526. static int config_action_write_config(struct conf_entry *ce)
  527. {
  528. /* This is the success return here */
  529. int err = ERR_OPT_EXIT, plen;
  530. const char *config_dir;
  531. FILE *f = NULL;
  532. config_dir = config_get("config-dir");
  533. plen = snprintf(NULL, 0, "%s/%s", config_dir, CONFIG_FILE_NAME);
  534. char path[plen + 1];
  535. snprintf(path, plen + 1, "%s/%s", config_dir, CONFIG_FILE_NAME);
  536. for (int i = 0; i < 2; i++) {
  537. f = fopen(path, "wb");
  538. if (!f) {
  539. int errn = errno;
  540. if (errn == ENOENT && i == 0) {
  541. /* Try to create parent directory */
  542. if (mkdir(config_dir, 0755) == -1) {
  543. err = errno;
  544. fprintf(stderr, "Config mkdir failed: %s\n", strerror(err));
  545. goto end;
  546. }
  547. } else {
  548. err = errn;
  549. fprintf(stderr, "Config fopen failed: %s\n", strerror(err));
  550. goto end;
  551. }
  552. } else {
  553. break;
  554. }
  555. }
  556. for (int i = 0; i < options_count; i++) {
  557. if (!options[i].in_file)
  558. continue;
  559. fprintf(f, "%s = ", options[i].l_name);
  560. switch (options[i].type) {
  561. case OTYPE_S:
  562. // TODO toml escaping
  563. fprintf(f, "\"%s\"\n", options[i].value.s);
  564. break;
  565. case OTYPE_HU:
  566. fprintf(f, "%hu\n", options[i].value.hu);
  567. break;
  568. case OTYPE_B:
  569. fprintf(f, "%s\n", options[i].value.b ? "true" : "false");
  570. break;
  571. default:
  572. break;
  573. }
  574. }
  575. config_dump();
  576. end:
  577. if (f)
  578. fclose(f);
  579. return err;
  580. }
  581. #endif
  582. enum error config_get_inter(int cenf_count, const struct conf_entry cenf[cenf_count],
  583. const char *key, void **out)
  584. {
  585. enum error err = ERR_OPT_NOTFOUND;
  586. for (int i = 0; i < cenf_count; i++) {
  587. const struct conf_entry *cc = &cenf[i];
  588. if (strcmp(cc->l_name, key) == 0) {
  589. if (cc->value_is_set) {
  590. if (out)
  591. *out = (void**)&cc->value.s;
  592. err = NOERR;
  593. } else {
  594. err = ERR_OPT_UNSET;
  595. }
  596. break;
  597. }
  598. }
  599. return err;
  600. }
  601. enum error config_get(const char *key, void **out)
  602. {
  603. return config_get_inter(options_count, options, key, out);
  604. }
  605. enum error config_get_subopt(const char *subcomm, const char *key, void **out)
  606. {
  607. for (int i = 0; i < options_count; i++) {
  608. struct conf_entry *cc = &options[i];
  609. if (strcmp(cc->l_name, subcomm) == 0) {
  610. return config_get_inter(cc->subopts_count, cc->subopts, key, out);
  611. }
  612. }
  613. return ERR_OPT_NOTFOUND;
  614. }
  615. const char *config_get_nonopt(int index)
  616. {
  617. if (index >= config_get_nonopt_count())
  618. return NULL;
  619. return opt_argv[optind + index];
  620. }
  621. int config_get_nonopt_count()
  622. {
  623. return opt_argc - optind;
  624. }
  625. void config_dump()
  626. {
  627. for (int i = 0; i < options_count; i++) {
  628. if (options[i].type == OTYPE_ACTION)
  629. continue;
  630. printf("%s: ", options[i].l_name);
  631. if (!options[i].value_is_set) {
  632. printf("[UNSET (>.<)]\n");
  633. continue;
  634. }
  635. switch (options[i].type) {
  636. case OTYPE_S:
  637. printf("%s\n", options[i].value.s);
  638. break;
  639. case OTYPE_HU:
  640. printf("%hu\n", options[i].value.hu);
  641. break;
  642. case OTYPE_B:
  643. printf("%s\n", options[i].value.b ? "True" : "False");
  644. break;
  645. default:
  646. printf("Error :(\n");
  647. break;
  648. }
  649. }
  650. }