"Fossies" - the Fresh Open Source Software Archive

Member "aspell-0.60.8/common/config.cpp" (8 Oct 2019, 43006 Bytes) of package /linux/misc/aspell-0.60.8.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "config.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 0.60.7_vs_0.60.8.

    1 // This file is part of The New Aspell
    2 // Copyright (C) 2001 by Kevin Atkinson under the GNU LGPL license
    3 // version 2.0 or 2.1.  You should have received a copy of the LGPL
    4 // license along with this library if you did not you can find
    5 // it at http://www.gnu.org/.
    6 
    7 //#include <stdio.h>
    8 //#define DEBUG {fprintf(stderr,"File: %s(%i)\n",__FILE__,__LINE__);}
    9 #include <string.h>
   10 #include <stdlib.h>
   11 #include "ndebug.hpp"
   12 #include <assert.h>
   13 
   14 #include "dirs.h"
   15 #include "settings.h"
   16 
   17 #ifdef USE_LOCALE
   18 # include <locale.h>
   19 #endif
   20 
   21 #ifdef HAVE_LANGINFO_CODESET
   22 # include <langinfo.h>
   23 #endif
   24 
   25 #include "cache.hpp"
   26 #include "asc_ctype.hpp"
   27 #include "config.hpp"
   28 #include "errors.hpp"
   29 #include "file_util.hpp"
   30 #include "fstream.hpp"
   31 #include "getdata.hpp"
   32 #include "itemize.hpp"
   33 #include "mutable_container.hpp"
   34 #include "posib_err.hpp"
   35 #include "string_map.hpp"
   36 #include "stack_ptr.hpp"
   37 #include "char_vector.hpp"
   38 #include "convert.hpp"
   39 #include "vararray.hpp"
   40 #include "string_list.hpp"
   41 
   42 #include "gettext.h"
   43 
   44 #include "iostream.hpp"
   45 
   46 #define DEFAULT_LANG "en_US"
   47 
   48 // NOTE: All filter options are now stored with he "f-" prefix.  However
   49 //   during lookup, the non prefix version is also recognized.
   50 
   51 // The "place_holder" field in Entry and the "Vector<int>" parameter of
   52 // commit_all are there to deal with the fact than when spacing
   53 // options on the command line such as "--key what" it can not be
   54 // determined if "what" should be a the value of "key" or if it should
   55 // be treated as an independent arg.  This is because "key" may
   56 // be a filter option.  Filter options KeyInfo are not loaded until
   57 // after a commit which is not done at the time the options are being
   58 // read in from the command line.  (If the command line arguments are
   59 // read in after the other settings are read in and committed than any
   60 // options setting any of the config files will be ignored.  Thus the
   61 // command line must be parsed and options must be added in an
   62 // uncommitted state).  So the solution is to assume it is an
   63 // independent arg until told otherwise, the position in the arg array
   64 // is stored along with the value in the "place_holder" field.  When
   65 // the config class is finally committed and it is determined that
   66 // "what" is really a value for key the stored arg position is pushed
   67 // onto the Vector<int> so it can be removed from the arg array.  In
   68 // the case of a "lset-*" this will happen in multiple config
   69 // "Entry"s, so care is taken to only add the arg position once.
   70 
   71 namespace acommon {
   72 
   73   const char * const keyinfo_type_name[4] = {
   74     N_("string"), N_("integer"), N_("boolean"), N_("list")
   75   };
   76 
   77   const int Config::num_parms_[9] = {1, 1, 0, 0, 0,
   78                                      1, 1, 1, 0};
   79   
   80   typedef Notifier * NotifierPtr;
   81   
   82   Config::Config(ParmStr name,
   83          const KeyInfo * mainbegin, 
   84          const KeyInfo * mainend)
   85     : name_(name)
   86     , first_(0), insert_point_(&first_)
   87     , committed_(true), attached_(false)
   88     , md_info_list_index(-1)
   89     , settings_read_in_(false)
   90     , load_filter_hook(0)
   91     , filter_mode_notifier(0)
   92   {
   93     keyinfo_begin = mainbegin;
   94     keyinfo_end   = mainend;
   95     extra_begin = 0;
   96     extra_end   = 0;
   97   }
   98 
   99   Config::~Config() {
  100     del();
  101   }
  102 
  103   Config::Config(const Config & other) 
  104   {
  105     copy(other);
  106   }
  107   
  108   Config & Config::operator= (const Config & other)
  109   {
  110     del();
  111     copy(other);
  112     return *this;
  113   }
  114   
  115   Config * Config::clone() const {
  116     return new Config(*this);
  117   }
  118 
  119   void Config::assign(const Config * other) {
  120     *this = *(const Config *)(other);
  121   }
  122 
  123   void Config::copy(const Config & other)
  124   {
  125     name_ = other.name_;
  126 
  127     committed_ = other.committed_;
  128     attached_ = other.attached_;
  129     settings_read_in_ = other.settings_read_in_;
  130 
  131     keyinfo_begin = other.keyinfo_begin;
  132     keyinfo_end   = other.keyinfo_end;
  133     extra_begin   = other.extra_begin;
  134     extra_end     = other.extra_end;
  135     filter_modules = other.filter_modules;
  136 
  137 #ifdef HAVE_LIBDL
  138     filter_modules_ptrs = other.filter_modules_ptrs;
  139     for (Vector<Cacheable *>::iterator i = filter_modules_ptrs.begin();
  140          i != filter_modules_ptrs.end();
  141          ++i)
  142       (*i)->copy();
  143 #endif
  144 
  145     md_info_list_index = other.md_info_list_index;
  146 
  147     insert_point_ = 0;
  148     Entry * const * src  = &other.first_;
  149     Entry * * dest = &first_;
  150     while (*src) 
  151     {
  152       *dest = new Entry(**src);
  153       if (src == other.insert_point_)
  154         insert_point_ = dest;
  155       src  = &((*src)->next);
  156       dest = &((*dest)->next);
  157     }
  158     if (insert_point_ == 0)
  159       insert_point_ = dest;
  160     *dest = 0;
  161 
  162     Vector<Notifier *>::const_iterator i   = other.notifier_list.begin();
  163     Vector<Notifier *>::const_iterator end = other.notifier_list.end();
  164 
  165     for(; i != end; ++i) {
  166       Notifier * tmp = (*i)->clone(this);
  167       if (tmp != 0)
  168     notifier_list.push_back(tmp);
  169     }
  170   }
  171 
  172   void Config::del()
  173   {
  174     while (first_) {
  175       Entry * tmp = first_->next;
  176       delete first_;
  177       first_ = tmp;
  178     }
  179 
  180     Vector<Notifier *>::iterator i   = notifier_list.begin();
  181     Vector<Notifier *>::iterator end = notifier_list.end();
  182 
  183     for(; i != end; ++i) {
  184       delete (*i);
  185       *i = 0;
  186     }
  187     
  188     notifier_list.clear();
  189 
  190 #ifdef HAVE_LIBDL
  191     filter_modules.clear();
  192     for (Vector<Cacheable *>::iterator i = filter_modules_ptrs.begin();
  193          i != filter_modules_ptrs.end();
  194          ++i)
  195       (*i)->release();
  196     filter_modules_ptrs.clear();
  197 #endif
  198   }
  199 
  200   void Config::set_filter_modules(const ConfigModule * modbegin, 
  201                   const ConfigModule * modend)
  202   {
  203     assert(filter_modules_ptrs.empty());
  204     filter_modules.clear();
  205     filter_modules.assign(modbegin, modend);
  206   }
  207 
  208   void Config::set_extra(const KeyInfo * begin, 
  209                    const KeyInfo * end) 
  210   {
  211     extra_begin = begin;
  212     extra_end   = end;
  213   }
  214 
  215   //
  216   //
  217   //
  218 
  219 
  220   //
  221   // Notifier methods
  222   //
  223 
  224   NotifierEnumeration * Config::notifiers() const 
  225   {
  226     return new NotifierEnumeration(notifier_list);
  227   }
  228 
  229   bool Config::add_notifier(Notifier * n) 
  230   {
  231     Vector<Notifier *>::iterator i   = notifier_list.begin();
  232     Vector<Notifier *>::iterator end = notifier_list.end();
  233 
  234     while (i != end && *i != n)
  235       ++i;
  236 
  237     if (i != end) {
  238     
  239       return false;
  240     
  241     } else {
  242 
  243       notifier_list.push_back(n);
  244       return true;
  245 
  246     }
  247   }
  248 
  249   bool Config::remove_notifier(const Notifier * n) 
  250   {
  251     Vector<Notifier *>::iterator i   = notifier_list.begin();
  252     Vector<Notifier *>::iterator end = notifier_list.end();
  253 
  254     while (i != end && *i != n)
  255       ++i;
  256 
  257     if (i == end) {
  258     
  259       return false;
  260     
  261     } else {
  262 
  263       delete *i;
  264       notifier_list.erase(i);
  265       return true;
  266 
  267     }
  268   }
  269 
  270   bool Config::replace_notifier(const Notifier * o, 
  271                       Notifier * n) 
  272   {
  273     Vector<Notifier *>::iterator i   = notifier_list.begin();
  274     Vector<Notifier *>::iterator end = notifier_list.end();
  275 
  276     while (i != end && *i != o)
  277       ++i;
  278 
  279     if (i == end) {
  280     
  281       return false;
  282     
  283     } else {
  284 
  285       delete *i;
  286       *i = n;
  287       return true;
  288 
  289     }
  290   }
  291 
  292   //
  293   // retrieve methods
  294   //
  295 
  296   const Config::Entry * Config::lookup(const char * key) const
  297   {
  298     const Entry * res = 0;
  299     const Entry * cur = first_;
  300 
  301     while (cur) {
  302       if (cur->key == key && cur->action != NoOp)  res = cur;
  303       cur = cur->next;
  304     }
  305 
  306     if (!res || res->action == Reset) return 0;
  307     return res;
  308   }
  309 
  310   bool Config::have(ParmStr key) const 
  311   {
  312     PosibErr<const KeyInfo *> pe = keyinfo(key);
  313     if (pe.has_err()) {pe.ignore_err(); return false;}
  314     return lookup(pe.data->name);
  315   }
  316 
  317   PosibErr<String> Config::retrieve(ParmStr key) const
  318   {
  319     RET_ON_ERR_SET(keyinfo(key), const KeyInfo *, ki);
  320     if (ki->type == KeyInfoList) return make_err(key_not_string, ki->name);
  321 
  322     const Entry * cur = lookup(ki->name);
  323 
  324     return cur ? cur->value : get_default(ki);
  325   }
  326 
  327   PosibErr<Config::Value> Config::retrieve_value(ParmStr key) const
  328   {
  329     RET_ON_ERR_SET(keyinfo(key), const KeyInfo *, ki);
  330     if (ki->type == KeyInfoList) return make_err(key_not_string, ki->name);
  331 
  332     const Entry * cur = lookup(ki->name);
  333 
  334     return cur ? Value(cur->value,cur->secure) : Value(get_default(ki), true);
  335   }
  336   
  337   PosibErr<String> Config::retrieve_any(ParmStr key) const
  338   {
  339     RET_ON_ERR_SET(keyinfo(key), const KeyInfo *, ki);
  340 
  341     if (ki->type != KeyInfoList) {
  342       const Entry * cur = lookup(ki->name);
  343       return cur ? cur->value : get_default(ki);
  344     } else {
  345       StringList sl;
  346       RET_ON_ERR(retrieve_list(key, &sl));
  347       StringListEnumeration els = sl.elements_obj();
  348       const char * s;
  349       String val;
  350       while ( (s = els.next()) != 0 ) {
  351         val += s;
  352         val += '\n';
  353       }
  354       val.pop_back();
  355       return val;
  356     }
  357   }
  358 
  359   PosibErr<bool> Config::retrieve_bool(ParmStr key) const
  360   {
  361     RET_ON_ERR_SET(keyinfo(key), const KeyInfo *, ki);
  362     if (ki->type != KeyInfoBool) return make_err(key_not_bool, ki->name);
  363 
  364     const Entry * cur = lookup(ki->name);
  365 
  366     String value(cur ? cur->value : get_default(ki));
  367 
  368     if (value == "false") return false;
  369     else                  return true;
  370   }
  371   
  372   PosibErr<int> Config::retrieve_int(ParmStr key) const
  373   {
  374     assert(committed_); // otherwise the value may not be an integer
  375                         // as it has not been verified.
  376 
  377     RET_ON_ERR_SET(keyinfo(key), const KeyInfo *, ki);
  378     if (ki->type != KeyInfoInt) return make_err(key_not_int, ki->name);
  379 
  380     const Entry * cur = lookup(ki->name);
  381 
  382     String value(cur ? cur->value : get_default(ki));
  383 
  384     return atoi(value.str());
  385   }
  386 
  387 #define RET_ON_ERR_WRAP(prefix, key, cmd)                                \
  388   do{PosibErrBase pe(cmd);if(pe.has_err())return pe.with_key(prefix, strncmp(key, "f-", 2) == 0 ? key + 2 : key);}while(false)
  389 
  390   PosibErr<void> Config::lookup_list(const KeyInfo * ki,
  391                                      MutableContainer & m,
  392                                      bool include_default) const
  393   {
  394     const Entry * cur = first_;
  395     const Entry * first_to_use = 0;
  396 
  397     while (cur) {
  398       if (cur->key == ki->name && 
  399           (first_to_use == 0 || 
  400            cur->action == Reset || cur->action == Set 
  401            || cur->action == ListClear)) 
  402         first_to_use = cur;
  403       cur = cur->next;
  404     }
  405 
  406     cur = first_to_use;
  407 
  408     if (include_default && 
  409         (!cur || 
  410          !(cur->action == Set || cur->action == ListClear)))
  411     {
  412       String def = get_default(ki);
  413       separate_list(def, m, true);
  414     }
  415 
  416     if (cur && cur->action == Reset) {
  417       cur = cur->next;
  418     }
  419 
  420     if (cur && cur->action == Set) {
  421       if (!include_default) m.clear();
  422       RET_ON_ERR_WRAP("", ki->name, m.add(cur->value));
  423       cur = cur->next;
  424     }
  425 
  426     if (cur && cur->action == ListClear) {
  427       if (!include_default) m.clear();
  428       cur = cur->next;
  429     }
  430 
  431     while (cur) {
  432       if (cur->key == ki->name) {
  433         if (cur->action == ListAdd)
  434           RET_ON_ERR_WRAP("add-", ki->name, m.add(cur->value));
  435         else if (cur->action == ListRemove)
  436           RET_ON_ERR_WRAP("remove-", ki->name, m.remove(cur->value));
  437       }
  438       cur = cur->next;
  439     }
  440     return no_err;
  441   }
  442 
  443 #undef RET_ON_ERR_WRAP
  444 
  445   PosibErr<void> Config::retrieve_list(ParmStr key, 
  446                        MutableContainer * m) const
  447   {
  448     RET_ON_ERR_SET(keyinfo(key), const KeyInfo *, ki);
  449     if (ki->type != KeyInfoList) return make_err(key_not_list, ki->name);
  450 
  451     RET_ON_ERR(lookup_list(ki, *m, true));
  452 
  453     return no_err;
  454   }
  455 
  456   static const KeyInfo * find(ParmStr key, 
  457                   const KeyInfo * i, 
  458                   const KeyInfo * end) 
  459   {
  460     while (i != end) {
  461       if (strcmp(key, i->name) == 0)
  462     return i;
  463       ++i;
  464     }
  465     return i;
  466   }
  467 
  468   static const ConfigModule * find(ParmStr key, 
  469                    const ConfigModule * i, 
  470                    const ConfigModule * end) 
  471   {
  472     while (i != end) {
  473       if (strcmp(key, i->name) == 0)
  474     return i;
  475       ++i;
  476     }
  477     return i;
  478   }
  479 
  480   PosibErr<const KeyInfo *> Config::keyinfo(ParmStr key) const
  481   {
  482     typedef PosibErr<const KeyInfo *> Ret;
  483     {
  484       const KeyInfo * i;
  485       i = acommon::find(key, keyinfo_begin, keyinfo_end);
  486       if (i != keyinfo_end) return Ret(i);
  487       
  488       i = acommon::find(key, extra_begin, extra_end);
  489       if (i != extra_end) return Ret(i);
  490       
  491       const char * s = strncmp(key, "f-", 2) == 0 ? key + 2 : key.str();
  492       const char * h = strchr(s, '-');
  493       if (h == 0) goto err;
  494 
  495       String k(s, h - s);
  496       const ConfigModule * j = acommon::find(k,
  497                          filter_modules.pbegin(),
  498                          filter_modules.pend());
  499       
  500       if (j == filter_modules.pend() && load_filter_hook && committed_) {
  501         // FIXME: This isn't quite right
  502         PosibErrBase pe = load_filter_hook(const_cast<Config *>(this), k);
  503         pe.ignore_err();
  504         j = acommon::find(k,
  505                           filter_modules.pbegin(),
  506                           filter_modules.pend());
  507       }
  508 
  509       if (j == filter_modules.pend()) goto err;
  510 
  511       i = acommon::find(key, j->begin, j->end);
  512       if (i != j->end) return Ret(i);
  513       
  514       if (strncmp(key, "f-", 2) != 0) k = "f-";
  515       else                            k = "";
  516       k += key;
  517       i = acommon::find(k, j->begin, j->end);
  518       if (i != j->end) return Ret(i);
  519     }
  520   err:  
  521     return Ret().prim_err(unknown_key, key);
  522   }
  523 
  524   static bool proc_locale_str(ParmStr lang, String & final_str)
  525   {
  526     if (lang == 0) return false;
  527     const char * i = lang;
  528     if (!(asc_islower(i[0]) && asc_islower(i[1]))) return false;
  529     final_str.assign(i, 2);
  530     i += 2;
  531     if (! (i[0] == '_' || i[0] == '-')) return true;
  532     i += 1;
  533     if (!(asc_isupper(i[0]) && asc_isupper(i[1]))) return true;
  534     final_str += '_';
  535     final_str.append(i, 2);
  536     return true;
  537   }
  538 
  539   static void get_lang_env(String & str) 
  540   {
  541     if (proc_locale_str(getenv("LC_MESSAGES"), str)) return;
  542     if (proc_locale_str(getenv("LANG"), str)) return;
  543     if (proc_locale_str(getenv("LANGUAGE"), str)) return;
  544     str = DEFAULT_LANG;
  545   }
  546 
  547 #ifdef USE_LOCALE
  548 
  549   static void get_lang(String & final_str) 
  550   {
  551     // FIXME: THIS IS NOT THREAD SAFE
  552     String locale = setlocale (LC_ALL, NULL);
  553     if (locale == "C")
  554       setlocale (LC_ALL, "");
  555     const char * lang = setlocale (LC_MESSAGES, NULL);
  556     bool res = proc_locale_str(lang, final_str);
  557     if (locale == "C")
  558       setlocale(LC_MESSAGES, locale.c_str());
  559     if (!res)
  560       get_lang_env(final_str);
  561   }
  562 
  563 #else
  564 
  565   static inline void get_lang(String & str) 
  566   {
  567     get_lang_env(str);
  568   }
  569 
  570 #endif
  571 
  572 #if defined USE_LOCALE && defined HAVE_LANGINFO_CODESET
  573 
  574   static inline void get_encoding(const Config & c, String & final_str)
  575   {
  576     const char * codeset = nl_langinfo(CODESET);
  577     if (ascii_encoding(c, codeset)) codeset = "none";
  578     final_str = codeset;
  579   }
  580 
  581 #else
  582 
  583   static inline void get_encoding(const Config &, String & final_str)
  584   {
  585     final_str = "none";
  586   }
  587 
  588 #endif
  589 
  590   String Config::get_default(const KeyInfo * ki) const
  591   {
  592     bool   in_replace = false;
  593     String final_str;
  594     String replace;
  595     const char * i = ki->def;
  596     if (*i == '!') { // special cases
  597       ++i;
  598     
  599       if (strcmp(i, "lang") == 0) {
  600         
  601         const Entry * entry;
  602         if (entry = lookup("actual-lang"), entry) {
  603           return entry->value;
  604         } else if (have("master")) {
  605       final_str = "<unknown>";
  606     } else {
  607       get_lang(final_str);
  608     }
  609     
  610       } else if (strcmp(i, "encoding") == 0) {
  611 
  612         get_encoding(*this, final_str);
  613 
  614       } else if (strcmp(i, "special") == 0) {
  615 
  616     // do nothing
  617 
  618       } else {
  619       
  620     abort(); // this should not happen
  621       
  622       }
  623     
  624     } else for(; *i; ++i) {
  625     
  626       if (!in_replace) {
  627 
  628     if (*i == '<') {
  629       in_replace = true;
  630     } else {
  631       final_str += *i;
  632     }
  633 
  634       } else { // in_replace
  635       
  636     if (*i == '/' || *i == ':' || *i == '|' || *i == '#' || *i == '^') {
  637       char sep = *i;
  638       String second;
  639       ++i;
  640       while (*i != '\0' && *i != '>') second += *i++;
  641       if (sep == '/') {
  642         String s1 = retrieve(replace);
  643         String s2 = retrieve(second);
  644         final_str += add_possible_dir(s1, s2);
  645       } else if (sep == ':') {
  646         String s1 = retrieve(replace);
  647         final_str += add_possible_dir(s1, second);
  648       } else if (sep == '#') {
  649         String s1 = retrieve(replace);
  650         assert(second.size() == 1);
  651         unsigned int s = 0;
  652         while (s != s1.size() && s1[s] != second[0]) ++s;
  653         final_str.append(s1, s);
  654       } else if (sep == '^') {
  655         String s1 = retrieve(replace);
  656         String s2 = retrieve(second);
  657         final_str += figure_out_dir(s1, s2);
  658       } else { // sep == '|'
  659         assert(replace[0] == '$');
  660         const char * env = getenv(replace.c_str()+1);
  661         final_str += env ? env : second;
  662       }
  663       replace = "";
  664       in_replace = false;
  665 
  666     } else if (*i == '>') {
  667 
  668       final_str += retrieve(replace).data;
  669       replace = "";
  670       in_replace = false;
  671 
  672     } else {
  673 
  674       replace += *i;
  675 
  676     }
  677 
  678       }
  679       
  680     }
  681     return final_str;
  682   }
  683 
  684   PosibErr<String> Config::get_default(ParmStr key) const
  685   {
  686     RET_ON_ERR_SET(keyinfo(key), const KeyInfo *, ki);
  687     return get_default(ki);
  688   }
  689 
  690 
  691 
  692 #define TEST(v,l,a)                         \
  693   do {                                      \
  694     if (len == l && memcmp(s, v, l) == 0) { \
  695       if (action) *action = a;              \
  696       return c + 1;                         \
  697     }                                       \
  698   } while (false)
  699 
  700   const char * Config::base_name(const char * s, Action * action)
  701   {
  702     if (action) *action = Set;
  703     const char * c = strchr(s, '-');
  704     if (!c) return s;
  705     unsigned len = c - s;
  706     TEST("reset",   5, Reset);
  707     TEST("enable",  6, Enable);
  708     TEST("dont",    4, Disable);
  709     TEST("disable", 7, Disable);
  710     TEST("lset",    4, ListSet);
  711     TEST("rem",     3, ListRemove);
  712     TEST("remove",  6, ListRemove);
  713     TEST("add",     3, ListAdd);
  714     TEST("clear",   5, ListClear);
  715     return s;
  716   }
  717 
  718 #undef TEST
  719 
  720   void separate_list(ParmStr value, AddableContainer & out, bool do_unescape)
  721   {
  722     unsigned len = value.size();
  723     
  724     VARARRAY(char, buf, len + 1);
  725     memcpy(buf, value, len + 1);
  726     
  727     len = strlen(buf);
  728     char * s = buf;
  729     char * end = buf + len;
  730       
  731     while (s < end)
  732     {
  733       if (do_unescape) while (*s == ' ' || *s == '\t') ++s;
  734       char * b = s;
  735       char * e = s;
  736       while (*s != '\0') {
  737         if (do_unescape && *s == '\\') {
  738           ++s;
  739           if (*s == '\0') break;
  740           e = s;
  741           ++s;
  742         } else {
  743           if (*s == ':') break;
  744           if (!do_unescape || (*s != ' ' && *s != '\t')) e = s;
  745           ++s;
  746         }
  747       }
  748       if (s != b) {
  749         ++e;
  750         *e = '\0';
  751         if (do_unescape) unescape(b);
  752       
  753         out.add(b);
  754       }
  755       ++s;
  756     }
  757   }
  758 
  759   void combine_list(String & res, const StringList & in)
  760   {
  761     res.clear();
  762     StringListEnumeration els = in.elements_obj();
  763     const char * s = 0;
  764     while ( (s = els.next()) != 0) 
  765     {
  766       for (; *s; ++s) {
  767         if (*s == ':')
  768           res.append('\\');
  769         res.append(*s);
  770       }
  771       res.append(':');
  772     }
  773     if (!res.empty() && res.back() == ':') res.pop_back();
  774   }
  775 
  776   struct ListAddHelper : public AddableContainer 
  777   {
  778     Config * config;
  779     Config::Entry * orig_entry;
  780     PosibErr<bool> add(ParmStr val);
  781   };
  782 
  783   PosibErr<bool> ListAddHelper::add(ParmStr val)
  784   {
  785     Config::Entry * entry = new Config::Entry(*orig_entry);
  786     entry->value = val;
  787     entry->action = Config::ListAdd;
  788     config->set(entry);
  789     return true;
  790   }
  791 
  792   void Config::replace_internal(ParmStr key, ParmStr value)
  793   {
  794     Entry * entry = new Entry;
  795     entry->key = key;
  796     entry->value = value;
  797     entry->action = Set;
  798     entry->next = *insert_point_;
  799     *insert_point_ = entry;
  800     insert_point_ = &entry->next;
  801   }
  802 
  803   PosibErr<void> Config::replace(ParmStr key, ParmStr value)
  804   {
  805     Entry * entry = new Entry;
  806     entry->key = key;
  807     entry->value = value;
  808     entry->secure = true;
  809     return set(entry);
  810   }
  811   
  812   PosibErr<void> Config::remove(ParmStr key)
  813   {
  814     Entry * entry = new Entry;
  815     entry->key = key;
  816     entry->action = Reset;
  817     return set(entry);
  818   }
  819 
  820   PosibErr<void> Config::set(Entry * entry0, bool do_unescape)
  821   {
  822     StackPtr<Entry> entry(entry0);
  823 
  824     if (entry->action == NoOp)
  825       entry->key = base_name(entry->key.str(), &entry->action);
  826 
  827     if (num_parms(entry->action) == 0 && !entry->value.empty()) 
  828     {
  829       if (entry->place_holder == -1) {
  830         switch (entry->action) {
  831         case Reset:
  832           return make_err(no_value_reset, entry->key);
  833         case Enable:
  834           return make_err(no_value_enable, entry->key);
  835         case Disable:
  836           return make_err(no_value_disable, entry->key);
  837         case ListClear:
  838           return make_err(no_value_clear, entry->key);
  839         default:
  840           abort(); // this shouldn't happen
  841         }
  842       } else {
  843         entry->place_holder = -1;
  844       }
  845     }
  846 
  847     if (entry->action != ListSet) {
  848 
  849       switch (entry->action) {
  850       case Enable:
  851         entry->value = "true";
  852         entry->action = Set;
  853         break;
  854       case Disable:
  855         entry->value = "false";
  856         entry->action = Set;
  857         break;
  858       default:
  859         ;
  860       }
  861       if (do_unescape) unescape(entry->value.mstr());
  862 
  863       entry->next = *insert_point_;
  864       *insert_point_ = entry;
  865       insert_point_ = &entry->next;
  866       entry.release();
  867       if (committed_) RET_ON_ERR(commit(entry0)); // entry0 == entry
  868       
  869     } else { // action == ListSet
  870 
  871       Entry * ent = new Entry;
  872       ent->key = entry->key;
  873       ent->action = ListClear;
  874       RET_ON_ERR(set(ent));
  875 
  876       ListAddHelper helper;
  877       helper.config = this;
  878       helper.orig_entry = entry;
  879 
  880       separate_list(entry->value.str(), helper, do_unescape);
  881     }
  882     return no_err;
  883   }
  884 
  885   PosibErr<void> Config::merge(const Config & other)
  886   {
  887     for (const Entry * src  = other.first_; src; src = src->next)
  888     {
  889       if (src->action == NoOp) continue;
  890       Entry * entry = new Entry(*src);
  891       entry->next = *insert_point_;
  892       *insert_point_ = entry;
  893       insert_point_ = &entry->next;
  894       if (committed_) RET_ON_ERR(commit(entry));
  895     }
  896     return no_err;
  897   }
  898 
  899   PosibErr<void> Config::lang_config_merge(const Config & other,
  900                                            int which, ParmStr data_encoding)
  901   {
  902     Conv to_utf8;
  903     RET_ON_ERR(to_utf8.setup(*this, data_encoding, "utf-8", NormTo));
  904     const Entry * src  = other.first_;
  905     Entry * * ip = &first_;
  906     while (src)
  907     {
  908       const KeyInfo * l_ki = other.keyinfo(src->key);
  909       if (l_ki->other_data == which) {
  910         const KeyInfo * c_ki = keyinfo(src->key);
  911         Entry * entry = new Entry(*src);
  912         if (c_ki->flags & KEYINFO_UTF8)
  913           entry->value = to_utf8(entry->value);
  914         entry->next = *ip;
  915         *ip = entry;
  916         ip = &entry->next;
  917       }
  918       src = src->next;
  919     }
  920     return no_err;
  921   }
  922 
  923 
  924 #define NOTIFY_ALL(fun)                                       \
  925   do {                                                        \
  926     Vector<Notifier *>::iterator   i = notifier_list.begin(); \
  927     Vector<Notifier *>::iterator end = notifier_list.end();   \
  928     while (i != end) {                                        \
  929       RET_ON_ERR((*i)->fun);                                  \
  930       ++i;                                                    \
  931     }                                                         \
  932   } while (false)
  933 
  934   PosibErr<int> Config::commit(Entry * entry, Conv * conv) 
  935   {
  936     PosibErr<const KeyInfo *> pe = keyinfo(entry->key);
  937     {
  938       if (pe.has_err()) goto error;
  939       
  940       const KeyInfo * ki = pe;
  941 
  942       entry->key = ki->name;
  943       
  944       // FIXME: This is the correct thing to do but it causes problems
  945       //        with changing a filter mode in "pipe" mode and probably
  946       //        elsewhere.
  947       //if (attached_ && !(ki->flags & KEYINFO_MAY_CHANGE)) {
  948       //  pe = make_err(cant_change_value, entry->key);
  949       //  goto error;
  950       //}
  951 
  952       int place_holder = entry->place_holder;
  953       
  954       if (conv && ki->flags & KEYINFO_UTF8)
  955         entry->value = (*conv)(entry->value);
  956 
  957       if (ki->type != KeyInfoList && list_action(entry->action)) {
  958         pe = make_err(key_not_list, entry->key);
  959         goto error;
  960       }
  961 
  962       if (!ki->def) // if null this key should never have values
  963                     // directly added to it
  964         return make_err(aerror_cant_change_value, entry->key);
  965 
  966       String value(entry->action == Reset ? get_default(ki) : entry->value);
  967       
  968       switch (ki->type) {
  969         
  970       case KeyInfoBool: {
  971 
  972         bool val;
  973       
  974         if  (value.empty() || entry->place_holder != -1) {
  975           // if entry->place_holder != -1 than IGNORE the value no
  976           // matter what it is
  977           entry->value = "true";
  978           val = true;
  979           place_holder = -1;
  980         } else if (value == "true") {
  981           val = true;
  982         } else if (value == "false") {
  983           val = false;
  984         } else {
  985           pe = make_err(bad_value, entry->key, value,
  986                         /* TRANSLATORS: "true" and "false" are literal
  987                          * values and should not be translated.*/
  988                         _("either \"true\" or \"false\""));
  989           goto error;
  990         }
  991 
  992         NOTIFY_ALL(item_updated(ki, val));
  993         break;
  994         
  995       } case KeyInfoString:
  996         
  997         NOTIFY_ALL(item_updated(ki, value));
  998         break;
  999         
 1000       case KeyInfoInt: 
 1001       {
 1002         int num;
 1003         
 1004         if (sscanf(value.str(), "%i", &num) == 1 && num >= 0) {
 1005           NOTIFY_ALL(item_updated(ki, num));
 1006         } else {
 1007           pe = make_err(bad_value, entry->key, value, _("a positive integer"));
 1008           goto error;
 1009         }
 1010         
 1011         break;
 1012       }
 1013       case KeyInfoList:
 1014         
 1015         NOTIFY_ALL(list_updated(ki));
 1016         break;
 1017         
 1018       }
 1019       return place_holder;
 1020     }
 1021   error:
 1022     entry->action = NoOp;
 1023     if (!entry->file.empty())
 1024       return pe.with_file(entry->file, entry->line_num);
 1025     else
 1026       return (PosibErrBase &)pe;
 1027   }
 1028 
 1029 #undef NOTIFY_ALL
 1030 
 1031 
 1032   /////////////////////////////////////////////////////////////////////
 1033   /////////////////////////////////////////////////////////////////////
 1034 
 1035   class PossibleElementsEmul : public KeyInfoEnumeration
 1036   {
 1037   private:
 1038     bool include_extra;
 1039     bool include_modules;
 1040     bool module_changed;
 1041     const Config * cd;
 1042     const KeyInfo * i;
 1043     const ConfigModule * m;
 1044   public:
 1045     PossibleElementsEmul(const Config * d, bool ic, bool im)
 1046       : include_extra(ic), include_modules(im), 
 1047         module_changed(false), cd(d), i(d->keyinfo_begin), m(0) {}
 1048 
 1049     KeyInfoEnumeration * clone() const {
 1050       return new PossibleElementsEmul(*this);
 1051     }
 1052 
 1053     void assign(const KeyInfoEnumeration * other) {
 1054       *this = *(const PossibleElementsEmul *)(other);
 1055     }
 1056 
 1057     virtual bool active_filter_module_changed(void) {
 1058       return module_changed;
 1059     }
 1060 
 1061     const char * active_filter_module_name(void){
 1062       if (m != 0)
 1063         return m->name;
 1064       return "";
 1065     }
 1066 
 1067     virtual const char * active_filter_module_desc(void) {
 1068       if (m != 0)
 1069         return m->desc;
 1070       return "";
 1071     }
 1072 
 1073     const KeyInfo * next() {
 1074       if (i == cd->keyinfo_end) {
 1075     if (include_extra)
 1076       i = cd->extra_begin;
 1077     else
 1078       i = cd->extra_end;
 1079       }
 1080       
 1081       module_changed = false;
 1082       if (i == cd->extra_end) {
 1083     m = cd->filter_modules.pbegin();
 1084     if (!include_modules || m == cd->filter_modules.pend()) return 0;
 1085     else {
 1086           i = m->begin;
 1087           module_changed = true;
 1088         }
 1089       }
 1090 
 1091       if (m == 0){
 1092     return i++;
 1093       }
 1094 
 1095       if (m == cd->filter_modules.pend()){
 1096     return 0;
 1097       }
 1098 
 1099       while (i == m->end) {
 1100     ++m;
 1101     if (m == cd->filter_modules.pend()) return 0;
 1102     else {
 1103           i = m->begin;
 1104           module_changed = true;
 1105         }
 1106       }
 1107 
 1108       return i++;
 1109     }
 1110 
 1111     bool at_end() const {
 1112       return (m == cd->filter_modules.pend());
 1113     }
 1114   };
 1115 
 1116   KeyInfoEnumeration *
 1117   Config::possible_elements(bool include_extra, bool include_modules) const
 1118   {
 1119     return new PossibleElementsEmul(this, include_extra, include_modules);
 1120   }
 1121 
 1122   struct ListDefaultDump : public AddableContainer 
 1123   {
 1124     OStream & out;
 1125     bool first;
 1126     const char * first_prefix;
 1127     unsigned num_blanks;
 1128     ListDefaultDump(OStream & o);
 1129     PosibErr<bool> add(ParmStr d);
 1130   };
 1131   
 1132   ListDefaultDump::ListDefaultDump(OStream & o) 
 1133     : out(o), first(false)
 1134   {
 1135     first_prefix = _("# default: ");
 1136     num_blanks = strlen(first_prefix) - 1;
 1137   }
 1138 
 1139   PosibErr<bool> ListDefaultDump::add(ParmStr d) 
 1140   {
 1141     if (first) {
 1142       out.write(first_prefix);
 1143     } else {
 1144       out.put('#');
 1145       for (unsigned i = 0; i != num_blanks; ++i)
 1146         out.put(' ');
 1147     }
 1148     VARARRAY(char, buf, d.size() * 2 + 1);
 1149     escape(buf, d);
 1150     out.printl(buf);
 1151     first = false;
 1152     return true;
 1153   }
 1154 
 1155   class ListDump : public MutableContainer 
 1156   {
 1157     OStream & out;
 1158     const char * name;
 1159   public:
 1160     ListDump(OStream & o, ParmStr n) 
 1161       : out(o), name(n) {}
 1162     PosibErr<bool> add(ParmStr d);
 1163     PosibErr<bool> remove(ParmStr d);
 1164     PosibErr<void> clear();
 1165   };
 1166 
 1167   PosibErr<bool> ListDump::add(ParmStr d) {
 1168     VARARRAY(char, buf, d.size() * 2 + 1);
 1169     escape(buf, d);
 1170     out.printf("add-%s %s\n", name, buf);
 1171     return true;
 1172   }
 1173   PosibErr<bool> ListDump::remove(ParmStr d) {
 1174     VARARRAY(char, buf, d.size() * 2 + 1);
 1175     escape(buf, d);
 1176     out.printf("remove-%s %s\n", name, buf);
 1177     return true;
 1178   }
 1179   PosibErr<void> ListDump::clear() {
 1180     out.printf("clear-%s\n", name);
 1181     return no_err;
 1182   }
 1183 
 1184   void Config::write_to_stream(OStream & out, 
 1185                    bool include_extra) 
 1186   {
 1187     KeyInfoEnumeration * els = possible_elements(include_extra);
 1188     const KeyInfo * i;
 1189     String buf;
 1190     String obuf;
 1191     String def;
 1192     bool have_value;
 1193 
 1194     while ((i = els->next()) != 0) {
 1195       if (i->desc == 0) continue;
 1196 
 1197       if (els->active_filter_module_changed()) {
 1198         out.printf(_("\n"
 1199                      "#######################################################################\n"
 1200                      "#\n"
 1201                      "# Filter: %s\n"
 1202                      "#   %s\n"
 1203                      "#\n"
 1204                      "# configured as follows:\n"
 1205                      "\n"),
 1206                    els->active_filter_module_name(),
 1207                    _(els->active_filter_module_desc()));
 1208       }
 1209 
 1210       obuf.clear();
 1211       have_value = false;
 1212 
 1213       obuf.printf("# %s (%s)\n#   %s\n",
 1214                   i->name, _(keyinfo_type_name[i->type]), _(i->desc));
 1215       if (i->def != 0) {
 1216     if (i->type != KeyInfoList) {
 1217           buf.resize(strlen(i->def) * 2 + 1);
 1218           escape(buf.data(), i->def);
 1219           obuf.printf("# default: %s", buf.data());
 1220           def = get_default(i);
 1221           if (def != i->def) {
 1222             buf.resize(def.size() * 2 + 1);
 1223             escape(buf.data(), def.str());
 1224             obuf.printf(" = %s", buf.data());
 1225           }
 1226           obuf << '\n';
 1227           const Entry * entry = lookup(i->name);
 1228       if (entry) {
 1229             have_value = true;
 1230             buf.resize(entry->value.size() * 2 + 1);
 1231             escape(buf.data(), entry->value.str());
 1232         obuf.printf("%s %s\n", i->name, buf.data());
 1233           }
 1234     } else {
 1235           unsigned s = obuf.size();
 1236           ListDump ld(obuf, i->name);
 1237           lookup_list(i, ld, false);
 1238           have_value = s != obuf.size();
 1239     }
 1240       }
 1241       obuf << '\n';
 1242       if (!(i->flags & KEYINFO_HIDDEN) || have_value)
 1243         out.write(obuf);
 1244     }
 1245     delete els;
 1246   }
 1247 
 1248   PosibErr<void> Config::read_in(IStream & in, ParmStr id) 
 1249   {
 1250     String buf;
 1251     DataPair dp;
 1252     while (getdata_pair(in, dp, buf)) {
 1253       to_lower(dp.key);
 1254       Entry * entry = new Entry;
 1255       entry->key = dp.key;
 1256       entry->value = dp.value;
 1257       entry->file = id;
 1258       entry->line_num = dp.line_num;
 1259       RET_ON_ERR(set(entry, true));
 1260     }
 1261     return no_err;
 1262   }
 1263 
 1264   PosibErr<void> Config::read_in_file(ParmStr file) {
 1265     FStream in;
 1266     RET_ON_ERR(in.open(file, "r"));
 1267     return read_in(in, file);
 1268   }
 1269 
 1270   PosibErr<void> Config::read_in_string(ParmStr str, const char * what) {
 1271     StringIStream in(str);
 1272     return read_in(in, what);
 1273   }
 1274 
 1275 
 1276   PosibErr<bool> Config::read_in_settings(const Config * other)
 1277   {
 1278     if (settings_read_in_) return false;
 1279 
 1280     bool was_committed = committed_;
 1281     set_committed_state(false);
 1282 
 1283     if (other && other->settings_read_in_) {
 1284 
 1285       assert(empty());
 1286       del(); // to clean up any notifiers and similar stuff
 1287       copy(*other);
 1288 
 1289     } else {
 1290 
 1291       if (other) merge(*other);
 1292 
 1293       const char * env = getenv("ASPELL_CONF");
 1294       if (env != 0) { 
 1295         insert_point_ = &first_;
 1296         RET_ON_ERR(read_in_string(env, _("ASPELL_CONF env var")));
 1297       }
 1298       
 1299       {
 1300         insert_point_ = &first_;
 1301         PosibErrBase pe = read_in_file(retrieve("per-conf-path"));
 1302         if (pe.has_err() && !pe.has_err(cant_read_file)) return pe;
 1303       }
 1304       
 1305       {
 1306         insert_point_ = &first_;
 1307         PosibErrBase pe = read_in_file(retrieve("conf-path"));
 1308         if (pe.has_err() && !pe.has_err(cant_read_file)) return pe;
 1309       }
 1310 
 1311       if (was_committed)
 1312         RET_ON_ERR(commit_all());
 1313 
 1314       settings_read_in_ = true;
 1315     }
 1316 
 1317     return true;
 1318   }
 1319 
 1320   PosibErr<void> Config::commit_all(Vector<int> * phs, const char * codeset)
 1321   {
 1322     committed_ = true;
 1323     Entry * uncommited = first_;
 1324     first_ = 0;
 1325     insert_point_ = &first_;
 1326     Conv to_utf8;
 1327     if (codeset)
 1328       RET_ON_ERR(to_utf8.setup(*this, codeset, "utf-8", NormTo));
 1329     PosibErr<void> ret;
 1330     while (uncommited) {
 1331       Entry * cur = uncommited;
 1332       uncommited = cur->next;
 1333       cur->next = 0;
 1334       *insert_point_ = cur;
 1335       insert_point_ = &((*insert_point_)->next);
 1336       PosibErr<int> pe = commit(cur, codeset ? &to_utf8 : 0);
 1337       if (pe.has_err()) {
 1338         if (ret.has_err())
 1339           pe.ignore_err();
 1340         else
 1341           ret = pe;
 1342         continue;
 1343       }
 1344       int place_holder = pe.data;
 1345       if (phs && place_holder != -1 && (phs->empty() || phs->back() != place_holder))
 1346         phs->push_back(place_holder);
 1347     }
 1348     return ret;
 1349   }
 1350 
 1351   PosibErr<void> Config::set_committed_state(bool val) {
 1352     if (val && !committed_) {
 1353       RET_ON_ERR(commit_all());
 1354     } else if (!val && committed_) {
 1355       assert(empty());
 1356       committed_ = false;
 1357     }
 1358     return no_err;
 1359   }
 1360 
 1361 
 1362 #ifdef ENABLE_WIN32_RELOCATABLE
 1363 #  define HOME_DIR "<prefix>"
 1364 #  define PERSONAL "<lang>.pws"
 1365 #  define REPL     "<lang>.prepl"
 1366 #else
 1367 #  define HOME_DIR "<$HOME|./>"
 1368 #  define PERSONAL ".aspell.<lang>.pws"
 1369 #  define REPL     ".aspell.<lang>.prepl"
 1370 #endif
 1371 
 1372   static const KeyInfo config_keys[] = {
 1373     // the description should be under 50 chars
 1374     {"actual-dict-dir", KeyInfoString, "<dict-dir^master>", 0}
 1375     , {"actual-lang",     KeyInfoString, "", 0} 
 1376     , {"conf",     KeyInfoString, "aspell.conf",
 1377        /* TRANSLATORS: The remaining strings in config.cpp should be kept
 1378           under 50 characters, begin with a lower case character and not
 1379           include any trailing punctuation marks. */
 1380        N_("main configuration file")}
 1381     , {"conf-dir", KeyInfoString, CONF_DIR,
 1382        N_("location of main configuration file")}
 1383     , {"conf-path",     KeyInfoString, "<conf-dir/conf>", 0}
 1384     , {"data-dir", KeyInfoString, DATA_DIR,
 1385        N_("location of language data files")}
 1386     , {"dict-alias", KeyInfoList, "",
 1387        N_("create dictionary aliases")}
 1388     , {"dict-dir", KeyInfoString, DICT_DIR,
 1389        N_("location of the main word list")}
 1390     , {"encoding",   KeyInfoString, "!encoding",
 1391        N_("encoding to expect data to be in"), KEYINFO_COMMON}
 1392     , {"filter",   KeyInfoList  , "url",
 1393        N_("add or removes a filter"), KEYINFO_MAY_CHANGE}
 1394     , {"filter-path", KeyInfoList, DICT_DIR,
 1395        N_("path(s) aspell looks for filters")}
 1396     //, {"option-path", KeyInfoList, DATA_DIR,
 1397     //   N_("path(s) aspell looks for options descriptions")}
 1398     , {"mode",     KeyInfoString, "url",
 1399        N_("filter mode"), KEYINFO_COMMON}
 1400     , {"extra-dicts", KeyInfoList, "",
 1401        N_("extra dictionaries to use")}
 1402     , {"wordlists", KeyInfoList, "",
 1403        N_("files with list of extra words to accept")}
 1404     , {"home-dir", KeyInfoString, HOME_DIR,
 1405        N_("location for personal files")}
 1406     , {"ignore",   KeyInfoInt   , "1",
 1407        N_("ignore words <= n chars"), KEYINFO_MAY_CHANGE}
 1408     , {"ignore-accents" , KeyInfoBool, "false",
 1409        /* TRANSLATORS: It is OK if this is longer than 50 chars */
 1410        N_("ignore accents when checking words -- CURRENTLY IGNORED"), KEYINFO_MAY_CHANGE | KEYINFO_HIDDEN}
 1411     , {"ignore-case", KeyInfoBool  , "false",
 1412        N_("ignore case when checking words"), KEYINFO_MAY_CHANGE}
 1413     , {"ignore-repl", KeyInfoBool  , "false",
 1414        N_("ignore commands to store replacement pairs"), KEYINFO_MAY_CHANGE}
 1415     , {"jargon",     KeyInfoString, "",
 1416        N_("extra information for the word list"), KEYINFO_HIDDEN}
 1417     , {"keyboard", KeyInfoString, "standard",
 1418        N_("keyboard definition to use for typo analysis")}
 1419     , {"lang", KeyInfoString, "<language-tag>",
 1420        N_("language code"), KEYINFO_COMMON}
 1421     , {"language-tag", KeyInfoString, "!lang",
 1422        N_("deprecated, use lang instead"), KEYINFO_HIDDEN}
 1423     , {"local-data-dir", KeyInfoString, "<actual-dict-dir>",
 1424        N_("location of local language data files")     }
 1425     , {"master",        KeyInfoString, "<lang>",
 1426        N_("base name of the main dictionary to use"), KEYINFO_COMMON}
 1427     , {"master-flags",  KeyInfoString, "", 0}
 1428     , {"master-path",   KeyInfoString, "<dict-dir/master>",   0}
 1429     , {"module",        KeyInfoString, "default",
 1430        N_("set module name"), KEYINFO_HIDDEN}
 1431     , {"module-search-order", KeyInfoList, "",
 1432        N_("search order for modules"), KEYINFO_HIDDEN}
 1433     , {"normalize", KeyInfoBool, "true",
 1434        N_("enable Unicode normalization")}
 1435     , {"norm-required", KeyInfoBool, "false",
 1436        N_("Unicode normalization required for current lang")}
 1437     , {"norm-form", KeyInfoString, "nfc",
 1438        /* TRANSLATORS: the values after the ':' are literal
 1439           values and should not be translated. */
 1440        N_("Unicode normalization form: none, nfd, nfc, comp")}
 1441     , {"norm-strict", KeyInfoBool, "false",
 1442        N_("avoid lossy conversions when normalization")}
 1443     , {"per-conf", KeyInfoString, ".aspell.conf",
 1444        N_("personal configuration file")}
 1445     , {"per-conf-path", KeyInfoString, "<home-dir/per-conf>", 0}
 1446     , {"personal", KeyInfoString, PERSONAL,
 1447        N_("personal dictionary file name")}
 1448     , {"personal-path", KeyInfoString, "<home-dir/personal>", 0}
 1449     , {"prefix",   KeyInfoString, PREFIX,
 1450        N_("prefix directory")}
 1451     , {"repl",     KeyInfoString, REPL,
 1452        N_("replacements list file name") }
 1453     , {"repl-path",     KeyInfoString, "<home-dir/repl>",     0}
 1454     , {"run-together",        KeyInfoBool,  "false",
 1455        N_("consider run-together words legal"), KEYINFO_MAY_CHANGE}
 1456     , {"run-together-limit",  KeyInfoInt,   "2",
 1457        N_("maximum number that can be strung together"), KEYINFO_MAY_CHANGE}
 1458     , {"run-together-min",    KeyInfoInt,   "3",
 1459        N_("minimal length of interior words"), KEYINFO_MAY_CHANGE}
 1460     , {"camel-case", KeyInfoBool,  "false",
 1461        N_("consider camel case words legal"), KEYINFO_MAY_CHANGE}
 1462     , {"save-repl", KeyInfoBool  , "true",
 1463        N_("save replacement pairs on save all")}
 1464     , {"set-prefix", KeyInfoBool, "true",
 1465        N_("set the prefix based on executable location")}
 1466     , {"size",          KeyInfoString, "+60",
 1467        N_("size of the word list")}
 1468     , {"spelling",   KeyInfoString, "",
 1469        N_("no longer used"), KEYINFO_HIDDEN}
 1470     , {"sug-mode",   KeyInfoString, "normal",
 1471        N_("suggestion mode"), KEYINFO_MAY_CHANGE | KEYINFO_COMMON}
 1472     , {"sug-typo-analysis", KeyInfoBool, "true",
 1473        /* TRANSLATORS: "sug-mode" is a literal value and should not be
 1474           translated. */
 1475        N_("use typo analysis, override sug-mode default")}
 1476     , {"sug-repl-table", KeyInfoBool, "true",
 1477        N_("use replacement tables, override sug-mode default")}
 1478     , {"sug-split-char", KeyInfoList, "\\ :-",
 1479        N_("characters to insert when a word is split"), KEYINFO_UTF8}
 1480     , {"use-other-dicts", KeyInfoBool, "true",
 1481        N_("use personal, replacement & session dictionaries")}
 1482     , {"variety", KeyInfoList, "",
 1483        N_("extra information for the word list")}
 1484     , {"word-list-path", KeyInfoList, DATA_DIR,
 1485        N_("search path for word list information files"), KEYINFO_HIDDEN}
 1486     , {"warn", KeyInfoBool, "true",
 1487        N_("enable warnings")}
 1488     
 1489     
 1490     //
 1491     // These options are generally used when creating dictionaries
 1492     // and may also be specified in the language data file
 1493     //
 1494 
 1495     , {"affix-char",          KeyInfoString, "/", // FIXME: Implement
 1496        /* TRANSLATORS: It is OK if this is longer than 50 chars */
 1497        N_("indicator for affix flags in word lists -- CURRENTLY IGNORED"), KEYINFO_UTF8 | KEYINFO_HIDDEN}
 1498     , {"affix-compress", KeyInfoBool, "false",
 1499        N_("use affix compression when creating dictionaries")}
 1500     , {"clean-affixes", KeyInfoBool, "true",
 1501        N_("remove invalid affix flags")}
 1502     , {"clean-words", KeyInfoBool, "false",
 1503        N_("attempts to clean words so that they are valid")}
 1504     , {"invisible-soundslike", KeyInfoBool, "false",
 1505        N_("compute soundslike on demand rather than storing")} 
 1506     , {"partially-expand",  KeyInfoBool, "false",
 1507        N_("partially expand affixes for better suggestions")}
 1508     , {"skip-invalid-words",  KeyInfoBool, "true",
 1509        N_("skip invalid words")}
 1510     , {"validate-affixes", KeyInfoBool, "true",
 1511        N_("check if affix flags are valid")}
 1512     , {"validate-words", KeyInfoBool, "true",
 1513        N_("check if words are valid")}
 1514     
 1515     //
 1516     // These options are specific to the "aspell" utility.  They are
 1517     // here so that they can be specified in configuration files.
 1518     //
 1519     , {"backup",  KeyInfoBool, "true",
 1520        N_("create a backup file by appending \".bak\"")}
 1521     , {"byte-offsets", KeyInfoBool, "false",
 1522        N_("use byte offsets instead of character offsets")}
 1523     , {"guess", KeyInfoBool, "false",
 1524        N_("create missing root/affix combinations"), KEYINFO_MAY_CHANGE}
 1525     , {"keymapping", KeyInfoString, "aspell",
 1526        N_("keymapping for check mode: \"aspell\" or \"ispell\"")}
 1527     , {"reverse", KeyInfoBool, "false",
 1528        N_("reverse the order of the suggest list")}
 1529     , {"suggest", KeyInfoBool, "true",
 1530        N_("suggest possible replacements"), KEYINFO_MAY_CHANGE}
 1531     , {"time"   , KeyInfoBool, "false",
 1532        N_("time load time and suggest time in pipe mode"), KEYINFO_MAY_CHANGE}
 1533     };
 1534 
 1535   const KeyInfo * config_impl_keys_begin = config_keys;
 1536   const KeyInfo * config_impl_keys_end   
 1537   = config_keys + sizeof(config_keys)/sizeof(KeyInfo);
 1538 
 1539   Config * new_basic_config() { 
 1540     aspell_gettext_init();
 1541     return new Config("aspell",
 1542               config_impl_keys_begin,
 1543               config_impl_keys_end);
 1544   }
 1545   
 1546 }
 1547