Platon Technologies
not logged in Login Registration
EnglishSlovak
open source software development celebrating 10 years of open source development! Friday, October 16, 2026

Diff for libcfg+/src/cmdline.c between version 1.14 and 1.15

version 1.14, 2001/12/09 15:07:37 version 1.15, 2001/12/16 20:44:37
Line 1 
Line 1 
 #include <math.h>  #include <string.h>
 #include <limits.h>  #include <stdlib.h>
 #include <float.h>  
 #include <ctype.h>  
 #include <errno.h>  
   
 #include "str/strplus.h"  #include "str/strplus.h"
 #include "str/strdyn.h"  #include "str/strdyn.h"
   
   #include "cfg.h"
   #include "shared.h"
   
 static int cfg_cmdline_get_next_opt(const CFG_CONTEXT con);  int cfg_cmdline_get_next_opt(const CFG_CONTEXT con)
 static int process_currents(const CFG_CONTEXT con, int *ret_val, int *arg_used);  
 static void free_currents(const CFG_CONTEXT con);  
 static int cmdline_set_currents(const CFG_CONTEXT con);  
 static int search_cur_opt_idx(const CFG_CONTEXT con);  
 static int add_to_used_opt_idx(const CFG_CONTEXT con, int opt_idx);  
 static int store_multi_arg(const int type, const char **multi_arg, void ***ar);  
 static int store_single_arg(const int type, const char *arg, const void *where);  
   
   
 static int cfg_cmdline_get_next_opt(const CFG_CONTEXT con)  
 {  {
         /* register int opt_idx; */          /* register int opt_idx; */
         register int ret;          register int ret;
Line 66  static int cfg_cmdline_get_next_opt(cons
Line 55  static int cfg_cmdline_get_next_opt(cons
                                         && ! strcmp(con->cur_arg, con->argv[con->cur_idx - 1]))                                          && ! strcmp(con->cur_arg, con->argv[con->cur_idx - 1]))
                                 leftover_init = 1;                                  leftover_init = 1;
   
                         free_currents(con);                          __cfg_free_currents(con);
   
                         if (! strdyn_compare(con->line_stop_str, con->argv[con->cur_idx]))                          if (! strdyn_compare(con->line_stop_str, con->argv[con->cur_idx]))
                                 return CFG_ERROR_STOP_STR_FOUND;                                  return CFG_ERROR_STOP_STR_FOUND;
Line 80  static int cfg_cmdline_get_next_opt(cons
Line 69  static int cfg_cmdline_get_next_opt(cons
                                         return CFG_ERROR_NOMEM;                                          return CFG_ERROR_NOMEM;
                         }                          }
                         else {                          else {
                                 if (cmdline_set_currents(con) == 0)                                  if (__cfg_cmdline_set_currents(con) == 0)
                                         return CFG_ERROR_NOMEM;                                          return CFG_ERROR_NOMEM;
                         }                          }
                 }                  }
   
                 ret = process_currents(con, &ret_val, &arg_used);                  ret = __cfg_process_currents(con, &ret_val, &arg_used);
   
                 if (ret != CFG_OK)                  if (ret != CFG_OK)
                         return ret;                          return ret;
Line 94  static int cfg_cmdline_get_next_opt(cons
Line 83  static int cfg_cmdline_get_next_opt(cons
                         if (! (con->cur_opt_type & CFG_LONG_SEPINIT)                          if (! (con->cur_opt_type & CFG_LONG_SEPINIT)
                                         && !(con->cur_opt_type & CFG_SHORT_OPTIONS)                                          && !(con->cur_opt_type & CFG_SHORT_OPTIONS)
                                         && !(con->cur_opt_type == CFG_NONE_OPTION))                                          && !(con->cur_opt_type == CFG_NONE_OPTION))
                         {  
                                 /* puts("[1] argument used, moving cur_idx"); */  
                                 con->cur_idx_tmp++;                                  con->cur_idx_tmp++;
                         }  
   
                         if (con->cur_opt_type & CFG_SHORT_OPTIONS)                          if (con->cur_opt_type & CFG_SHORT_OPTIONS)
                                 con->cur_opt_type -= CFG_SHORT_OPTIONS;                                  con->cur_opt_type -= CFG_SHORT_OPTIONS;
                 }                  }
   
                 if (! (con->cur_opt_type & CFG_SHORT_OPTIONS)) {                  if (! (con->cur_opt_type & CFG_SHORT_OPTIONS))
                         /* puts("[2] moving cur_idx"); */  
                         con->cur_idx_tmp++;                          con->cur_idx_tmp++;
                 }  
   
                 if (ret_val > 0)                  if (ret_val > 0)
                         return ret_val;                          return ret_val;
