/* clparser.c - Command line parser for ck -----------------------------*- C -*- * * This file is part of ck, the config keeper * * ----------------------------------------------------------------------------- * * Copyright (C) 2019 Anastasis Grammenos * GPLv3 (see LICENCE for the full notice) * * -------------------------------------------------------------------------- */ #include "confparser.h" #include "ckinfo.h" #include "ckerrlog.h" #include "actions.h" ERRLOG(parser); /* accepted commands */ /* [0] is the count */ const char* const strINIT[] = {"3", "init", "i", "-i"}; const char* const strADD[] = {"3", "add", "a", "-a"}; const char* const strDEL[] = {"4", "delete", "del","d", "-d"}; const char* const strEDIT[] = {"3", "edit", "e", "-e"}; const char* const strLIST[] = {"5", "list", "ls", "l", "-l", "-ls"}; const char* const strSEARCH[] = {"4", "search", "grep", "s", "-s"}; const char* const strHELP[] = {"5", "help", "h", "-?", "-h", "--help"}; const char* const strRESTORE[] = {"3", "restore","r", "-r"}; const char* const strEXPORT[] = {"3", "export", "ex", "--export"}; const char* const strConfDir[] = {"2", "--config", "-c"}; const char* const strVerbose1[] = {"2", "--verbose", "-v"}; const char* const strVersion[] = {"2", "version", "--version"}; const char* const strVerbose2[] = {"2", "--Verbose", "-V"}; /* Number of opts */ static int optNum; /* holds the list of the opts * as given by the user */ static const char **opts; /* points to the current token */ static const char *token; /* the position to be read */ static int pos = 0; /* Advance one token. * Returns 1 if it exists * 0 otherwise */ int next_token() { if (pos < optNum) { token = opts[pos]; pos++; return 1; } else { token = NULL; return 0; } } static void fill_args_list(int arg_num, UserOpt *opt) { for (int i = 0; i < arg_num; i++) { if (next_token()) { list_add(opt->args, token); } } } #define X(ACTION, MIN, MAX) \ static int parse_ ##ACTION(UserOpt *opt) { \ if (optNum < pos + MIN \ || optNum > pos + MAX) { \ opt->err = PERR_ ##ACTION## _WRONG; \ return -1; \ } \ int arg_num = optNum - pos; \ fill_args_list(arg_num, opt); \ return 0; \ } CK_ACTIONS #undef X static void determine_action(UserOpt *opt) { /* get action */ if (!next_token()) { opt->action = CK_WRONG_ACTION; return; } char actionName[STR_S] = ""; opt->action = parser_get_action(token, actionName); if (opt->action != CK_WRONG_ACTION) { LOG("Action to perform: %s", actionName); } return; } static int parse_vals(UserOpt *opt) { /* find the action */ determine_action(opt); if (opt->action == CK_WRONG_ACTION) { opt->err = PERR_UNKNOWN_ACTION; return -1; } switch (opt->action) { #define X(ACTION, MIN, MAX) \ case CKA_##ACTION: \ return parse_##ACTION(opt); CK_ACTIONS #undef X default: return -1; } } CkAction parser_get_action(const char *name, char *actionName) { int i; #define X(ACTION, MIN, MAX) \ for (i = 1; i < atoi(str##ACTION[0]) + 1; i++) { \ if (strcmp(name, str##ACTION[i]) == 0) { \ if (actionName) { \ strcpy(actionName, str##ACTION[1]); \ } \ return CKA_##ACTION; \ } \ } CK_ACTIONS; #undef X return CK_WRONG_ACTION; } static UserOpt make_empty_user_opt() { UserOpt opt; opt.version = 0; opt.action = CK_WRONG_ACTION; opt.err = PERR_NOERR; opt.confDir = NULL; opt.args = list_make_new(); return opt; } /* called to free the resources * UserOpt holds */ void free_user_opt(UserOpt *opt) { if (opt->confDir != NULL) { free(opt->confDir); } list_free(opt->args); } /* find the correct config */ static int get_config(UserOpt *opt) { /* If it's a cli option */ if (next_token()) { for (int i = 1; i < atoi(strConfDir[0]) + 1; i++) { if (strcmp(token, strConfDir[i]) == 0) { if (!next_token()) { ERR("Config needs a value"); return -1; } char dir[STR_L] = ""; realpath(token, dir); if (!util_is_dir(dir)) { ERR("%s is not a directory", token); return -1; } opt->confDir = malloc(strlen(dir) + 1); strcpy(opt->confDir, dir); // remove trailing `/` if (opt->confDir[strlen(dir) - 1] == '/') { opt->confDir[strlen(dir) - 1] = '\0'; } return 0; } } /* rewind */ pos = pos - 1; token = opts[pos]; } return find_config(opt); } static int version() { /* get first token */ if (next_token()) { for (int i = 1; i < atoi(strVersion[0]) + 1; i++) { if (strcmp(token, strVersion[i]) == 0) { print_version(); return 1; } } // rewind pos = pos - 1; token = opts[pos]; } return 0; } static void verbose() { /* get first token */ if (next_token()) { for (int i = 1; i < atoi(strVerbose1[0]) + 1; i++) { if (strcmp(token, strVerbose1[i]) == 0) { errlog_set_verbose(1); return; } } for (int i = 1; i < atoi(strVerbose2[0]) + 1; i++) { if (strcmp(token, strVerbose2[i]) == 0) { errlog_set_verbose(2); return; } } // rewind pos = pos - 1; token = opts[pos]; } } char * get_possible_action_strings(char *dest, CkAction ckAction) { char buf[STR_M] = ""; switch (ckAction) { #define X(ACTION, MIN, MAX) \ case CKA_##ACTION: \ strcpy(buf, "{ "); \ for (int i = 1; i < atoi(str##ACTION[0]); i++) { \ strcat(buf, str##ACTION[i]); \ strcat(buf, ", "); \ } \ strcat(buf, str##ACTION[atoi(str##ACTION[0])]); \ strcat(buf, " }"); \ break; CK_ACTIONS #undef X default: dest = NULL; return NULL; } strcpy(dest, buf); return dest; } static void print_parser_error(UserOpt *opt) { char names[STR_M] = ""; get_possible_action_strings(names, opt->action); switch (opt->err) { case PERR_NOERR: return; case PERR_UNKNOWN_ACTION: ERR("Unknown action: %s", token); return; #define X(ACTION, MIN, MAX) \ case PERR_ ##ACTION## _WRONG: \ HELP("Usage:\n%s", names); \ print_##ACTION##_help(); \ break; CK_ACTIONS; #undef X } } static void print_parser_help() { char names[STR_M] = ""; ckhelp("ck - the config keeper"); ckhelp("Usage:"); ckhelp("Init\t%s", get_possible_action_strings(names, CKA_INIT)); ckhelp("Add\t%s", get_possible_action_strings(names, CKA_ADD)); ckhelp("Delete\t%s", get_possible_action_strings(names, CKA_DEL)); ckhelp("Edit\t%s", get_possible_action_strings(names, CKA_EDIT)); ckhelp("List\t%s", get_possible_action_strings(names, CKA_LIST)); ckhelp("Search\t%s", get_possible_action_strings(names, CKA_SEARCH)); ckhelp("Restore\t%s", get_possible_action_strings(names, CKA_RESTORE)); ckhelp("Export\t%s", get_possible_action_strings(names, CKA_EXPORT)); ckhelp("Help\t%s", get_possible_action_strings(names, CKA_HELP)); report_help(); } int parse_action(int argc, const char **argv, UserOpt *opt) { /* make empty user opt */ *opt = make_empty_user_opt(); opts = argv; optNum = argc; /* skip the program name */ next_token(); /* set verbose level */ verbose(); /* handle version info */ if (version()) { opt->version = 1; return -1; } /* figure what is the config file */ if (get_config(opt)) { return -1; } /* If the remaining arguments are < 1 * print help and exit */ if (optNum - pos < 1) { print_parser_help(); return -1; } /* parse values */ if (parse_vals(opt)) { print_parser_error(opt); return -1; } if (opt->err == PERR_NOERR) { return 0; } print_parser_error(opt); return -1; }