commit
8dbdea7331
|
@ -0,0 +1,11 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [0.2.0]()
|
||||||
|
|
||||||
|
26.03.2023
|
||||||
|
|
||||||
|
### New
|
||||||
|
|
||||||
|
- Adding sections support
|
||||||
|
- Taking into account spaces and tabs to separate the parameter, value and comment
|
||||||
|
- Added aliases to convenient parameter access to the value
|
44
README.md
44
README.md
|
@ -2,6 +2,13 @@
|
||||||
|
|
||||||
Singleton for reading the configuration file required for your program.
|
Singleton for reading the configuration file required for your program.
|
||||||
|
|
||||||
|
## What can do
|
||||||
|
|
||||||
|
1. Separation of parameter and value by separators `=` and `=>`
|
||||||
|
2. Commenting on lines using special characters `;`, `#`, `//`, `/*`
|
||||||
|
3. Support for sections for describing parameter blocks (sections are set by the name in `[]`)
|
||||||
|
4. Support for spaces and tabs for visual separation
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
The `settings.conf` file (see the [tests](tests/)):
|
The `settings.conf` file (see the [tests](tests/)):
|
||||||
|
@ -16,27 +23,36 @@ import std.stdio;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
Config.file.read("./settings.conf");
|
rc.read("./settings.conf");
|
||||||
|
|
||||||
foreach (key, param; Config.file.keys())
|
foreach (key, param; rc.sn.keys())
|
||||||
writefln("%s => %s", key, param);
|
writefln("%s => %s", key, param);
|
||||||
|
|
||||||
writeln(Config.file.key("value1"));
|
writeln(rc.sn.key("value1"));
|
||||||
|
|
||||||
|
foreach (key, param; rc.sn("part2").keys())
|
||||||
|
writefln("%s => %s", key, param);
|
||||||
|
|
||||||
|
writeln(rc.sn("part2").key("value1"));
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Result:
|
Result:
|
||||||
|
|
||||||
```
|
```
|
||||||
value1 => This is the full value
|
value1 => text without quotes
|
||||||
value2 => Take the value in quotation marks
|
value2 => Yes!
|
||||||
value3 => Or take in apostrophes
|
value3 => value in apostrophes
|
||||||
value4 => You can also comment
|
value4 => 1000
|
||||||
value5 => So you can also comment
|
value5 => 0.000
|
||||||
value6 => "And you can even do that!"
|
value7 => //path
|
||||||
value7 => 1234567890
|
value8 => "Hey!"
|
||||||
value8 => 12345.67890
|
text without quotes
|
||||||
value9 => You can use large margins
|
value1 => this value will be in the new section
|
||||||
value12 => //path
|
value3 => good value!
|
||||||
This is the full value
|
this value will be in the new section
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Dub
|
||||||
|
|
||||||
|
Add a dependency on `"readconf": "~>0.2.0"`
|
||||||
|
|
BIN
img/matches.png
BIN
img/matches.png
Binary file not shown.
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 137 KiB |
|
@ -6,72 +6,53 @@ import std.regex;
|
||||||
import std.meta;
|
import std.meta;
|
||||||
import singlog;
|
import singlog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read config object
|
||||||
|
*/
|
||||||
|
alias rc = Config.file;
|
||||||
|
|
||||||
class Config
|
class Config
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
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, // "[]"
|
||||||
|
}
|
||||||
|
|
||||||
static Config config;
|
static Config config;
|
||||||
string path;
|
string path;
|
||||||
PP[string] properties;
|
|
||||||
bool readed = false;
|
bool readed = false;
|
||||||
|
ConfigSection[string] sections;
|
||||||
|
|
||||||
/**
|
const string pattern = "^( |\\t)*(((\\w(\\w|-)+)(( |\\t)*(=>|=){1}"
|
||||||
* Parameter and its value with the ability to convert to the desired data type
|
~ "( |\\t)*)(?!\\/(\\/|\\*))(([^ >\"'=\\n\\t#;].*?)|(\"(.+)\")"
|
||||||
*/
|
~ "|('(.+)')){1})|(\\[(\\w(\\w|-)+)\\])|(\\[\\]))( |\\t)*"
|
||||||
struct PP
|
~ "(( |\\t)(#|;|\\/\\/|\\/\\*).*)?$";
|
||||||
{
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
return this.property.length == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a string representation of the value
|
|
||||||
* Returns: default string value
|
|
||||||
*/
|
|
||||||
@property string toString() const
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
alias toString this;
|
|
||||||
|
|
||||||
auto opCast(T)() const
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return this.value.to!T;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.msg.error("Cannot convert type");
|
|
||||||
Log.msg.warning(e);
|
|
||||||
return T.init;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reading the configuration file
|
* Reading the configuration file
|
||||||
*/
|
*/
|
||||||
void readConfig()
|
bool readConfig()
|
||||||
{
|
{
|
||||||
File configuration;
|
File configuration;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
configuration = File(this.path, "r");
|
configuration = File(this.path, "r");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.msg.error("Unable to open the configuration file " ~ this.path);
|
Log.msg.warning("Unable to open the configuration file " ~ this.path);
|
||||||
Log.msg.warning(e);
|
Log.msg.error(e);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
string pattern = "^ *(\\w+)(( +=> +)|( += +))(?!\\/\\/)(([^ >\"'\\n#;].*?)|"
|
auto regular = regex(this.pattern, "m");
|
||||||
~ "(\"(.+?)\")|('(.+?)')){1} *( #.*?)?( ;.*?)?( \\/\\/.*)?$";
|
|
||||||
auto regular = regex(pattern, "m");
|
// reading from the main section
|
||||||
|
string sectionName = "[]";
|
||||||
|
|
||||||
while (!configuration.eof())
|
while (!configuration.eof())
|
||||||
{
|
{
|
||||||
|
@ -79,26 +60,42 @@ private:
|
||||||
auto match = matchFirst(line, regular);
|
auto match = matchFirst(line, regular);
|
||||||
if (match)
|
if (match)
|
||||||
{
|
{
|
||||||
int group = 5;
|
// if again main section
|
||||||
if (match[group].length)
|
if (match[GROUP_SECTION_MAIN].length)
|
||||||
{
|
{
|
||||||
if (match[group][0] == '\'')
|
sectionName = match[GROUP_SECTION_MAIN];
|
||||||
group = 10;
|
continue;
|
||||||
else if (match[group][0] == '\"')
|
|
||||||
group = 8;
|
|
||||||
}
|
}
|
||||||
this.properties[match[1]] = PP(match[1], match[group]);
|
if (match[GROUP_SECTION_OTHER_OUTER].length)
|
||||||
}
|
{
|
||||||
|
sectionName = match[GROUP_SECTION_OTHER_INNER];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int group = GROUP_VALUE_1;
|
||||||
|
|
||||||
|
if (match[group][0] == '\"')
|
||||||
|
group = GROUP_VALUE_2;
|
||||||
|
else if (match[group][0] == '\'')
|
||||||
|
group = GROUP_VALUE_3;
|
||||||
|
|
||||||
|
if (sectionName !in this.sections)
|
||||||
|
this.sections[sectionName] = ConfigSection(sectionName);
|
||||||
|
|
||||||
|
this.sections[sectionName].add(ConfigParameter(match[GROUP_PROPERTY], match[group]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
configuration.close();
|
configuration.close();
|
||||||
this.readed = true;
|
this.readed = true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.msg.error("Unable to close the configuration file " ~ this.path);
|
Log.msg.warning("Unable to close the configuration file " ~ this.path);
|
||||||
Log.msg.warning(e);
|
Log.msg.error(e);
|
||||||
return;
|
this.readed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this.readed;
|
||||||
}
|
}
|
||||||
|
|
||||||
this() {}
|
this() {}
|
||||||
|
@ -121,15 +118,46 @@ public:
|
||||||
* Params:
|
* Params:
|
||||||
* path = the path to the configuration file
|
* path = the path to the configuration file
|
||||||
*/
|
*/
|
||||||
void read(string path)
|
bool read(string path)
|
||||||
{
|
{
|
||||||
this.path = path;
|
this.path = path;
|
||||||
if (!path.exists)
|
if (!path.exists)
|
||||||
{
|
throw new Exception("The configuration file does not exist: " ~ path);
|
||||||
Log.msg.error("The configuration file does not exist: " ~ path);
|
return readConfig();
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
readConfig();
|
/**
|
||||||
|
* Get the section
|
||||||
|
* Params:
|
||||||
|
* section = section name (default main "[]")
|
||||||
|
*/
|
||||||
|
@property ConfigSection sectionName(string section = "[]")
|
||||||
|
{
|
||||||
|
return section in sections ? sections[section] : ConfigSection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Section name
|
||||||
|
*
|
||||||
|
* Get the section
|
||||||
|
* Params:
|
||||||
|
* section = section name (default main "[]")
|
||||||
|
*/
|
||||||
|
alias sn = sectionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConfigSection
|
||||||
|
{
|
||||||
|
private string name = "[]";
|
||||||
|
private ConfigParameter[string] parameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checking for the presence of a partition
|
||||||
|
* Returns: true if the parameter is missing, otherwise false
|
||||||
|
*/
|
||||||
|
@property bool empty()
|
||||||
|
{
|
||||||
|
return this.parameters.length == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -138,20 +166,64 @@ public:
|
||||||
* key = parameter from the configuration file
|
* key = parameter from the configuration file
|
||||||
* Returns: the value of the parameter in the PP structure view
|
* Returns: the value of the parameter in the PP structure view
|
||||||
*/
|
*/
|
||||||
PP key(string key)
|
ConfigParameter key(string key)
|
||||||
{
|
{
|
||||||
if (this.readed)
|
return key in this.parameters ? this.parameters[key] : ConfigParameter();
|
||||||
return key in this.properties ? this.properties[key] : PP();
|
|
||||||
Log.msg.warning("The configuration file was not read!");
|
|
||||||
return PP();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all keys and their values
|
* Get all keys and their values
|
||||||
* Returns: collection of properties structures PP
|
* Returns: collection of properties structures PP
|
||||||
*/
|
*/
|
||||||
PP[string] keys()
|
ConfigParameter[string] keys()
|
||||||
{
|
{
|
||||||
return this.properties;
|
return this.parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(ConfigParameter parameter)
|
||||||
|
{
|
||||||
|
if (parameter.property in parameters)
|
||||||
|
Log.msg.warning("The parameter exists but will be overwritten");
|
||||||
|
this.parameters[parameter.property] = parameter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameter and its value with the ability to convert to the desired data type
|
||||||
|
*/
|
||||||
|
struct ConfigParameter
|
||||||
|
{
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
return this.property.length == 0 || this.value.length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
Log.msg.warning("Cannot convert type");
|
||||||
|
Log.msg.error(e);
|
||||||
|
return T.init;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,23 @@
|
||||||
Such a line will not be read
|
Such a line will not be read
|
||||||
value1 = This is the full value
|
The main section is set by default
|
||||||
value2 = "Take the value in quotation marks"
|
value1=text without quotes
|
||||||
value3 = 'Or take in apostrophes'
|
value2=>"value in quotation marks"
|
||||||
value4 => You can also comment // Another separator and comment
|
value3=>'value in apostrophes' ; and here is the first comment
|
||||||
value5 => 'So you can also comment' # Yeah!
|
value4 => 1000 // free space in spaces and tabs
|
||||||
value6 => '"And you can even do that!"' ; He-he;)
|
value5 => 0.000 /* different form of commenting
|
||||||
value7 = 1234567890 # decimal value
|
value6 = // the string will not be read because the parameter value is missing
|
||||||
value8 => 12345.67890 ; float value
|
value7 = '' ; limiting empty characters will also not be taken into account
|
||||||
value9 => You can use large margins
|
[part2] ; a new section!
|
||||||
value10 = // But a line without a value will not be read
|
value1 = this value will be in the new section
|
||||||
value11 = //path # not working
|
value 2 = 200 ; The parameter name is incorrect
|
||||||
value12 = "//path" // nice way (or '//path')
|
value3 => good value! # and another way of commenting
|
||||||
|
|
||||||
|
[] ; Make the main section again!
|
||||||
|
value7 => '//path' # and now the value7 will be read!
|
||||||
|
value2 = Yes! /* We will also redefine the value2
|
||||||
|
value8 => '"Hey!"' # you can separate the values in quotation marks or apostrophes
|
||||||
|
|
||||||
|
[part3] // and another section
|
||||||
|
value1=>-2 ; so you can
|
||||||
|
value2=3// but it will not work to leave a comment
|
||||||
|
value3=>100 //you need space from the value by at least 1 character
|
||||||
|
|
37
tests/test.d
37
tests/test.d
|
@ -2,30 +2,33 @@ import readconf;
|
||||||
|
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
Config.file.read("./tests/settings.conf");
|
rc.read("./tests/settings.conf");
|
||||||
|
|
||||||
assert(Config.file.key("value1") == "This is the full value");
|
assert(rc.sn.key("value1") == "text without quotes");
|
||||||
assert(Config.file.key("value2") == "Take the value in quotation marks");
|
assert(rc.sn.key("value2") == "Yes!");
|
||||||
assert(Config.file.key("value3") == "Or take in apostrophes");
|
assert(rc.sn.key("value3") == "value in apostrophes");
|
||||||
assert(Config.file.key("value4") == "You can also comment");
|
assert(rc.sn.key("value4") == "1000");
|
||||||
assert(Config.file.key("value5") == "So you can also comment");
|
assert(rc.sn.key("value5") == "0.000");
|
||||||
assert(Config.file.key("value6") == "\"And you can even do that!\"");
|
assert(rc.sn.key("value7") == "//path");
|
||||||
assert(Config.file.key("value7") == "1234567890");
|
assert(rc.sn.key("value8") == "\"Hey!\"");
|
||||||
assert(Config.file.key("value8") == "12345.67890");
|
assert(rc.sn("part2").key("value1") == "this value will be in the new section");
|
||||||
assert(Config.file.key("value9") == "You can use large margins");
|
assert(rc.sn("part2").key("value3") == "good value!");
|
||||||
assert(Config.file.key("value10").empty);
|
assert(rc.sn("part3").key("value1") == "-2");
|
||||||
assert(Config.file.key("value11").empty);
|
assert(rc.sn("part3").key("value3") == "100");
|
||||||
assert(Config.file.key("value12") == "//path");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// void main()
|
// void main()
|
||||||
// {
|
// {
|
||||||
// import std.stdio;
|
// import std.stdio;
|
||||||
// Config.file.read("./tests/settings.conf");
|
// rc.read("./tests/settings.conf");
|
||||||
|
|
||||||
// foreach (key, param; Config.file.keys())
|
// foreach (key, param; rc.sn.keys())
|
||||||
// writefln("%s => %s", key, param);
|
// writefln("%s => %s", key, param);
|
||||||
|
|
||||||
// writeln(Config.file.key("value1"));
|
// writeln(rc.sn.key("value1"));
|
||||||
|
|
||||||
|
// foreach (key, param; rc.sn("part2").keys())
|
||||||
|
// writefln("%s => %s", key, param);
|
||||||
|
|
||||||
|
// writeln(rc.sn("part2").key("value1"));
|
||||||
// }
|
// }
|
||||||
|
|
Loading…
Reference in New Issue