//============================================================================= // // File : libkviconfig.cpp // Creation date : Thu Jan 31 2002 22:50:12 GMT by Szymon Stefanek // // This file is part of the KVIrc IRC client distribution // Copyright (C) 2002-2010 Szymon Stefanek (pragma@kvirc.net) // // This program is FREE software. You can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the HOPE that it will be USEFUL, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, write to the Free Software Foundation, // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //============================================================================= #include "KviModule.h" #include "KviCString.h" #include "KviConfigurationFile.h" #include "KviFileUtils.h" #include "KviApplication.h" #include "KviLocale.h" #include "KviPointerHashTable.h" static KviPointerHashTable * g_pConfigDict = 0; static int g_iNextConfigId = 0; /* @doc: config.open @type: function @title: $config.open @short: Opens a config file @syntax: $config.open([,]) @description: Opens a config file. If the file already exists, its contents are parsed and available for extracting by the [fnc]$config.read[/fnc]() function.[br] may be an absolute path or a relative path: if a relative path is used, KVIrc will complete it with a local KVIrc directory suitable for writing script config files.[br] If the config file doesn't exist, it is opened as empty config file.[br] Flags can contain a combination of letters [b]r[/b] and [b]w[/b].[br] If only [b]r[/b] is specified, the config file is opened in [i]read-only[/i] mode: no changes will be written to disk.[br] If only [b]w[/b] is specified, the config file is opened in [i]write-only[/i] mode: the contents of the file on disk are not read.[br] If are not specified then [b][i]rw[/i][/b] is assumed.[br] The function returns an identifier for the open config file. This identifier is a mandatory parameter in all the other config.* functions and commands.[br] The config section is set to the default section name: you can change it by using [cmd]config.setsection[/cmd].[br] After you have finished using the file, don't forget to call [cmd]config.close[/cmd]! KVIrc tries to not open a config file twice: if the file was already opened then the identifier of the open file is returned.[br] You can check if a config file is already open by using the [fnc]$config.id[/fnc]() function.[br] @examples: [example] [comment]# It is a good idea to store the returned ID to a variable :)[/comment] %cfg = $config.open(myscript.kvc) %num = [fnc]$config.read[/fnc](%cfg,Number,0) [cmd]echo[/cmd] Number of entries is %num %num++; [cmd]config.write[/cmd] %cfg Number %num [cmd]config.close[/cmd] %cfg [/example] @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_fnc_open(KviKvsModuleFunctionCall * c) { QString szFile; QString szMode; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("filename", KVS_PT_STRING, 0, szFile) KVSM_PARAMETER("mode", KVS_PT_STRING, KVS_PF_OPTIONAL, szMode) KVSM_PARAMETERS_END(c) KviConfigurationFile::FileMode fileMode; if(szMode.contains('r')) { if(szMode.contains('w')) fileMode = KviConfigurationFile::ReadWrite; else fileMode = KviConfigurationFile::Read; } else { if(szMode.contains('w')) fileMode = KviConfigurationFile::Write; else fileMode = KviConfigurationFile::ReadWrite; } KviFileUtils::adjustFilePath(szFile); QString szAbsFile; if(KviFileUtils::isAbsolutePath(szFile)) szAbsFile = szFile; else g_pApp->getLocalKvircDirectory(szAbsFile, KviApplication::ConfigScripts, szFile, true); KviPointerHashTableIterator it(*g_pConfigDict); while(it.current()) { if(KviQString::equalCI(it.current()->fileName(), szAbsFile)) { c->returnValue()->setString(it.currentKey()); if(it.current()->readOnly() && (fileMode & KviConfigurationFile::Write)) { it.current()->setReadOnly(false); } return true; } ++it; } KviConfigurationFile * cfg = new KviConfigurationFile(szAbsFile, fileMode); g_iNextConfigId++; QString tmp = QString("%1").arg(g_iNextConfigId); g_pConfigDict->insert(tmp, cfg); c->returnValue()->setString(tmp); return true; } /* @doc: config.id @type: function @title: $config.id @short: Returns the ID of an open config file @syntax: $config.id() @description: Returns the ID of an open config file specified by .[br] (This is the same ID that is returned by [fnc]$config.open[/fnc].[br] If no such file is open, [b]0[/b] is returned (that is an invalid config identifier).[br] @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_fnc_id(KviKvsModuleFunctionCall * c) { QString szFile; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("file", KVS_PT_STRING, 0, szFile) KVSM_PARAMETERS_END(c) KviFileUtils::adjustFilePath(szFile); QString szAbsFile; if(KviFileUtils::isAbsolutePath(szFile)) szAbsFile = szFile; else g_pApp->getLocalKvircDirectory(szAbsFile, KviApplication::ConfigScripts, szFile, true); KviPointerHashTableIterator it(*g_pConfigDict); while(it.current()) { if(KviQString::equalCI(it.current()->fileName(), szAbsFile)) { c->returnValue()->setString(it.currentKey()); return true; } ++it; } c->returnValue()->setString("0"); return true; } /* @doc: config.read @type: function @title: $config.read @short: Reads an entry from a config file @syntax: $config.read(,[,]) @description: Reads and returns the value associated to in the current section of the config file identified by .[br] must be a valid config identifier returned by [fnc]$config.open[/fnc]() If no value is associated with in the current section the return value is an empty string or the string if it has been passed.[br] @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_fnc_read(KviKvsModuleFunctionCall * c) { QString szId; QString szKey; QString szDefault; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("id", KVS_PT_STRING, 0, szId) KVSM_PARAMETER("key", KVS_PT_STRING, 0, szKey) KVSM_PARAMETER("default", KVS_PT_STRING, KVS_PF_OPTIONAL, szDefault) KVSM_PARAMETERS_END(c) KviConfigurationFile * cfg = g_pConfigDict->find(szId); if(cfg) { c->returnValue()->setString(cfg->readEntry(szKey, szDefault)); } else { c->warning(__tr2qs("The config file with ID '%Q' is not open"), &szId); } return true; } /* @doc: config.section @type: function @title: $config.section @short: Returns the name of the current section of a config file @syntax: $config.section() @description: Returns the name of the current section in the config file identifier by .[br] must be a valid config identifier returned by [fnc]$config.open[/fnc]() @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_fnc_section(KviKvsModuleFunctionCall * c) { QString szId; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("id", KVS_PT_STRING, 0, szId) KVSM_PARAMETERS_END(c) KviConfigurationFile * cfg = g_pConfigDict->find(szId); if(cfg) { c->returnValue()->setString(cfg->group()); } else { c->warning(__tr2qs("The config file with ID '%Q' is not open"), &szId); } return true; } /* @doc: config.readonly @type: function @title: $config.readonly @short: Checks whether a config file is opened in read-only mode @syntax: $config.readonly() @description: Returns [b]1[/b] if the config file identified by is opened in read-only mode, and [b]0[/b] otherwise.[br] must be a valid config identifier returned by [fnc]$config.open[/fnc]() If does not identify an open config file, a warning is printed and [b]0[/b] is returned.[br] @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_fnc_readonly(KviKvsModuleFunctionCall * c) { QString szId; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("id", KVS_PT_STRING, 0, szId) KVSM_PARAMETERS_END(c) KviConfigurationFile * cfg = g_pConfigDict->find(szId); if(cfg) { c->returnValue()->setBoolean(cfg->readOnly()); } else { c->warning(__tr2qs("The config file with ID '%Q' is not open"), &szId); c->returnValue()->setInteger(false); } return true; } /* @doc: config.filename @type: function @title: $config.filename @short: Returns the filename of an open config file @syntax: $config.filename() @description: Returns the filename of the [b]open[/b] config file identifier by .[br] must be a valid config identifier returned by [fnc]$config.open[/fnc]() If the doesn't match any open file, an empty string is returned.[br] @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_fnc_filename(KviKvsModuleFunctionCall * c) { QString szId; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("id", KVS_PT_STRING, 0, szId) KVSM_PARAMETERS_END(c) KviConfigurationFile * cfg = g_pConfigDict->find(szId); if(cfg) { c->returnValue()->setString(cfg->fileName()); } return true; } /* @doc: config.hassection @type: function @title: $config.hassection @short: Checks whether a section is present in a config file @syntax: $config.hassection(,) @description: Returns [b]1[/b] if the section exists in the config file identifier by , and [b]0[/b] otherwise.[br] must be a valid config identifier returned by [fnc]$config.open[/fnc]() @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_fnc_hassection(KviKvsModuleFunctionCall * c) { QString szId; QString szSect; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("id", KVS_PT_STRING, 0, szId) KVSM_PARAMETER("id", KVS_PT_STRING, 0, szSect) KVSM_PARAMETERS_END(c) KviConfigurationFile * cfg = g_pConfigDict->find(szId); if(cfg) { c->returnValue()->setBoolean(cfg->hasGroup(szSect)); } else { c->warning(__tr2qs("The config file with ID '%Q' is not open"), &szId); } return true; } /* @doc: config.sectionlist @type: function @title: $config.sectionlist @short: Returns a list of the sections in a config file @syntax: $config.sectionlist() @description: Returns an array of sections present in the config file identified by .[br] must be a valid config identifier returned by [fnc]$config.open[/fnc]() @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_fnc_sectionlist(KviKvsModuleFunctionCall * c) { QString szId; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("id", KVS_PT_STRING, 0, szId) KVSM_PARAMETERS_END(c) KviConfigurationFile * cfg = g_pConfigDict->find(szId); if(cfg) { KviConfigurationFileIterator it(*(cfg->dict())); KviKvsArray * pArray = new KviKvsArray(); int id = 0; while(it.current()) { pArray->set(id++, new KviKvsVariant(it.currentKey())); ++it; } c->returnValue()->setArray(pArray); } else { c->warning(__tr2qs("The config file with ID '%Q' is not open"), &szId); } return true; } /* @doc: config.keylist @type: function @title: $config.keylist @short: Returns a list of keys in a config file @syntax: $config.keylist() @description: Returns an array of keys present in the current section of the config file identified by .[br] must be a valid config identifier returned by [fnc]$config.open[/fnc]() @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_fnc_keylist(KviKvsModuleFunctionCall * c) { QString szId; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("id", KVS_PT_STRING, 0, szId) KVSM_PARAMETERS_END(c) KviConfigurationFile * cfg = g_pConfigDict->find(szId); if(cfg) { KviConfigurationFileGroup * d = cfg->dict()->find(cfg->group()); if(!d) return true; KviConfigurationFileGroupIterator it(*d); KviKvsArray * pArray = new KviKvsArray(); int id = 0; while(it.current()) { pArray->set(id++, new KviKvsVariant(it.currentKey())); ++it; } c->returnValue()->setArray(pArray); } else { c->warning(__tr2qs("The config file with ID '%Q' is not open"), &szId); } return true; } /* @doc: config.filelist @type: function @title: $config.filelist @short: Returns a list of open config files @syntax: $config.filelist @description: Returns an array of open config files.[br] @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_fnc_filelist(KviKvsModuleFunctionCall * c) { KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETERS_END(c) KviKvsArray * pArray = new KviKvsArray(); int id = 0; KviPointerHashTableIterator it(*g_pConfigDict); while(it.current()) { pArray->set(id++, new KviKvsVariant(it.currentKey())); ++it; } c->returnValue()->setArray(pArray); return true; } /* @doc: config.close @type: command @title: config.close @keyterms: storing & reading configuration entries @short: Closes a config file @syntax: config.close [-q] @description: Closes the config file identified by .[br] must be a a valid config file identifier returned by [fnc]$config.open[/fnc].[br] If the config file was opened as read-write (default), the changes will be stored to disk. If the config was opened as read-only, changes will not be written.[br] If the does not match any open config file, a warning is printed unless the -q (quiet) switch is used.[br] @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_cmd_close(KviKvsModuleCommandCall * c) { QString szId; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("id", KVS_PT_STRING, 0, szId) KVSM_PARAMETERS_END(c) KviConfigurationFile * cfg = g_pConfigDict->find(szId); if(cfg) { if(cfg->readOnly()) { if(cfg->dirty()) { if(!c->hasSwitch('q', "quiet")) c->warning(__tr2qs("The config file '%Q' has been changed but is opened as read-only: changes will be lost"), &cfg->fileName()); } } else { // we force a save here if(!cfg->sync()) if(!c->hasSwitch('q', "quiet")) c->warning(__tr2qs("An error has occurred while trying to save the config file with ID '%Q'"), &szId); } g_pConfigDict->remove(szId); } else { if(!c->hasSwitch('q', "quiet")) c->warning(__tr2qs("The config file with ID '%Q' is not open"), &szId); } return true; } /* @doc: config.flush @type: command @title: config.flush @keyterms: storing & reading configuration entries @short: Flushes a config file @syntax: config.flush @description: Flushes the config file identified by .[br] must be a a valid config file identifier returned by [fnc]$config.open[/fnc].[br] If the config file was opened as read-write (default), the changes will be stored to disk. If the config was opened as read-only, an error is printed.[br] If the does not match any open config file, a warning is printed. @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_cmd_flush(KviKvsModuleCommandCall * c) { QString szId; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("id", KVS_PT_STRING, 0, szId) KVSM_PARAMETERS_END(c) KviConfigurationFile * cfg = g_pConfigDict->find(szId); if(cfg) { if(cfg->readOnly()) c->warning(__tr2qs("The config file with ID '%Q' is read-only"), &szId); else if(!cfg->sync()) c->warning(__tr2qs("An error has occurred while trying to save the config file with ID '%Q'"), &szId); } else { c->warning(__tr2qs("The config file with ID '%Q' is not open"), &szId); } return true; } /* @doc: config.clear @type: command @title: config.clear @keyterms: storing & reading configuration entries @short: Clears a config file @syntax: config.clear @description: Clears the contents of the config file identified by .[br] All the sections are removed (except for the default section) and all the key=value pairs are destroyed.[br] must be a a valid config file identifier returned by [fnc]$config.open[/fnc].[br] If the does not match any open config file, a warning is printed. @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_cmd_clear(KviKvsModuleCommandCall * c) { QString szId; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("id", KVS_PT_STRING, 0, szId) KVSM_PARAMETERS_END(c) KviConfigurationFile * cfg = g_pConfigDict->find(szId); if(cfg) { cfg->clear(); } else { c->warning(__tr2qs("The config file with ID '%Q' is not open"), &szId); } return true; } /* @doc: config.clearsection @type: command @title: config.clearsection @keyterms: storing & reading configuration entries @short: Clears a section in a config file @syntax: config.clearsection @description: Clears the contents of the section in the config file identified by .[br] All the key=value pairs in the section are destroyed.[br] must be a a valid config file identifier returned by [fnc]$config.open[/fnc].[br] If the does not match any open config file, a warning is printed. If is also the current section, the default section is made current.[br] No warning is printed if the section does not exist.[br] @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_cmd_clearsection(KviKvsModuleCommandCall * c) { QString szId; QString szSect; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("id", KVS_PT_STRING, 0, szId) KVSM_PARAMETER("section", KVS_PT_STRING, 0, szSect) KVSM_PARAMETERS_END(c) KviConfigurationFile * cfg = g_pConfigDict->find(szId); if(cfg) { cfg->clearGroup(szSect); } else { c->warning(__tr2qs("The config file with ID '%Q' is not open"), &szId); } return true; } /* @doc: config.write @type: command @title: config.write @keyterms: storing & reading configuration entries @short: Writes a data field to a config file @syntax: config.write @description: Writes the = pair to the config file identifier by .[br] must be a valid config file ID returned by [fnc]$config.open[/fnc]().[br] and can be any strings.[br] The = pair is written in the current section of the config file.[br] If already exists in the current section of the config, the corresponding value is replaced with .[br] If is an empty string, the is simply removed from the current section.[br] If a section remains empty (with no keys) at file write time, that section will be removed.[br] The changes are [b]not[/b] written to disk: you must call [cmd]config.flush[/cmd] or (better) [cmd]config.close[/cmd] for the changes to be effectively written.[br] @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_cmd_write(KviKvsModuleCommandCall * c) { QString szId; QString szKey; QString szVal; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("id", KVS_PT_STRING, 0, szId) KVSM_PARAMETER("key", KVS_PT_STRING, 0, szKey) KVSM_PARAMETER("value", KVS_PT_STRING, 0, szVal) KVSM_PARAMETERS_END(c) KviConfigurationFile * cfg = g_pConfigDict->find(szId); if(cfg) { if(!szVal.isEmpty()) cfg->writeEntry(szKey, szVal); else cfg->clearKey(szKey); } else { c->warning(__tr2qs("The config file with ID '%Q' is not open"), &szId); } return true; } /* @doc: config.setsection @type: command @title: config.setsection @keyterms: storing & reading configuration entries @short: Changes the current section of a config file @syntax: config.setsection @description: Changes the current section of the config file to .[br] Sections are effectively namespaces for the keys: each section can contain a set of keys and equal keys in different sections do not collide.[br] must be a valid config file ID returned by [fnc]$config.open[/fnc]().[br] @seealso: [module:config]Config module documentation[/module] */ static bool config_kvs_cmd_setsection(KviKvsModuleCommandCall * c) { QString szId; QString szSect; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("id", KVS_PT_STRING, 0, szId) KVSM_PARAMETER("section", KVS_PT_STRING, 0, szSect) KVSM_PARAMETERS_END(c) KviConfigurationFile * cfg = g_pConfigDict->find(szId); if(cfg) { cfg->setGroup(szSect); } else { c->warning(__tr2qs("The config file with ID '%Q' is not open"), &szId); } return true; } /* @doc: config @type: module @short: Managing of configuration files @title: The config module @body: The config module is a powerful interface that manages text configuration files.[br] The files are arranged in named sections and the data is stored in key=value pairs belonging to each section.[br] [br] [section1_name][br] =[br] =[br] =[br] ....[br] [section2_name][br] =[br] ....[br] [br] Keys and values are plain text strings - newlines in values are encoded using a simple hexadecimal notation.[br] The config files can be also edited by using any text editor.[br] The config module works on config files loaded in memory. For this reason, you must first open a config file by [fnc]$config.open[/fnc]() - if the file exists on disk, its contents are loaded and you can access the data stored in it, otherwise an empty config file is created in memory.[br] The config file is opened in read-write mode by default. You can also choose the "read-only" method - a read-only config file is never flushed to disk (but you can still set values in memory!).[br] It is not a good idea to keep a config file open forever. A better approach is to keep settings in memory variables - when you want to write to disk, open the config file, write all your values and then close the file.[br] You can obviously keep the config file open for some time but remember that no changes are written to the disk until [cmd]config.flush[/cmd] or [cmd]config.close[/cmd] is called.[br] You write entries by using [cmd]config.write[/cmd] and read them by using [fnc]$config.read[/fnc]().[br] You can change the "current" config section by using [cmd]config.setsection[/cmd].[br] The following lists all commands and functions exported by this module:[br] [fnc]$config.open[/fnc][br] [fnc]$config.read[/fnc][br] [fnc]$config.section[/fnc][br] [fnc]$config.hassection[/fnc][br] [fnc]$config.sectionlist[/fnc][br] [fnc]$config.keylist[/fnc][br] [fnc]$config.filelist[/fnc][br] [fnc]$config.filename[/fnc][br] [fnc]$config.id[/fnc][br] [fnc]$config.readonly[/fnc][br] [cmd]config.close[/cmd][br] [cmd]config.flush[/cmd][br] [cmd]config.clear[/cmd][br] [cmd]config.clearsection[/cmd][br] [cmd]config.setsection[/cmd][br] [cmd]config.write[/cmd][br] */ static bool config_module_init(KviModule * m) { g_pConfigDict = new KviPointerHashTable; g_pConfigDict->setAutoDelete(true); KVSM_REGISTER_FUNCTION(m, "open", config_kvs_fnc_open); KVSM_REGISTER_FUNCTION(m, "id", config_kvs_fnc_id); KVSM_REGISTER_FUNCTION(m, "read", config_kvs_fnc_read); KVSM_REGISTER_FUNCTION(m, "section", config_kvs_fnc_section); KVSM_REGISTER_FUNCTION(m, "hassection", config_kvs_fnc_hassection); KVSM_REGISTER_FUNCTION(m, "sectionlist", config_kvs_fnc_sectionlist); KVSM_REGISTER_FUNCTION(m, "keylist", config_kvs_fnc_keylist); KVSM_REGISTER_FUNCTION(m, "filelist", config_kvs_fnc_filelist); KVSM_REGISTER_FUNCTION(m, "filename", config_kvs_fnc_filename); KVSM_REGISTER_FUNCTION(m, "readonly", config_kvs_fnc_readonly); KVSM_REGISTER_SIMPLE_COMMAND(m, "close", config_kvs_cmd_close); KVSM_REGISTER_SIMPLE_COMMAND(m, "flush", config_kvs_cmd_flush); KVSM_REGISTER_SIMPLE_COMMAND(m, "clear", config_kvs_cmd_clear); KVSM_REGISTER_SIMPLE_COMMAND(m, "clearsection", config_kvs_cmd_clearsection); KVSM_REGISTER_SIMPLE_COMMAND(m, "setsection", config_kvs_cmd_setsection); KVSM_REGISTER_SIMPLE_COMMAND(m, "write", config_kvs_cmd_write); return true; } static bool config_module_cleanup(KviModule *) { delete g_pConfigDict; g_pConfigDict = 0; return true; } static bool config_module_can_unload(KviModule *) { return g_pConfigDict->isEmpty(); } KVIRC_MODULE( "Config", // module name "4.0.0", // module version "Copyright (C) 2001 Szymon Stefanek (pragma at kvirc dot net)", // author & (C) "Config file system for KVIrc", config_module_init, config_module_can_unload, 0, config_module_cleanup, 0)