// This file is part of The New Aspell // Copyright (C) 2001 by Kevin Atkinson under the GNU LGPL license // version 2.0 or 2.1. You should have received a copy of the LGPL // license along with this library if you did not you can find // it at http://www.gnu.org/. #include #include #include #include #include "dirs.h" #include "settings.h" #include "asc_ctype.hpp" #include "config.hpp" #include "errors.hpp" #include "file_util.hpp" #include "fstream.hpp" #include "getdata.hpp" #include "itemize.hpp" #include "mutable_container.hpp" #include "posib_err.hpp" #include "string_map.hpp" #define DEFAULT_LANG "en_US" namespace acommon { static const ConfigModule a_module = ConfigModule(); typedef Notifier * NotifierPtr; Config::Config(ParmString name, const KeyInfo * mainbegin, const KeyInfo * mainend) : name_(name) , attached_(0) , md_info_list_index(-1) { kmi.main_begin = mainbegin; kmi.main_end = mainend; kmi.extra_begin = 0; kmi.extra_end = 0; kmi.modules_begin = &a_module; kmi.modules_end = &a_module; } Config::~Config() { del_notifiers(); } Config::Config(const Config & other) : name_(other.name_), data_(other.data_), attached_(0), kmi(other.kmi), md_info_list_index(other.md_info_list_index) { copy_notifiers(other); } Config & Config::operator= (const Config & other) { attached_ = 0; kmi = other.kmi; data_ = other.data_; md_info_list_index = other.md_info_list_index; copy_notifiers(other); return *this; } Config * Config::clone() const { return new Config(*this); } void Config::assign(const Config * other) { *this = *(const Config *)(other); } void Config::set_modules(const ConfigModule * modbegin, const ConfigModule * modend) { kmi.modules_begin = modbegin; kmi.modules_end = modend; } void Config::set_extra(const KeyInfo * begin, const KeyInfo * end) { kmi.extra_begin = begin; kmi.extra_end = end; } // // Notifier methods // NotifierEnumeration * Config::notifiers() const { return new NotifierEnumeration(notifier_list); } void Config::copy_notifiers(const Config & other) { notifier_list.clear(); Vector::const_iterator i = other.notifier_list.begin(); Vector::const_iterator end = other.notifier_list.end(); for(; i != end; ++i) { Notifier * tmp = (*i)->clone(this); if (tmp != 0) notifier_list.push_back(tmp); } } void Config::del_notifiers() { Vector::iterator i = notifier_list.begin(); Vector::iterator end = notifier_list.end(); for(; i != end; ++i) { delete (*i); *i = 0; } notifier_list.clear(); } bool Config::add_notifier(Notifier * n) { Vector::iterator i = notifier_list.begin(); Vector::iterator end = notifier_list.end(); while (i != end && *i != n) ++i; if (i != end) { return false; } else { notifier_list.push_back(n); return true; } } bool Config::remove_notifier(const Notifier * n) { Vector::iterator i = notifier_list.begin(); Vector::iterator end = notifier_list.end(); while (i != end && *i != n) ++i; if (i == end) { return false; } else { delete *i; notifier_list.erase(i); return true; } } bool Config::replace_notifier(const Notifier * o, Notifier * n) { Vector::iterator i = notifier_list.begin(); Vector::iterator end = notifier_list.end(); while (i != end && *i != o) ++i; if (i == end) { return false; } else { delete *i; *i = n; return true; } } bool Config::have(ParmString key) const { const char * value = data_.lookup(key); if (value == 0 || value[0] == '\x01') { return false; } else { return true; } } // // retrive methods // PosibErr Config::retrieve_bool(ParmString key) const { RET_ON_ERR_SET(retrieve(key), String, str); return str[0] == 't'; } PosibErr Config::retrieve_int(ParmString key) const { RET_ON_ERR_SET(retrieve(key), String, str); int i; sscanf(str.c_str(), "%i", &i); return i; } PosibErr Config::retrieve(ParmString key) const { const char * value = data_.lookup(key); if (value != 0) { if (value[0] == '\x01') ++value; return String(value); } else { return get_default(key); } } PosibErr Config::retrieve_list(ParmString key, MutableContainer * m) const { RET_ON_ERR_SET(get_default(key), String, def); const char * value = data_.lookup(key); if (value != 0) { def += ','; def += data_.lookup(key); } RET_ON_ERR(itemize(def, *m)); return no_err; } static const KeyInfo * find(ParmString key, const KeyInfo * i, const KeyInfo * end) { while (i != end) { if (strcmp(key, i->name) == 0) return i; ++i; } return i; } static const ConfigModule * find(ParmString key, const ConfigModule * i, const ConfigModule * end) { while (i != end) { if (strcmp(key, i->name) == 0) return i; ++i; } return i; } const char * Config::base_name(ParmString name) { const char * c = strchr(name, '-'); unsigned int p = c ? c - name : -1; if ((p == 3 && (strncmp(name, "add",p) == 0 || strncmp(name, "rem",p) == 0)) || (p == 4 && strncmp(name, "dont",p) == 0)) return name + p + 1; else return name; } PosibErr Config::keyinfo(ParmString key) const { PosibErr ret; const KeyInfo * i; i = acommon::find(key, kmi.main_begin, kmi.main_end); if (i != kmi.main_end) return ret = i; i = acommon::find(key, kmi.extra_begin, kmi.extra_end); if (i != kmi.extra_end) return ret = i; const char * h = strchr(key, '-'); if (h == 0) return ret.prim_err(unknown_key, key); String k(key,h-key); const ConfigModule * j = acommon::find(k, kmi.modules_begin, kmi.modules_end); if (j == kmi.modules_end) return ret.prim_err(unknown_key, key); i = acommon::find(key, j->begin, j->end); if (i != j->end) return ret = i; return ret.prim_err(unknown_key, key); } static bool proc_locale_str(ParmString lang, String & final_str) { if (lang == 0) return false; const char * i = lang; if (! (asc_islower(i[0]) && asc_islower(i[1])) ) return false; final_str.assign(i, 2); i += 2; if (! (i[0] == '_' || i[0] == '-')) return true; i += 1; if (! (asc_isupper(i[0]) && asc_isupper(i[1])) ) return true; final_str += '_'; final_str.append(i, 2); return true; } static void get_lang_env(String & str) { // NOTE: THIS IS NOT THREAD SAFE if (proc_locale_str(getenv("LC_MESSAGES"), str)) return; if (proc_locale_str(getenv("LANG"), str)) return; if (proc_locale_str(getenv("LANGUAGE"), str)) return; str = DEFAULT_LANG; } #ifdef USE_LOCALE static void get_lang(String & final_str) { // NOTE: THIS IS NOT THREAD SAFE String locale = setlocale (LC_ALL, NULL); if (locale == "C") setlocale (LC_ALL, ""); const char * lang = setlocale (LC_MESSAGES, NULL); bool res = proc_locale_str(lang, final_str); if (locale == "C") setlocale(LC_MESSAGES, locale.c_str()); if (!res) get_lang_env(final_str); } #else static inline void get_lang(String & str) { get_lang_env(str); } #endif PosibErr Config::get_default(ParmString key) const { RET_ON_ERR_SET(keyinfo(key), const KeyInfo *, ki); bool in_replace = false; String final_str; String replace; const char * i = ki->def; if (*i == '!') { // special cases ++i; if (strcmp(i, "lang") == 0) { if (have("master")) { final_str = ""; } else { get_lang(final_str); } } else if (strcmp(i, "actual-lang") == 0) { unsigned int len = 0; final_str = retrieve("lang"); while (len < final_str.size() && final_str[len] != '_') ++len; final_str.resize(len); } else if (strcmp(i, "special") == 0) { // do nothing } else { abort(); // this should not happen } } else for(; *i; ++i) { if (!in_replace) { if (*i == '<') { in_replace = true; } else { final_str += *i; } } else { // in_replace if (*i == '/' || *i == ':' || *i == '|' || *i == '#' || *i == '^') { char sep = *i; String second; ++i; while (*i != '\0' && *i != '>') second += *i++; if (sep == '/') { String s1 = retrieve(replace); String s2 = retrieve(second); final_str += add_possible_dir(s1, s2); } else if (sep == ':') { String s1 = retrieve(replace); final_str += add_possible_dir(s1, second); } else if (sep == '#') { String s1 = retrieve(replace); assert(second.size() == 1); unsigned int s = 0; while (s != s1.size() && s1[s] != second[0]) ++s; final_str.append(s1, s, String::npos); } else if (sep == '^') { String s1 = retrieve(replace); String s2 = retrieve(second); final_str += figure_out_dir(s1, s2); } else { // sep == '|' assert(replace[0] == '$'); const char * env = getenv(replace.c_str()+1); final_str += env ? env : second; } replace = ""; in_replace = false; } else if (*i == '>') { final_str += retrieve(replace); replace = ""; in_replace = false; } else { replace += *i; } } } return final_str; } #define notify_all(ki, value, fun) \ do { \ Vector::iterator i = notifier_list.begin(); \ Vector::iterator end = notifier_list.end(); \ while (i != end) { \ RET_ON_ERR((*i)->fun(ki,value)); \ ++i; \ } \ } while (false) class NotifyListBlockChange : public MutableContainer { const KeyInfo * key_info; Vector & notifier_list; public: NotifyListBlockChange(const KeyInfo * ki, Vector & n); PosibErr add(ParmString); PosibErr remove(ParmString); PosibErr clear(); }; NotifyListBlockChange:: NotifyListBlockChange(const KeyInfo * ki, Vector & n) : key_info(ki), notifier_list(n) {} PosibErr NotifyListBlockChange::add(ParmString v) { notify_all(key_info, v, item_added); return true; } PosibErr NotifyListBlockChange::remove(ParmString v) { notify_all(key_info, v, item_removed); return true; } PosibErr NotifyListBlockChange::clear() { notify_all(key_info, 0, all_removed); return no_err; } PosibErr Config::replace(ParmString k, ParmString value) { if (strcmp(value,"") == 0) return remove(k); const char * key; const char * i = strchr(k, '-'); int p = (i == 0 ? -1 : i - k); if ((p == 3 && (strncmp(k, "add",p) == 0 || strncmp(k, "rem",p) == 0)) || (p == 4 && strncmp(k, "dont",p) == 0)) { key = k + p + 1; if (strncmp(key, "all-", 4) == 0) { key = key + 4; p = 7; } } else { key = k; p = 0; } RET_ON_ERR_SET(keyinfo(key), const KeyInfo *, ki); if (ki->otherdata[1] && attached_) return make_err(cant_change_value, key); assert(ki->def != 0); // if null this key should never have values // directly added to it int num; switch (ki->type) { case KeyInfoBool: if (p == 4 || (p == 0 && strcmp(value,"false") == 0)) { data_.replace(key, "false"); notify_all(ki, false, item_updated); return no_err; } else if (p != 0) { return make_err(unknown_key, k); } else if (value[0] == '\0' || strcmp(value,"true") == 0) { data_.replace(key, "true"); notify_all(ki, true, item_updated); return no_err; } else { return make_err(bad_value, key, value, "either \"true\" or \"false\""); } break; case KeyInfoString: if (p == 0) { data_.replace(key,value); notify_all(ki, value, item_updated); return no_err; } else { return make_err(unknown_key, key); } break; case KeyInfoInt: if (p == 0 && sscanf(value, "%i", &num) == 1 && num >= 0) { data_.replace(key,value); notify_all(ki, num, item_updated); return no_err; } else if (p != 0) { return make_err(unknown_key, key); } else { return make_err(bad_value, key, value, "a positive integer"); } break; case KeyInfoList: char a; if (p == 0) { abort(); //FIXME //return ret.prim_err(list_set, key); } else if (p == 7) { // prefix must be "rem-all-" if (value[0] != '\0') { return make_err(bad_value, k, value, "nothing"); } a = '!'; } else if (k[0] == 'a') { // prefix must be "add-" a = '+'; } else { // prefix must be "rem-" a = '-'; } if (a != '!') { i = data_.lookup(key); if (i == 0) i = ""; String s = i; s += ','; s += a; s += value; data_.replace(key, s); } else { data_.replace(key, "!"); } switch (a) { case '!': notify_all(ki, value, all_removed); break; case '+': notify_all(ki, value, item_added); break; case '-': notify_all(ki, value, item_removed); break; } break; } return no_err; } PosibErr Config::remove (ParmString key) { RET_ON_ERR_SET(keyinfo(key), const KeyInfo *, ki); if (ki->otherdata[1] && attached_) return make_err(cant_change_value, key); assert(ki->def != 0); // if null this key should never have values // directly added to it bool success = data_.remove(key); switch (ki->type) { case KeyInfoString: notify_all(ki, retrieve(key), item_updated); break; case KeyInfoBool: notify_all(ki, retrieve_bool(key), item_updated); break; case KeyInfoInt: notify_all(ki, retrieve_int(key), item_updated); break; case KeyInfoList: NotifyListBlockChange n(ki, notifier_list); RET_ON_ERR(retrieve_list(key, &n)); break; } return success; } StringPairEnumeration * Config::elements() { return data_.elements(); } ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// class PossibleElementsEmul : public KeyInfoEnumeration { private: bool include_extra; const Config * cd; const KeyInfo * i; const ConfigModule * m; public: PossibleElementsEmul(const Config * d, bool ic) : include_extra(ic), cd(d), i(d->kmi.main_begin), m(0) {} KeyInfoEnumeration * clone() const { return new PossibleElementsEmul(*this); } void assign(const KeyInfoEnumeration * other) { *this = *(const PossibleElementsEmul *)(other); } const KeyInfo * next() { if (i == cd->kmi.main_end) { if (include_extra) i = cd->kmi.extra_begin; else i = cd->kmi.extra_end; } if (i == cd->kmi.extra_end) { m = cd->kmi.modules_begin; if (m == cd->kmi.modules_end) return 0; else i = m->begin; } if (m == 0) return i++; if (m == cd->kmi.modules_end) return 0; if (i == m->end) { ++m; if (m == cd->kmi.modules_end) return 0; else i = m->begin; } return i++; } bool at_end() const { return (m == cd->kmi.modules_end); } }; KeyInfoEnumeration * Config::possible_elements(bool include_extra) { return new PossibleElementsEmul(this, include_extra); } class ListDump : public MutableContainer { OStream & out; const char * name; public: ListDump(OStream & o, ParmString n) : out(o), name(n) {} PosibErr add(ParmString d) { out << "add-" << name << ' ' << d << '\n'; return true; } PosibErr remove(ParmString d) { out << "rem-" << name << ' ' << d << '\n'; return true; } PosibErr clear() { out << "rem-all-" << name << '\n'; return no_err; } }; void Config::write_to_stream(OStream & out, bool include_extra) { KeyInfoEnumeration * els = possible_elements(include_extra); const KeyInfo * i; while ((i = els->next()) != 0) { if (i->desc == 0) continue; out << "# " << (i->type == KeyInfoList ? "add|rem-" : "") << i->name << " descrip: " << (i->def == 0 ? "(action option) " : "") << i->desc << '\n'; if (i->def != 0) { out << "# " << i->name << " default: " << i->def << '\n'; String val = retrieve(i->name); if (i->type != KeyInfoList) { out << "# " << i->name << " current: " << val << "\n"; if (have(i->name)) out << i->name << " " << val << "\n"; } else { const char * value = data_.lookup(i->name); if (value != 0) { ListDump ld(out, i->name); itemize(value, ld); } } } out << "\n\n"; } delete els; } PosibErr Config::read_in(IStream & in) { String key,value; while (getdata_pair(in, key, value)) { RET_ON_ERR(replace(key, value)); } return no_err; } PosibErr Config::read_in_file(ParmString file) { FStream in; RET_ON_ERR(in.open(file, "r")); return read_in(in); } PosibErr Config::read_in_string(ParmString str) { StringIStream in(str); return read_in(in); } void Config::merge(const Config & other) { KeyInfoEnumeration * els = possible_elements(); bool diff_name = strcmp(name(), other.name()) != 0; const KeyInfo * k; const KeyInfo * other_k; const char * other_name; String this_value; String other_value; while ( (k = els->next()) != 0) { if (diff_name && k->otherdata[0] == 'p' && strncmp(k->name, other.name_.c_str(), other.name_.size()) && k->name[other.name_.size()] == '_') other_name = k->name + other.name_.size(); else other_name = k->name; other_k = other.keyinfo(other_name); if (diff_name && other_k && other_k->otherdata[0] == 'r') continue; // the other key is a prefix key so skip it // when this is a prefix key than this key // would be prefix_ if (other_k != 0 && strcmp(k->def, other_k->def) == 0 && !other.have(other_name)) continue; { PosibErr pe = other.retrieve(other_name); if (pe.get_err() != 0) continue; // if an err then this key does not exist in the other // table. other_value = pe; } if (other_value == "(default)") continue; this_value = retrieve(k->name); if (this_value == other_value && !other.have(k->name)) continue; // if the two values match there is no need to insert it into the // table unless the other value is specificly set if (k->type != KeyInfoList) { data_.replace(k->name, other_value); } else { String new_value; if (other_value[0] != '!') { new_value = this_value; new_value += ','; } new_value += other_value; data_.replace(k->name, new_value); } } delete els; } PosibErr Config::read_in_settings(const Config * override) { // FIXME: make this more robust. // Catch errors and atatched there source of origin { PosibErrBase pe = read_in_file(retrieve("conf-path")); if (pe.has_err() && !pe.has_err(cant_read_file)) return pe; } { PosibErrBase pe = read_in_file(retrieve("per-conf-path")); if (pe.has_err() && !pe.has_err(cant_read_file)) return pe; } const char * env = getenv("ASPELL_CONF"); if (env != 0) RET_ON_ERR(read_in_string(env)); if (override != 0) merge(*override); return no_err; } #define CANT_CHANGE 1 #ifdef ENABLE_WIN32_RELOCATABLE # define HOME_DIR "" # define PERSONAL ".pws" # define REPL ".prepl" #else # define HOME_DIR "<$HOME|./>" # define PERSONAL ".aspell..pws" # define REPL ".aspell..prepl" #endif char mode_string[128] = "filter mode"; static const KeyInfo config_keys[] = { {"actual-dict-dir", KeyInfoString, "", 0} , {"actual-lang", KeyInfoString, "!actual-lang", 0} , {"conf", KeyInfoString, "aspell.conf", "main configuration file" , {0, CANT_CHANGE}} , {"conf-dir", KeyInfoString, CONF_DIR, "location of main configuration file" ,{0, CANT_CHANGE}} , {"conf-path", KeyInfoString, "", 0} , {"data-dir", KeyInfoString, DATA_DIR, "location of language data files", "r"} , {"dict-dir", KeyInfoString, DICT_DIR, "location of the main word list" } , {"encoding", KeyInfoString, "iso8859-1", "encoding to expect data to be in"} , {"filter", KeyInfoList , "url", "add or removes a filter"} , {"mode", KeyInfoString, "url", mode_string } , {"extra-dicts", KeyInfoList, "", "extra dictionaries to use"} , {"home-dir", KeyInfoString, HOME_DIR, "location for personal files" } , {"ignore", KeyInfoInt , "1", "ignore words <= n chars" } , {"ignore-accents" , KeyInfoBool, "false", "ignore accents when checking words"} , {"ignore-case", KeyInfoBool , "false", "ignore case when checking words"} , {"ignore-repl", KeyInfoBool , "false", "ignore commands to store replacement pairs"} , {"jargon", KeyInfoString, "", "extra information for the word list"} , {"keyboard", KeyInfoString, "standard", "keyboard definition to use for typo analysis"} , {"lang", KeyInfoString, "", "language code"} , {"language-tag", KeyInfoString, "!lang", "deprecated, use lang instead"} , {"local-data-dir", KeyInfoString, "", "location of local language data files" } , {"master", KeyInfoString, "", "base name of the main dictionary to use"} , {"master-flags", KeyInfoString, "", 0} , {"master-path", KeyInfoString, "", 0} , {"module", KeyInfoString, "default", ""} , {"module-search-order", KeyInfoList, "", ""} , {"per-conf", KeyInfoString, ".aspell.conf", "personal configuration file",{0, CANT_CHANGE}} , {"per-conf-path", KeyInfoString, "", 0} , {"personal", KeyInfoString, PERSONAL, "personal word list file name"} , {"personal-path", KeyInfoString, "", 0} , {"prefix", KeyInfoString, PREFIX, "prefix directory", {0, CANT_CHANGE}} , {"repl", KeyInfoString, REPL, "replacements list file name" } , {"repl-path", KeyInfoString, "", 0} , {"run-together", KeyInfoBool, "false", "consider run-together words legal"} , {"run-together-limit", KeyInfoInt, "8", "maxium numbers that can be strung together"} , {"run-together-min", KeyInfoInt, "3", "minimal length of interior words"} , {"run-together-specified", KeyInfoBool, "false", 0} , {"save-repl", KeyInfoBool , "true", "save replacement pairs on save all"} , {"set-prefix", KeyInfoBool, "true", "set the prefix based on executable location", {0, CANT_CHANGE}} , {"size", KeyInfoString, "+60", "size of the word list"} , {"spelling", KeyInfoString, "", "no longer used"} , {"strip-accents" , KeyInfoBool, "false", "strip accents from word lists"} , {"sug-mode", KeyInfoString, "normal", "suggestion mode"} , {"word-list-path", KeyInfoList, DATA_DIR, "Search path for word list information files"} // // These options are specific to the "aspell" utility. They are // here so that they can be specified in configuration files. // , {"backup", KeyInfoBool, "true", "create a backup file by appending \".bak\""} , {"reverse", KeyInfoBool, "false", "reverse the order of the suggest list"} , {"time" , KeyInfoBool, "false", "time load time and suggest time in pipe mode"} , {"keymapping", KeyInfoString, "aspell", "keymapping for check mode, one of aspell ispell"} }; const KeyInfo * config_impl_keys_begin = config_keys; const KeyInfo * config_impl_keys_end = config_keys + sizeof(config_keys)/sizeof(KeyInfo); Config * new_basic_config() { return new Config("aspell", config_impl_keys_begin, config_impl_keys_end); } }