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