Line 114  static int cfg_cmdline_get_next_opt(cons
Line 98  static int cfg_cmdline_get_next_opt(cons
         }          }
   
         return CFG_OK;          return CFG_OK;
 }  
   
 static int process_currents(const CFG_CONTEXT con, int *ret_val, int *arg_used)  
 {  
         register int opt_idx;  
         register int opt_data_type, opt_f_multi, opt_f_multi_sep;  
   
         *ret_val = 0;  
         if (arg_used != NULL)  
                 *arg_used = 0;  
   
         opt_idx = search_cur_opt_idx(con);  
   
 #if defined(DEBUG) && DEBUG  
         fprintf(stderr, "%s|%d|%s|%s|%s|%d|flags=%x[",  
                         con->type == CFG_LINE ? "cmdline" : "cfgfile",  
                         con->type == CFG_LINE ? con->cur_idx : 0,  
                         con->type == CFG_LINE ? con->argv[con->cur_idx] : "N/A",  
                         con->cur_opt == NULL ? "[NULL]" : con->cur_opt,  
                         con->cur_arg == NULL ? "[NULL]" : con->cur_arg,  
                         opt_idx, con->cur_opt_type);  
   
         if (con->cur_opt_type & CFG_LONG_OPTION)  
                 fputs("-LONG-", stderr);  
         if (con->cur_opt_type & CFG_SHORT_OPTION)  
                 fputs("-SHORT-", stderr);  
         if (con->cur_opt_type & CFG_SHORT_OPTIONS)  
                 fputs("-SHORTS-", stderr);  
         if (con->cur_opt_type & CFG_LONG_SEPINIT)  
                 fputs("-SEPINIT-", stderr);  
   
         fputs("]\n", stderr);  
 #endif  
   
         if (opt_idx < 0)  
                 return CFG_ERROR_UNKNOWN;  
   
         if ( ! (con->flags & CFG_IGNORE_MULTI)  
                         && ! (con->options[opt_idx].type & CFG_MULTI)) {  
                 int ret = 0;  
   
                 ret = add_to_used_opt_idx(con, opt_idx);  
   
                 if (ret < 0)  
                         return CFG_ERROR_NOMEM;  
                 else if (ret > 0)  
                         return CFG_ERROR_MULTI;  
         }  
   
         /* Setting others opt_XXX constants according to *opt_idx. */  
         /* opt_data_type - option data type, opt_f_XXX - option flags */  
         opt_data_type = con->options[opt_idx].type & CFG_DATA_TYPE_MASK;  
         opt_f_multi = con->options[opt_idx].type & CFG_MULTI;  
         opt_f_multi_sep = con->options[opt_idx].type &  
                 (CFG_MULTI_SEPARATED & ~CFG_MULTI);  
   
         switch (opt_data_type) {  
                 default:  
                         return CFG_ERROR_INTERNAL;  
                         break;  
   
                         /* Boolean data type. */  
   
                 case CFG_BOOLEAN:  
                         if (con->cur_opt_type & CFG_LONG_SEPINIT  
                                         && con->cur_arg != NULL)  
                                 return CFG_ERROR_NOTALLOWEDARG;  
   
                         if (con->options[opt_idx].value != NULL)  
                                 (*((int *) con->options[opt_idx].value))++;  
                         break;  
   
                         /* Numeric (int, long, float and double) data types. */  
   
                 case CFG_INT:  
                 case CFG_UINT:  
                 case CFG_LONG:  
                 case CFG_ULONG:  
                 case CFG_FLOAT:  
                 case CFG_DOUBLE:  
   
                         if (con->cur_arg == NULL)  
                                 return CFG_ERR_NOARG;  
   
                         {  
                                 register int ret;  
   
                                 if (opt_f_multi) {  
                                         char **add;  
                                         char *static_add[2] = {NULL, NULL};  
   
                                         if (opt_f_multi_sep) {  
                                                 add = strdyn_explode_ar(con->cur_arg,  
                                                                 con->multi_vals_separator);  
                                                 if (add == NULL)  
                                                         return CFG_ERROR_NOMEM;  
                                         }  
                                         else {  
                                                 add = static_add;  
                                                 add[0] = con->cur_arg;  
                                         }  
   
                                         ret = store_multi_arg(opt_data_type,  
                                                         (const char **) add,  
                                                         con->options[opt_idx].value);  
   
                                         if (add != static_add)  
                                                 strdyn_free((char **) add);  
                                 }  
                                 else  
                                         ret = store_single_arg(opt_data_type, con->cur_arg,  
                                                         con->options[opt_idx].value);  
   
                                 if (ret != CFG_OK)  
                                         return ret;  
                         }  
   
                         if (arg_used != NULL)  
                                 *arg_used = 1;  
                         break;  
   
                         /* String data type. */  
   
                 case CFG_STRING:  
   
                         if (con->cur_arg == NULL)  
                                 return CFG_ERR_NOARG;  
   
                         if (con->options[opt_idx].value != NULL) {  
                                 if (opt_f_multi) {  
                                         register char ***p;  
                                         p = (char ***) con->options[opt_idx].value;  
   
                                         if (opt_f_multi_sep) {  
                                                 register char **ar;  
   
                                                 ar = strdyn_explode_ar(con->cur_arg,  
                                                                 con->multi_vals_separator);  
                                                 if (ar == NULL)  
                                                         return CFG_ERROR_NOMEM;  
   
                                                 if ((*p = strdyn_add_ar(*p, ar)) == NULL)  
                                                         return CFG_ERROR_NOMEM;  
   
                                                 strdyn_free(ar);  
                                         }  
                                         else {  
                                                 if ((*p = strdyn_add(*p, con->cur_arg)) == NULL)  
                                                         return CFG_ERROR_NOMEM;  
                                         }  
                                 }  
                                 else  
                                         *((char **) con->options[opt_idx].value) =  
                                                 strdup(con->cur_arg);  
                         }  
   
                         if (arg_used != NULL)  
                                 *arg_used = 1;  
                         break;  
         }  
   
         /* if (con->options[opt_idx].val != 0) */  
         *ret_val = con->options[opt_idx].val;  
   
         return CFG_OK;  
 }  
   
 /*  
  * free_currents()  
  *  
  * Frees allocated memory for cur_opt, cur_arg.  
  * Sets cur_opt_type to CFG_NONE_OPTION.  
  */  
   
 static void free_currents(const CFG_CONTEXT con)  
 {  
         if (con->cur_opt != NULL)  
                 free(con->cur_opt);  
         if (con->cur_arg != NULL)  
                 free(con->cur_arg);  
   
         con->cur_opt_type = CFG_NONE_OPTION;  
   
         con->cur_opt = con->cur_arg = NULL;  
 }  
   
   
 /*  
  * cmdline_set_curents()  
  *  
  * Function allocate and initialize variables con->cur_opt and con->cur_arg  
  * according to con->argv[con->cur_idx].  
  * Returns 1 on success, 0 on error (not enough memory).  
  */  
   
 static int cmdline_set_currents(const CFG_CONTEXT con)  
 {  
         register int i, size;  
         register char *s, *s_sep, *s_tmp;  
   
         size = 0;  
         s = con->argv[con->cur_idx];  
   
         for (i = 0; con->short_option_prefix[i] != NULL; i++)  
                 if (strlen(con->short_option_prefix[i]) > size  
                                 && strstr(s, con->short_option_prefix[i]) == s)  
                         size = strlen(con->short_option_prefix[i]);  
   
         size = -size;  
   
         for (i = 0; con->long_option_prefix[i] != NULL; i++)  
                 if (strlen(con->long_option_prefix[i]) > (size < 0 ? -size : size)  
                                 && strstr(s, con->long_option_prefix[i]) == s)  
                         size = strlen(con->long_option_prefix[i]);  
   
         s_sep = NULL;  
   
         if (size == 0) {  
                 con->cur_opt_type = CFG_NONE_OPTION;  
         }  
         else if (size < 0) {  
                 s += -size;  
                 s_sep = s + 1;  
   
                 if (strlen(s_sep) > 0) {  
                         con->cur_opt_type = CFG_SHORT_OPTION + CFG_SHORT_OPTIONS;  
   
                         if ((con->cur_arg = strdup(s_sep)) == NULL)  
                                 return 0;  
                 }  
                 else {  
                         con->cur_opt_type = CFG_SHORT_OPTION;  
   
                         if (con->argv[con->cur_idx + 1] != NULL) {  
                                 con->cur_arg = strdup(con->argv[con->cur_idx + 1]);  
                                 if (con->cur_arg == NULL)  
                                         return 0;  
                         }  
                         else  
                                 con->cur_arg = NULL;  
                 }  
         }  
         else {  
                 con->cur_opt_type = CFG_LONG_OPTION;  
   
                 s += size;  
                 size = 0;  
   
                 for (i = 0; con->line_option_arg_separator[i] != NULL; i++)  
                         if ((s_tmp = strstr(s, con->line_option_arg_separator[i])) != NULL)  
                                 if (s_sep == NULL || s_tmp < s_sep  
                                                 || (s_tmp == s_sep &&  
                                                         strlen(con->line_option_arg_separator[i]) > size)) {  
                                         s_sep = s_tmp;  
                                         size = strlen(con->line_option_arg_separator[i]);  
                                 }  
   
                 if (s_sep == NULL) {  
                         if (con->argv[con->cur_idx + 1] != NULL) {  
                                 con->cur_arg = strdup(con->argv[con->cur_idx + 1]);  
                                 if (con->cur_arg == NULL)  
                                         return 0;  
                         }  
                         else  
                                 con->cur_arg = NULL;  
                 }  
                 else {  
                         con->cur_opt_type += CFG_LONG_SEPINIT;  
                         if ((con->cur_arg = strdup(s_sep + size)) == NULL)  
                                 return 0;  
                 }  
         }  
   
         if (s_sep == NULL)  
                 s_sep = s + strlen(s);  
   
         con->cur_opt = (char *) malloc((s_sep - s + 1) * sizeof(char));  
         if (con->cur_opt == NULL)  
                 return 0;  
   
         strncpy(con->cur_opt, s, s_sep - s);  
         con->cur_opt[s_sep - s] = '\0';  
   
         if (con->cur_opt_type == CFG_NONE_OPTION) {  
                 register char *tmp_str;  
   
                 tmp_str =con->cur_opt;  
                 con->cur_opt = con->cur_arg;  
                 con->cur_arg = tmp_str;  
         }  
   
         return 1;  
 }  
   
 /*  
  * search_cur_opt_idx()  
  *  
  * Function searches con->options for con->cur_opt.  
  * Returns index on success or -1 if not found.  
  */  
   
 static int search_cur_opt_idx(const CFG_CONTEXT con)  
 {  
         register int i;  
   
         for (i = 0; con->options[i].cmdline_long_name != NULL  
                         || con->options[i].cmdline_short_name != '\0'  
                         || con->options[i].cfgfile_name != NULL  
                         || con->options[i].type != CFG_END  
                         || con->options[i].value != NULL  
                         || con->options[i].val != 0;  
                         i++)  
         {  
   
                 if (con->type == CFG_CMDLINE) {  
                         if ((con->cur_opt_type & CFG_LONG_OPTION && con->cur_opt != NULL  
                                                 && con->options[i].cmdline_long_name != NULL  
                                                 && ! strcmp(con->cur_opt,  
                                                         con->options[i].cmdline_long_name))  
                                         || (con->cur_opt_type & CFG_SHORT_OPTION &&  
                                                 con->cur_opt[0] == con->options[i].cmdline_short_name)  
                                         || (con->cur_opt_type == CFG_NONE_OPTION &&  
                                                 con->options[i].type & CFG_LEFTOVER_ARGS))  
   
                                 return i;  
                 }  
                 else { /* con->type == CFG_CFGFILE */  
                         if (con->cur_opt != NULL  
                                         && con->options[i].cfgfile_name != NULL  
                                         && ! strcmp(con->cur_opt, con->options[i].cfgfile_name))  
   
                                 return i;  
                 }  
         }  
   
         return -1;  
 }  
   
 /*  
  * Size of realloc() step for used_opt member of cfg_context structure.  
  * Value must be grater than 0.  
  */  
   
 #ifdef CFG_USED_OPT_IDX_STEP  
 #       undef CFG_USED_OPT_IDX_STEP  
 #endif  
   
 #if defined(DEBUG) && DEBUG  
 #       define CFG_USED_OPT_IDX_STEP    (1)  
 #else  
 #       define CFG_USED_OPT_IDX_STEP    (10)  
 #endif  
   
 /*  
  * add_to_used_opt_idx()  
  *  
  * Function adds opt_idx to dynamic array con->used_opt_idx. If array is full,  
  * it is realloc()-ed according to CFG_USED_OPT_IDX_STEP constant. This  
  * function is used for detecting multiple options on command line.  
  *  
  * Note, that in array -1 means empty cell, which can be overwritten by option  
  * index value and -255 means end of array. If there is no -1 left in array,  
  * array must be resized to add new option index value.  
  *  
  * Retuns -1 on not enough memory error, 1 if opt_idx founded in array and 0 if  
  * opt_idx was sucessfully added to array.  
  */  
   
 static int add_to_used_opt_idx(const CFG_CONTEXT con, int opt_idx)  
 {  
         int *p_i = NULL;  
   
         if (opt_idx < 0)  
                 return 1;  
   
         if (con->used_opt_idx != NULL)  
                 for (p_i = con->used_opt_idx; *p_i >= 0; p_i++)  
                         if (*p_i == opt_idx)  
                                 return 1;  
   
         if (p_i == NULL || *p_i == -255) {  
                 register int *p_j;  
                 register int new_size;  
   
                 new_size = (p_i == NULL ? 0 : p_i - con->used_opt_idx)  
                         + 1 + CFG_USED_OPT_IDX_STEP;  
   
                 con->used_opt_idx = (int *) realloc(con->used_opt_idx,  
                                 new_size * sizeof(int));  
   
 #if defined(DEBUG) && DEBUG  
                 printf("add_to_used_opt_idx(con, %d): realloc(%d) = %p\n",  
                                 opt_idx, new_size, con->used_opt_idx);  
 #endif  
   
                 if (con->used_opt_idx == NULL)  
                         return -1;  
   
                 if (p_i == NULL)  
                         p_i = con->used_opt_idx;  
                 else /* Searching for new p_i after realloc(). */  
                         for (p_i = con->used_opt_idx; *p_i >= 0; p_i++) ;  
   
                 for (p_j = p_i; p_j - p_i < CFG_USED_OPT_IDX_STEP; p_j++)  
                         *p_j = -1;  
   
                 *p_j = -255;  
         }  
   
         *p_i = opt_idx;  
   
         return 0;  
 }  
   
 /*  
  * store_multi_arg()  
  *  
  * According to option date type (type) parses and stores multiple arguments  
  * (multi_arg) by adding to *ar dynamic array. Array resizing is done as first  
  * and than is store_single_arg() function called with appropriate parameters.  
  */  
   
 static int store_multi_arg(const int type, const char **multi_arg, void ***ar)  
 {  
         register int ptr_len, item_len;  
         register int size_plus, size;  
         register int k, ret;  
   
         switch (type) {  
                 default:  
                         return CFG_ERROR_INTERNAL;  
                         break;  
   
                 case CFG_INT:  
                         ptr_len = sizeof(int *);  
                         item_len = sizeof(int);  
                         break;  
   
                 case CFG_UINT:  
                         ptr_len = sizeof(unsigned int *);  
                         item_len = sizeof(unsigned int);  
                         break;  
   
                 case CFG_LONG:  
                         ptr_len = sizeof(long *);  
                         item_len = sizeof(long);  
                         break;  
   
                 case CFG_ULONG:  
                         ptr_len = sizeof(unsigned long *);  
                         item_len = sizeof(unsigned long);  
                         break;  
   
                 case CFG_FLOAT:  
                         ptr_len = sizeof(float *);  
                         item_len = sizeof(float);  
                         break;  
   
                 case CFG_DOUBLE:  
                         ptr_len = sizeof(double *);  
                         item_len = sizeof(double);  
                         break;  
         }  
   
         for (size_plus = 0; multi_arg[size_plus] != NULL; size_plus++) ;  
         for (size = 0; *ar != NULL && (*ar)[size] != NULL; size++) ;  
   
         *ar = realloc(*ar, (size + 1 + size_plus) * ptr_len);  
   
         if (*ar == NULL)  
                 return CFG_ERROR_NOMEM;  
   
         /* Array terminated NULL pointer (end of array). */  
         (*ar)[size + size_plus] = NULL;  
   
         for (k = 0; k < size_plus; k++) {  
                 (*ar)[size + k] = malloc(item_len);  
                 if ((*ar)[size + k] == NULL)  
                         return CFG_ERROR_NOMEM;  
   
                 ret = store_single_arg(type, multi_arg[k], (*ar)[size + k]);  
   
                 if (ret != CFG_OK) {  
                         /* To prevent having random value at size + k position in *ar. */  
                         (*ar)[size + k] = NULL;  
                         return ret;  
                 }  
         }  
   
         return CFG_OK;  
 }  
   
 /*  
  * store_single_arg()  
  *  
  * According to option date type (type) parses and stores single argument (arg)  
  * to specified place pointed by where.  
  */  
   
 static int store_single_arg(const int type, const char *arg, const void *where)  
 {  
         register long long_val = 0;  
         register unsigned long ulong_val = 0;  
         register double double_val = 0.0;  
         char *end;  
   
         if (where == NULL)  
                 return CFG_OK;  
   
         /* Conveting to numeric value. */  
         switch (type) {  
                 default:  
                         return CFG_ERROR_INTERNAL;  
                         break;  
   
                 case CFG_INT:  
                 case CFG_UINT:  
                 case CFG_LONG:  
                         long_val = strtol(arg, &end, 0);  
                         break;  
   
                 case CFG_ULONG:  
                         ulong_val = strtoul(arg, &end, 0);  
                         break;  
   
                 case CFG_FLOAT:  
                 case CFG_DOUBLE:  
                         double_val = strtod(arg, &end);  
                         if (*end)  
                                 return CFG_ERROR_BADNUMBER;  
   
                         if (double_val == +HUGE_VAL || double_val == -HUGE_VAL)  
                                 return CFG_ERROR_OVERFLOW;  
                         if (double_val == 0.0 && errno == ERANGE)  
                                 return CFG_ERROR_OVERFLOW;  
                         break;  
         }  
   
         /* Validating coversion results. */  
         if (!(end && *end == '\0'))  
                 return CFG_ERROR_BADNUMBER;  
   
         /* Range checking and value storing. */  
         switch (type) {  
                 default:  
                         return CFG_ERROR_INTERNAL;  
                         break;  
   
                 case CFG_INT:  
                         if (long_val >= INT_MAX || long_val <= INT_MIN)  
                                 return CFG_ERROR_OVERFLOW;  
   
                         *((int *) where) = (int) long_val;  
                         break;  
   
                 case CFG_UINT:  
                         if (long_val > UINT_MAX || long_val < 0)  
                                 return CFG_ERROR_OVERFLOW;  
   
                         *((unsigned int *) where) = (unsigned int) long_val;  
                         break;  
   
                 case CFG_LONG:  
                         if (long_val == LONG_MIN || long_val == LONG_MAX)  
                                 return CFG_ERROR_OVERFLOW;  
   
                         *((long *) where) = long_val;  
                         break;  
   
                 case CFG_ULONG:  
                         /* Fix strange strtoul() behaviour. */  
                         for (end = (char *) arg; isspace(*end); end++) ;  
   
                         /* Testing errno after strtoul() is not needed here. */  
                         if (*end == '-' || ulong_val == ULONG_MAX /* || ulong_val == 0 */)  
                                 return CFG_ERROR_OVERFLOW;  
   
                         *((unsigned long *) where) = (unsigned long) ulong_val;  
                         break;  
   
                 case CFG_FLOAT:  
 #ifdef ABS /* Borrowed from popt library. */  
 #       undef ABS  
 #endif  
 #define ABS(a) (((a) < 0) ? -(a) : (a))  
   
                         if (double_val != 0.0)  
                                 if (ABS(double_val) > FLT_MAX || ABS(double_val) < FLT_MIN)  
                                         return CFG_ERROR_OVERFLOW;  
   
                         *((float *) where) = (float) double_val;  
                         break;  
   
                 case CFG_DOUBLE:  
                         *((double *) where) = (double) double_val;  
                         break;  
         }  
   
         return CFG_OK;  
 }  }
   

Legend:
Removed from v.1.14  
changed lines
  Added in v.1.15

Platon Group <platon@platon.org> http://platon.org/
Copyright © 2002-2006 Platon Group
Site powered by Metafox CMS
Go to Top