Merge pull request 'dev' (#1) from dev into master

Reviewed-on: #1
This commit is contained in:
Alexander Zhirov 2023-03-26 08:38:22 +00:00
commit 8dbdea7331
6 changed files with 226 additions and 114 deletions

11
CHANGELOG.md Normal file
View File

@ -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

View File

@ -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"`

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 137 KiB

View File

@ -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,15 +60,29 @@ 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]));
} }
} }
@ -95,10 +90,12 @@ private:
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;
}
} }
} }

View File

@ -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

View File

@ -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"));
// } // }