/* clparser.c - Command line parser for ck -----------------------------*- C -*-
*
* This file is part of ck, the config keeper
*
* -----------------------------------------------------------------------------
*
* Copyright (C) 2018 Anastasis Grammenos
* GPLv3 (see LICENCE for the full notice)
*
* -------------------------------------------------------------------------- */
#include "ckutil.h"
#include "clparser.h"
#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 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.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("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()) {
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;
}