* by JH <jheinonen@users.sourceforge.net>
*
* Copyright (C) Jaakko Heinonen
+ *
*/
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <unistd.h>
-#include "abook_curses.h"
-#include "abook.h"
+#include <ctype.h>
+#include <assert.h>
#include "options.h"
-#ifdef HAVE_CONFIG_H
-# include "config.h"
+#include "abook.h"
+#include "gettext.h"
+#include "misc.h"
+#include "views.h"
+#include "xmalloc.h"
+
+#ifndef FALSE
+# define FALSE 0
+#endif
+#ifndef TRUE
+# define TRUE 1
#endif
-struct conff_node *abook_config;
+#define UL (unsigned long)
-static int rcfile_exist();
-static void default_options();
+/*
+ * option types
+ */
-extern char *rcfile;
+enum opt_type {
+ OT_BOOL,
+ OT_STR,
+ OT_INT
+};
-static char *
-abook_opt_conff_get_val(char *key)
+struct option {
+ char *option;
+ enum opt_type type;
+ unsigned int data;
+ unsigned long init;
+};
+
+static struct option abook_vars[] = {
+ { "autosave", OT_BOOL, BOOL_AUTOSAVE, TRUE },
+
+ { "show_all_emails", OT_BOOL, BOOL_SHOW_ALL_EMAILS, TRUE },
+ { "index_format", OT_STR, STR_INDEX_FORMAT, UL " {name:22} {email:40} {phone:12|workphone|mobile}" },
+ { "mutt_command", OT_STR, STR_MUTT_COMMAND, UL "mutt" },
+ { "mutt_return_all_emails", OT_BOOL, BOOL_MUTT_RETURN_ALL_EMAILS,
+ TRUE },
+
+ { "print_command", OT_STR, STR_PRINT_COMMAND, UL "lpr" },
+
+ { "www_command", OT_STR, STR_WWW_COMMAND, UL "lynx" },
+
+ { "address_style", OT_STR, STR_ADDRESS_STYLE, UL "eu" },
+
+ { "use_ascii_only", OT_BOOL, BOOL_USE_ASCII_ONLY, FALSE },
+
+ { "add_email_prevent_duplicates", OT_BOOL, BOOL_ADD_EMAIL_PREVENT_DUPLICATES, FALSE },
+ { "preserve_fields", OT_STR, STR_PRESERVE_FIELDS, UL "standard" },
+ { "sort_field", OT_STR, STR_SORT_FIELD, UL "nick" },
+ { "show_cursor", OT_BOOL, BOOL_SHOW_CURSOR, FALSE },
+
+ { NULL }
+};
+
+static unsigned char bool_opts[BOOL_MAX];
+static int int_opts[INT_MAXIMUM];
+static char *str_opts[STR_MAX];
+
+static void
+set_int(enum int_opts opt, int value)
{
- int tried;
- char *value = NULL;
+ assert(opt >= 0 && opt < INT_MAXIMUM);
+ int_opts[opt] = value;
+}
- for(tried = 0; tried < 2; ) {
- if( ( value = conff_get_value(abook_config, key) )
- == 0 ) {
- tried ++;
- default_options(); /* try with defaults */
- } else
- return value;
- }
- return NULL;
+static void
+set_bool(enum bool_opts opt, bool value)
+{
+ assert(opt >= 0 && opt < BOOL_MAX);
+
+ bool_opts[opt] = value;
+}
+
+static void
+set_str(enum str_opts opt, char *value)
+{
+ assert(opt >= 0 && opt < STR_MAX);
+
+ if(str_opts[opt])
+ free(str_opts[opt]);
+
+ str_opts[opt] = xstrdup(value);
}
int
-options_get_int(char *key)
+opt_get_int(enum int_opts opt)
{
- char *value;
- int ret;
-
- if( ( value = abook_opt_conff_get_val(key) )
- == NULL)
- return 1;
+ assert(opt >= 0 && opt < INT_MAXIMUM);
- if( !strcasecmp(value, "true") )
- ret = 1;
- else
- if( !strcasecmp(value, "false") )
- ret = 0;
- else
- ret = safe_atoi(value);
-
- return ret;
+ return int_opts[opt];
}
-
+
+bool
+opt_get_bool(enum bool_opts opt)
+{
+ assert(opt >= 0 && opt < BOOL_MAX);
+
+ return bool_opts[opt];
+}
+
char *
-options_get_str(char *key)
+opt_get_str(enum str_opts opt)
+{
+ assert(opt >= 0 && opt < STR_MAX);
+
+ return str_opts[opt];
+}
+
+static void
+restore_default(struct option *p)
{
- return abook_opt_conff_get_val(key);
+ switch(p -> type) {
+ case OT_BOOL:
+ set_bool(p -> data, (bool)p -> init);
+ break;
+ case OT_INT:
+ set_int(p -> data, (int)p -> init);
+ break;
+ case OT_STR:
+ if(p -> init)
+ set_str(p -> data, (char *) p -> init);
+ break;
+ default:
+ assert(0);
+ }
}
-
+
void
-init_options()
+init_opts()
{
- abook_config = NULL;
+ int i;
- if( rcfile_exist() )
- load_options();
- else
- default_options();
+ for(i = 0; abook_vars[i].option; i++)
+ restore_default(&abook_vars[i]);
}
void
-close_config()
+free_opts()
{
+ int i;
+
/*
- * we don't save the configuration file since user can specify
- * alternative configuration files
+ * only strings need to be freed
*/
-#if 0
- save_options();
-#endif
+ for(i = 0; i < STR_MAX; i++) {
+ free(str_opts[i]);
+ str_opts[i] = NULL;
+ }
+}
+
+/*
+ * file parsing
+ */
+
+typedef struct {
+ char *data, *ptr;
+} buffer;
- conff_free_nodes(abook_config);
+static void
+opt_line_remove_comments(char *p)
+{
+ bool in_quote = FALSE;
+ bool escape = FALSE;
+
+ assert(p != NULL);
+
+ for(; *p; p++) {
+ switch(*p) {
+ case '\"':
+ if(!escape)
+ in_quote = !in_quote;
+ break;
+ case '\\':
+ escape = TRUE;
+ break;
+ case '#':
+ if(!in_quote) {
+ *p = 0;
+ return;
+ }
+ default:
+ escape = FALSE;
+ }
+ }
}
-static int
-rcfile_exist()
+/* After calling,
+ * - b->data points to the found token, or NULL is end of parsing
+ * - b->ptr points to the begining of next token
+ *
+ * If the TOKEN_ALLOC option is used, the original string is not mangled
+ * and memory is allocated for the token.
+ */
+static char *
+get_token(buffer *b, int options)
{
- return ( (0 == access(SYSWIDE_RCFILE, F_OK)) ||
- (0 == access(rcfile, F_OK)) );
+ char quote = 0, c;
+ char *end = NULL;
+
+ assert(b);
+
+ SKIPWS(b->ptr);
+ if(*b->ptr && strchr("\"'", *b->ptr))
+ quote = *(b->ptr++);
+ b->data = b->ptr;
+
+ while(1) {
+ if(!(c = *b->ptr)) {
+ end = b->ptr;
+ break;
+ }
+
+ if(!quote && (
+ ISSPACE(c) ||
+ ((options & TOKEN_EQUAL) && (c == '=')) ||
+ ((options & TOKEN_COMMA) && (c == ',')))
+ ) {
+ end = b->ptr;
+ break;
+ } else if(c == quote) {
+ quote = 0;
+ end = b->ptr++;
+ break;
+ }
+
+ b->ptr++;
+ }
+
+ if(quote)
+ return _("quote mismatch");
+
+ if(options & (TOKEN_EQUAL | TOKEN_COMMA))
+ SKIPWS(b->ptr); /* whitespaces can precede the sign */
+
+ if((options & TOKEN_EQUAL) && (*b->ptr != '='))
+ return _("no assignment character found");
+
+ if((options & TOKEN_COMMA) && *b->ptr && (*b->ptr != ','))
+ return _("error in comma separated list");
+
+ if(b->ptr == b->data) {
+ b->data = NULL;
+ return NULL; /* no error, just end of parsing */
+ }
+
+ if(options & TOKEN_ALLOC) /* freeing is the caller's responsibility */
+ b->data = xstrndup(b->data, end - b->data);
+ else
+ *end = 0;
+
+ b->ptr++; /* advance to next token */
+ SKIPWS(b->ptr);
+
+ return NULL;
}
-void
-load_options()
-{
- int ret;
-
- if( (ret = conff_load_file(&abook_config, rcfile,
- REPLACE_KEY)) > 0) {
- fprintf(stderr, "%s: parse error at line %d\n", rcfile, ret);
- exit(1);
+static const char *
+opt_set_set_option(char *p, struct option *opt)
+{
+ int len;
+
+ assert(p);
+
+ strtrim(p);
+ len = strlen(p);
+
+ if(*p == '\"' && p[len - 1] == '\"') {
+ if(len < 3)
+ return _("invalid value");
+ p[len - 1] = 0;
+ p++;
}
- if( (ret = conff_load_file(&abook_config, SYSWIDE_RCFILE,
- DONT_REPLACE_KEY )) > 0) {
- fprintf(stderr, "%s: parse error at line %d\n",
- SYSWIDE_RCFILE, ret);
- exit(1);
+ switch(opt -> type) {
+ case OT_STR:
+ set_str(opt -> data, p);
+ break;
+ case OT_INT:
+ set_int(opt -> data, safe_atoi(p));
+ break;
+ case OT_BOOL:
+ if(!strcasecmp(p, "true") || !strcasecmp(p, "on"))
+ set_bool(opt -> data, TRUE);
+ else if(!strcasecmp(p, "false") ||
+ !strcasecmp(p, "off"))
+ set_bool(opt -> data, FALSE);
+ else
+ return _("invalid value");
+ break;
+ default:
+ assert(0);
}
+
+ return NULL;
}
-#if 0
-void
-save_options()
+static const char *
+opt_set_option(char *var, char *p)
{
- if( rcfile_exist() ) /* don't overwrite existing config */
- return;
+ int i;
- conff_save_file(abook_config, rcfile);
+ assert(var);
+ assert(p);
+
+ for(i = 0; abook_vars[i].option; i++)
+ if(!strcmp(abook_vars[i].option, var))
+ return opt_set_set_option(p, &abook_vars[i]);
+
+ return _("unknown option");
}
-#endif
-static void
-options_add_key(char *key, char *value)
+static int
+check_options()
{
- const int flags = DONT_REPLACE_KEY;
+ char *str;
+ int err = 0;
- conff_add_key(&abook_config, key, value, flags);
+ str = opt_get_str(STR_PRESERVE_FIELDS);
+ if(strcasecmp(str, "all") && strcasecmp(str, "none") &&
+ strcasecmp(str, "standard")) {
+ fprintf(stderr, _("valid values for the 'preserve_fields' "
+ "option are 'all', 'standard' "
+ "(default), and 'none'\n"));
+ restore_default(&abook_vars[STR_PRESERVE_FIELDS]);
+ err++;
+ }
+ str = opt_get_str(STR_ADDRESS_STYLE);
+ if(strcasecmp(str, "eu") && strcasecmp(str, "uk") &&
+ strcasecmp(str, "us")) {
+ fprintf(stderr, _("valid values for the 'address_style' "
+ "option are 'eu' (default), 'uk', "
+ "and 'us'\n"));
+ restore_default(&abook_vars[STR_ADDRESS_STYLE]);
+ err++;
+ }
+
+ return err;
}
-static void
-default_options()
+/*
+ * syntax: set <option> = <value>
+ */
+static const char *
+opt_parse_set(buffer *b)
{
- options_add_key("autosave", "true");
+ char *var, *err;
+
+ if((err = get_token(b, TOKEN_EQUAL)))
+ return err;
+
+ if((var = b->data) == NULL)
+ return _("invalid value assignment");
+
+ return opt_set_option(var, b->ptr);
+}
- options_add_key("show_all_emails", "true");
- options_add_key("emailpos", "25");
- options_add_key("extra_column", "7");
- options_add_key("extra_alternative", "-1");
- options_add_key("extrapos", "65");
+static const char *
+opt_parse_customfield(buffer *b)
+{
+ return _("customfield: obsolete command - please use the "
+ "'field' and 'view' commands instead");
+}
- options_add_key("mutt_command", "mutt");
- options_add_key("mutt_return_all_emails", "true");
+#include "views.h" /* needed for add_field_to_view */
- options_add_key("print_command", "lpr");
+/*
+ * syntax: view <tab name> = <field1> [ , <field2>, ... ]
+ */
+static const char *
+opt_parse_view(buffer *b)
+{
+ char *err, *view;
- options_add_key("filesel_sort", "false");
+ if((err = get_token(b, TOKEN_EQUAL)))
+ return err;
- options_add_key("www_command", "lynx");
+ if((view = b->data) == NULL)
+ return _("no view name provided");
- options_add_key("address_style", "eu");
+ while(1) {
+ if((err = get_token(b, TOKEN_COMMA)))
+ return err;
- options_add_key("use_ascii_only", "false");
+ if(b->data == NULL)
+ break;
+
+ if((err = add_field_to_view(view, b->data)))
+ return err;
+ }
+
+ return NULL;
}
+
+#include "database.h" /* needed for declare_new_field */
+
+/*
+ * syntax: field <identifier> = <human readable name> [ , <type> ]
+ */
+static const char *
+opt_parse_field(buffer *b)
+{
+ char *err, *field, *name;
+
+ if((err = get_token(b, TOKEN_EQUAL)))
+ return err;
+
+ if((field = b->data) == NULL)
+ return _("no field identifier provided");
+
+ if((err = get_token(b, TOKEN_COMMA)))
+ return err;
+
+ if((name = b->data) == NULL)
+ return _("no field name provided");
+
+ if((err = declare_new_field(field,
+ name,
+ b->ptr,
+ 0 /* reject "standard" fields */)))
+ return err;
+
+ return NULL;
+}
+
+
+static struct {
+ char *token;
+ const char * (*func) (buffer *line);
+} opt_parsers[] = {
+ { "set", opt_parse_set },
+ { "customfield", opt_parse_customfield }, /* obsolete */
+ { "view", opt_parse_view },
+ { "field", opt_parse_field },
+ { NULL }
+};
+
+static bool
+opt_parse_line(char *line, int n, char *fn)
+{
+ int i;
+ const char *err = NULL;
+ char *token;
+ buffer b;
+
+ assert(line && fn);
+
+ b.ptr = line;
+
+ if((err = get_token(&b, 0))) {
+ fprintf(stderr, "%s\n", err);
+ return FALSE;
+ }
+
+ if(b.data == NULL)
+ return FALSE;
+
+ strtrim(b.data);
+ strtrim(b.ptr);
+
+ token = b.data;
+ b.data = b.ptr = b.ptr;
+
+ for(i = 0; opt_parsers[i].token; i++)
+ if(!strcmp(opt_parsers[i].token, token)) {
+ if(!(err = opt_parsers[i].func(&b)))
+ return FALSE;
+ break;
+ }
+
+ fprintf(stderr, _("%s: parse error at line %d: "), fn, n);
+ if(err)
+ fprintf(stderr, "%s\n", err);
+ else
+ fprintf(stderr, _("unknown token %s\n"), token);
+
+ return TRUE;
+}
+
+int
+load_opts(char *filename)
+{
+ FILE *in;
+ char *line = NULL;
+ int n;
+ int err = 0;
+
+ if((in = fopen(filename, "r")) == NULL)
+ return -1;
+
+ for(n = 1;!feof(in); n++) {
+ line = getaline(in);
+
+ if(feof(in))
+ break;
+
+ if(line && *line) {
+ opt_line_remove_comments(line);
+ if(*line)
+ err += opt_parse_line(line, n, filename) ? 1:0;
+ }
+
+ free(line);
+ line = NULL;
+ }
+
+ free(line);
+
+ /* post-initialization */
+ err += check_options();
+ if(!strcasecmp(opt_get_str(STR_PRESERVE_FIELDS), "standard"))
+ init_standard_fields();
+
+ return err;
+}
+