2023-03-23 15:10:56 +00:00
|
|
|
module readconf;
|
|
|
|
|
|
|
|
import std.stdio, std.conv, std.path, std.file;
|
|
|
|
import core.stdc.stdlib : exit;
|
|
|
|
import std.regex;
|
|
|
|
import std.meta;
|
|
|
|
import singlog;
|
|
|
|
|
2023-03-25 22:45:41 +00:00
|
|
|
/**
|
|
|
|
* Read config object
|
|
|
|
*/
|
|
|
|
alias rc = Config.file;
|
|
|
|
|
2023-03-27 13:39:46 +00:00
|
|
|
private const string mainSection = "[]";
|
|
|
|
|
2023-03-23 15:10:56 +00:00
|
|
|
class Config
|
|
|
|
{
|
|
|
|
private:
|
2023-03-25 23:35:08 +00:00
|
|
|
enum {
|
|
|
|
GROUP_PROPERTY = 4,
|
|
|
|
GROUP_VALUE_1 = 11, // string
|
|
|
|
GROUP_VALUE_2 = 14, // "strin"
|
|
|
|
GROUP_VALUE_3 = 16, // 'string'
|
|
|
|
GROUP_SECTION_OTHER_OUTER = 17, // "[string]"
|
|
|
|
GROUP_SECTION_OTHER_INNER = 18, // "[string]"
|
|
|
|
GROUP_SECTION_MAIN = 20, // "[]"
|
|
|
|
}
|
|
|
|
|
2023-03-23 15:10:56 +00:00
|
|
|
static Config config;
|
|
|
|
string path;
|
|
|
|
bool readed = false;
|
2023-03-26 17:16:46 +00:00
|
|
|
ConfigFile[string] configs;
|
2023-03-25 23:35:08 +00:00
|
|
|
|
2023-03-25 12:37:46 +00:00
|
|
|
const string pattern = "^( |\\t)*(((\\w(\\w|-)+)(( |\\t)*(=>|=){1}"
|
|
|
|
~ "( |\\t)*)(?!\\/(\\/|\\*))(([^ >\"'=\\n\\t#;].*?)|(\"(.+)\")"
|
2023-03-25 22:45:41 +00:00
|
|
|
~ "|('(.+)')){1})|(\\[(\\w(\\w|-)+)\\])|(\\[\\]))( |\\t)*"
|
2023-03-25 12:37:46 +00:00
|
|
|
~ "(( |\\t)(#|;|\\/\\/|\\/\\*).*)?$";
|
2023-03-23 15:10:56 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Reading the configuration file
|
|
|
|
*/
|
2023-03-26 17:16:46 +00:00
|
|
|
bool readConfig(const string configName)
|
2023-03-23 15:10:56 +00:00
|
|
|
{
|
|
|
|
File configuration;
|
|
|
|
|
|
|
|
try {
|
|
|
|
configuration = File(this.path, "r");
|
|
|
|
} catch (Exception e) {
|
2023-03-29 15:43:32 +00:00
|
|
|
log.w("Unable to open the configuration file " ~ this.path);
|
|
|
|
log.e(e);
|
2023-03-25 23:03:03 +00:00
|
|
|
return false;
|
2023-03-23 15:10:56 +00:00
|
|
|
}
|
|
|
|
|
2023-03-26 17:16:46 +00:00
|
|
|
if (configName !in this.configs)
|
|
|
|
this.configs[configName] = ConfigFile(configName);
|
|
|
|
|
2023-03-25 13:36:59 +00:00
|
|
|
auto regular = regex(this.pattern, "m");
|
2023-03-23 15:10:56 +00:00
|
|
|
|
2023-03-25 23:35:08 +00:00
|
|
|
// reading from the main section
|
2023-03-27 13:39:46 +00:00
|
|
|
string sectionName = mainSection;
|
2023-03-25 21:31:25 +00:00
|
|
|
|
2023-03-23 15:10:56 +00:00
|
|
|
while (!configuration.eof())
|
|
|
|
{
|
|
|
|
string line = configuration.readln();
|
|
|
|
auto match = matchFirst(line, regular);
|
2023-03-27 15:52:01 +00:00
|
|
|
|
|
|
|
if (match.length == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// if again main section
|
|
|
|
if (match[GROUP_SECTION_MAIN].length)
|
|
|
|
{
|
|
|
|
sectionName = match[GROUP_SECTION_MAIN];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (match[GROUP_SECTION_OTHER_OUTER].length)
|
2023-03-23 15:10:56 +00:00
|
|
|
{
|
2023-03-27 15:52:01 +00:00
|
|
|
sectionName = match[GROUP_SECTION_OTHER_INNER];
|
|
|
|
continue;
|
2023-03-25 21:31:25 +00:00
|
|
|
}
|
2023-03-27 15:52:01 +00:00
|
|
|
|
|
|
|
int group = GROUP_VALUE_1;
|
|
|
|
|
|
|
|
if (match[group][0] == '\"')
|
|
|
|
group = GROUP_VALUE_2;
|
|
|
|
else if (match[group][0] == '\'')
|
|
|
|
group = GROUP_VALUE_3;
|
|
|
|
|
|
|
|
this.configs[configName].add(sectionName, ConfigParameter(match[GROUP_PROPERTY], match[group]));
|
2023-03-23 15:10:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
configuration.close();
|
|
|
|
this.readed = true;
|
|
|
|
} catch (Exception e) {
|
2023-03-29 15:43:32 +00:00
|
|
|
log.w("Unable to close the configuration file " ~ this.path);
|
|
|
|
log.e(e);
|
2023-03-26 17:16:46 +00:00
|
|
|
this.configs.remove(configName);
|
2023-03-25 23:03:03 +00:00
|
|
|
this.readed = false;
|
2023-03-23 15:10:56 +00:00
|
|
|
}
|
2023-03-25 23:03:03 +00:00
|
|
|
|
|
|
|
return this.readed;
|
2023-03-23 15:10:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this() {}
|
|
|
|
|
|
|
|
public:
|
|
|
|
/**
|
|
|
|
* Accessing the Config object
|
|
|
|
* Returns: Config Object
|
|
|
|
*/
|
|
|
|
@property static Config file()
|
|
|
|
{
|
|
|
|
if (this.config is null)
|
|
|
|
this.config = new Config;
|
|
|
|
|
|
|
|
return this.config;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Read the configuration file
|
|
|
|
* Params:
|
|
|
|
* path = the path to the configuration file
|
|
|
|
*/
|
2023-03-26 17:16:46 +00:00
|
|
|
bool read(string path, string configName = "")
|
2023-03-23 15:10:56 +00:00
|
|
|
{
|
|
|
|
this.path = path;
|
|
|
|
if (!path.exists)
|
2023-03-25 23:35:08 +00:00
|
|
|
throw new Exception("The configuration file does not exist: " ~ path);
|
2023-03-26 17:16:46 +00:00
|
|
|
if (configName.length == 0)
|
|
|
|
configName = path.baseName();
|
|
|
|
if (configName in configs)
|
|
|
|
throw new Exception("The configuration file with this name has already been read");
|
|
|
|
return readConfig(configName);
|
2023-03-23 15:10:56 +00:00
|
|
|
}
|
|
|
|
|
2023-03-26 17:16:46 +00:00
|
|
|
/**
|
|
|
|
* Get the section
|
|
|
|
* Params:
|
|
|
|
* section = section name (default main "[]")
|
|
|
|
*/
|
|
|
|
@property ConfigFile configFile(string configName = "")
|
|
|
|
{
|
|
|
|
if (configName.length == 0)
|
|
|
|
{
|
|
|
|
if (configs.length == 1)
|
|
|
|
return configs[configs.byKey.front];
|
|
|
|
else
|
|
|
|
throw new Exception("You must explicitly specify the name of the configuration file");
|
|
|
|
}
|
|
|
|
|
2023-03-27 13:39:46 +00:00
|
|
|
return configName in configs ? configs[configName] : ConfigFile(configName);
|
2023-03-26 17:16:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Config file
|
|
|
|
*
|
|
|
|
* Get the config file
|
|
|
|
* Params:
|
|
|
|
* configName = config name (by default the name of the configuration file)
|
|
|
|
*/
|
|
|
|
alias cf = configFile;
|
2023-03-27 15:32:29 +00:00
|
|
|
|
|
|
|
@property ConfigFile opIndex(string configName = "")
|
|
|
|
{
|
|
|
|
if (configName.length == 0)
|
|
|
|
{
|
|
|
|
if (configs.length == 1)
|
|
|
|
return configs[configs.byKey.front];
|
|
|
|
else
|
|
|
|
throw new Exception("More than one configuration file has been read. "
|
|
|
|
~ "It is necessary to specify the name of a specific");
|
|
|
|
}
|
|
|
|
|
|
|
|
return configName in configs ? configs[configName] : ConfigFile(configName);
|
|
|
|
}
|
2023-03-26 17:16:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct ConfigFile
|
|
|
|
{
|
2023-03-27 13:39:46 +00:00
|
|
|
private string name;
|
2023-03-26 17:16:46 +00:00
|
|
|
private ConfigSection[string] sections;
|
|
|
|
|
2023-03-27 12:37:37 +00:00
|
|
|
@property bool exist()
|
|
|
|
{
|
|
|
|
return this.sections.length > 0;
|
|
|
|
}
|
|
|
|
|
2023-03-25 22:45:41 +00:00
|
|
|
/**
|
|
|
|
* Get the section
|
|
|
|
* Params:
|
|
|
|
* section = section name (default main "[]")
|
|
|
|
*/
|
2023-03-27 13:39:46 +00:00
|
|
|
@property ConfigSection sectionName(string section = mainSection)
|
2023-03-25 21:31:25 +00:00
|
|
|
{
|
2023-03-27 12:37:37 +00:00
|
|
|
if (!this.exist)
|
|
|
|
throw new Exception("The configuration file does not exist");
|
2023-03-27 13:39:46 +00:00
|
|
|
if (section == mainSection && sections.length == 1)
|
2023-03-27 12:37:37 +00:00
|
|
|
return sections[sections.byKey.front];
|
2023-03-27 15:32:29 +00:00
|
|
|
if (section.length == 0)
|
|
|
|
section = mainSection;
|
2023-03-27 13:39:46 +00:00
|
|
|
return section in sections ? sections[section] : ConfigSection(section);
|
2023-03-25 21:31:25 +00:00
|
|
|
}
|
2023-03-25 22:45:41 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Section name
|
|
|
|
*
|
|
|
|
* Get the section
|
|
|
|
* Params:
|
|
|
|
* section = section name (default main "[]")
|
|
|
|
*/
|
|
|
|
alias sn = sectionName;
|
2023-03-26 17:16:46 +00:00
|
|
|
|
|
|
|
private void add(string sectionName, ConfigParameter parameter)
|
|
|
|
{
|
|
|
|
if (sectionName !in this.sections)
|
|
|
|
this.sections[sectionName] = ConfigSection(sectionName);
|
|
|
|
|
|
|
|
this.sections[sectionName].add(parameter);
|
|
|
|
}
|
2023-03-27 15:32:29 +00:00
|
|
|
|
|
|
|
@property ConfigSection opIndex(string section = mainSection)
|
|
|
|
{
|
|
|
|
if (!this.exist)
|
|
|
|
throw new Exception("The configuration file does not exist");
|
|
|
|
if (section == mainSection && sections.length == 1)
|
|
|
|
return sections[sections.byKey.front];
|
|
|
|
if (section.length == 0)
|
|
|
|
section = mainSection;
|
|
|
|
return section in sections ? sections[section] : ConfigSection(section);
|
|
|
|
}
|
2023-03-25 21:31:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct ConfigSection
|
|
|
|
{
|
2023-03-27 13:39:46 +00:00
|
|
|
private string name = mainSection;
|
2023-03-25 21:31:25 +00:00
|
|
|
private ConfigParameter[string] parameters;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checking for the presence of a partition
|
|
|
|
* Returns: true if the parameter is missing, otherwise false
|
|
|
|
*/
|
|
|
|
@property bool empty()
|
2023-03-25 13:36:59 +00:00
|
|
|
{
|
2023-03-25 21:31:25 +00:00
|
|
|
return this.parameters.length == 0;
|
|
|
|
}
|
2023-03-25 13:36:59 +00:00
|
|
|
|
2023-03-25 21:31:25 +00:00
|
|
|
/**
|
|
|
|
* Get the parameter value
|
|
|
|
* Params:
|
|
|
|
* key = parameter from the configuration file
|
|
|
|
* Returns: the value of the parameter in the PP structure view
|
|
|
|
*/
|
|
|
|
ConfigParameter key(string key)
|
|
|
|
{
|
2023-03-27 15:32:29 +00:00
|
|
|
if (key.length == 0)
|
|
|
|
throw new Exception("The key cannot be empty");
|
2023-03-27 12:37:37 +00:00
|
|
|
if (this.empty)
|
|
|
|
throw new Exception("The selected section has no parameters or does not exist");
|
2023-03-27 13:39:46 +00:00
|
|
|
return key in this.parameters ? this.parameters[key] : ConfigParameter(key);
|
2023-03-25 21:31:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all keys and their values
|
|
|
|
* Returns: collection of properties structures PP
|
|
|
|
*/
|
|
|
|
ConfigParameter[string] keys()
|
|
|
|
{
|
|
|
|
return this.parameters;
|
|
|
|
}
|
|
|
|
|
2023-03-25 23:03:03 +00:00
|
|
|
private void add(ConfigParameter parameter)
|
2023-03-25 21:31:25 +00:00
|
|
|
{
|
2023-03-25 23:03:03 +00:00
|
|
|
if (parameter.property in parameters)
|
2023-03-29 15:43:32 +00:00
|
|
|
log.w("The parameter exists but will be overwritten");
|
2023-03-25 21:31:25 +00:00
|
|
|
this.parameters[parameter.property] = parameter;
|
2023-03-25 13:36:59 +00:00
|
|
|
}
|
2023-03-27 15:32:29 +00:00
|
|
|
|
|
|
|
ConfigParameter opIndex(string key)
|
|
|
|
{
|
|
|
|
if (key.length == 0)
|
|
|
|
throw new Exception("The key cannot be empty");
|
|
|
|
if (this.empty)
|
|
|
|
throw new Exception("The selected section has no parameters or does not exist");
|
|
|
|
return key in this.parameters ? this.parameters[key] : ConfigParameter(key);
|
|
|
|
}
|
2023-03-25 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parameter and its value with the ability to convert to the desired data type
|
|
|
|
*/
|
2023-03-25 21:31:25 +00:00
|
|
|
struct ConfigParameter
|
2023-03-25 13:36:59 +00:00
|
|
|
{
|
|
|
|
private string property;
|
|
|
|
private string value;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checking for the presence of a parameter
|
|
|
|
* Returns: true if the parameter is missing, otherwise false
|
|
|
|
*/
|
|
|
|
@property bool empty()
|
|
|
|
{
|
2023-03-27 13:39:46 +00:00
|
|
|
return this.value.length == 0;
|
2023-03-25 13:36:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a string representation of the value
|
|
|
|
* Returns: default string value
|
|
|
|
*/
|
|
|
|
@property string toString() const
|
|
|
|
{
|
|
|
|
return this.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
alias toString this;
|
|
|
|
|
|
|
|
auto opCast(T)() const
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
return this.value.to!T;
|
|
|
|
} catch (Exception e) {
|
2023-03-29 15:43:32 +00:00
|
|
|
log.w("Cannot convert type");
|
|
|
|
log.e(e);
|
2023-03-25 13:36:59 +00:00
|
|
|
return T.init;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|