This commit is contained in:
Alexander Zhirov 2023-03-30 10:15:54 +03:00
commit 2b9e0366f4
10 changed files with 384 additions and 95 deletions

View File

@ -1,11 +1,36 @@
# Changelog # Changelog
## [0.2.0]() ## [v0.3.0](https://git.zhirov.kz/dlang/readconf/compare/v0.2.0...v0.3.0) (2023.03.30)
26.03.2023 ### New
- Read multiple configuration files
- Quick access to a file/section/parameter using indexes
### Other
- Updated unittests
- Added [examples](examples/) of configuration files
- [Wiki](https://git.zhirov.kz/dlang/readconf/wiki) added
## [v0.2.0](https://git.zhirov.kz/dlang/readconf/compare/v0.1.1...v0.2.0) (2023.03.26)
### New ### New
- Adding sections support - Adding sections support
- Taking into account spaces and tabs to separate the parameter, value and comment - Taking into account spaces and tabs to separate the parameter, value and comment
- Added aliases to convenient parameter access to the value - Added aliases to convenient parameter access to the value
## [v0.1.1](https://git.zhirov.kz/dlang/readconf/compare/v0.1.0...v0.1.1) (2023.03.24)
### Bug fixes
- Checking empty keys
- Reading an expression in quotation marks
## [v0.1.0](https://git.zhirov.kz/dlang/readconf/commits/6409917cbe6a287db73fe3eea4bccaadf00379e7) (2023.03.23)
### The first stable working release
- The parameters are separated by `=` or `=>`
- The ability to comment lines through delimiters `;`, `#`, `//`

View File

@ -1,13 +1,25 @@
# readconf <p align="center">
<img src="img/logo.png" width=320>
</p>
<h1 align="center">readconf</h1>
[![license](https://img.shields.io/github/license/AlexanderZhirov/readconf.svg?sort=semver&style=for-the-badge&color=green)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html)
[![main](https://img.shields.io/badge/dynamic/json.svg?label=git.zhirov.kz&style=for-the-badge&url=https://git.zhirov.kz/api/v1/repos/dlang/readconf/tags&query=$[0].name&color=violet&logo=D)](https://git.zhirov.kz/dlang/readconf)
[![githab](https://img.shields.io/github/v/tag/AlexanderZhirov/readconf.svg?sort=semver&style=for-the-badge&color=blue&label=github&logo=D)](https://github.com/AlexanderZhirov/readconf)
[![dub](https://img.shields.io/dub/v/readconf.svg?sort=semver&style=for-the-badge&color=orange&logo=D)](https://code.dlang.org/packages/readconf)
[![linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black)](https://www.linux.org/)
Singleton for reading the configuration file required for your program. Singleton for reading the configuration file required for your program.
## What can do ## What can do
1. Separation of parameter and value by separators `=` and `=>` - Reading multiple configuration files
2. Commenting on lines using special characters `;`, `#`, `//`, `/*` - Separation of parameters by sections
3. Support for sections for describing parameter blocks (sections are set by the name in `[]`) - Access to parameters and sections using keys and indexes
4. Support for spaces and tabs for visual separation - Commenting on lines
You will get more detailed information on the [wiki](https://git.zhirov.kz/dlang/readconf/wiki).
## Quick start ## Quick start
@ -23,7 +35,7 @@ import std.stdio;
void main() void main()
{ {
rc.read("./settings.conf"); rc.read("./tests/settings.conf");
foreach (key, param; rc.sn.keys()) foreach (key, param; rc.sn.keys())
writefln("%s => %s", key, param); writefln("%s => %s", key, param);
@ -33,7 +45,7 @@ void main()
foreach (key, param; rc.sn("part2").keys()) foreach (key, param; rc.sn("part2").keys())
writefln("%s => %s", key, param); writefln("%s => %s", key, param);
writeln(rc.sn("part2").key("value1")); writeln(rc["part2"]["value1"]);
} }
``` ```
@ -53,6 +65,20 @@ value3 => good value!
this value will be in the new section this value will be in the new section
``` ```
## Dub ## Unittests
Add a dependency on `"readconf": "~>0.2.0"` The unittests provide [examples](examples/) of configuration files and the `settings.conf` file located in the [tests](tests/):
```sh
Running bin/readconf-test-unittest
✓ test __unittest_L4_C1
✓ test __unittest_L106_C1
✓ test __unittest_L25_C1
✓ test __unittest_L51_C1
Summary: 4 passed, 0 failed in 7 ms
```
## DUB
Add a dependency on `"readconf": "~>0.3.0"`

View File

@ -33,6 +33,6 @@
], ],
"targetName": "readconf", "targetName": "readconf",
"dependencies": { "dependencies": {
"singlog": "~>0.1.0" "singlog": "~>0.2.1"
} }
} }

View File

@ -3,6 +3,6 @@
"versions": { "versions": {
"datefmt": "1.0.4", "datefmt": "1.0.4",
"silly": "1.1.1", "silly": "1.1.1",
"singlog": "0.1.0" "singlog": "0.2.1"
} }
} }

11
examples/comments.conf Normal file
View File

@ -0,0 +1,11 @@
This line will be a comment, since it does not match the basic template
parameter1 => value1 ; This will be a comment
parameter2 => value2 # This will be a comment
parameter3 => value3 // This will be a comment
parameter4 => value4 /* This will be a comment
parameter5 => value5;This will not be a comment
parameter6 => value6// This will also be a whole value
parameter7 => //value7 ;The value will not be read
parameter8 => "//value8" # Now the value is correctly
parameter9 => ';value9' // The value is correctly too
parameter10 => '"value10"' // Quotes inside

22
examples/sections.conf Normal file
View File

@ -0,0 +1,22 @@
parameter1 = value1
parameter_2 = value2
[first-section] ; Creating the first section
parameter1 = value3 ; A section may contain similar parameter names
parameter_2 = value4
[second-section] // Another section
parameter1 = value5
parameter_2 = value6
[] /* Addition of the main section
parameter3 = value7 /* A new parameter will be added
parameter1 = value8 /* And parameter1 will be redefined
[first-section] # Addition of the first section
parameter3 = value9
parameter4 = value10
[_section] # Creating the new section
parameter1 = value11
parameter2 = value12

16
examples/simple.conf Normal file
View File

@ -0,0 +1,16 @@
parameter1=value1
parameter2=>value2
parameter3=value3
parameter4=>value4
_parameter5 = value5
parameter6 => value6
parameter7 = value7
parameter8 => value8
parameter9 =value9
parameter-10 =>value10
parameter11 = value11
parameter12_ => value12

BIN
img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@ -7,27 +7,32 @@ import std.meta;
import singlog; import singlog;
/** /**
* Read config object * **Get an object to read the configuration file**
*
* - The `read()` will allow you to read the configuration file
* - `cf()` or `configFile()` will allow you to refer to the read file to get the parameters
*/ */
alias rc = Config.file; alias rc = Config.file;
private const string mainSection = "[]";
class Config class Config
{ {
private: private:
enum { enum {
GROUP_PROPERTY = 4, GROUP_PARAMETER = 4,
GROUP_VALUE_1 = 11, // string GROUP_VALUE_1 = 11, // string
GROUP_VALUE_2 = 14, // "strin" GROUP_VALUE_2 = 14, // "strin"
GROUP_VALUE_3 = 16, // 'string' GROUP_VALUE_3 = 16, // 'string'
GROUP_SECTION_OTHER_OUTER = 17, // "[string]" GROUP_SECTION_OTHER_OUTER = 17, // [string]
GROUP_SECTION_OTHER_INNER = 18, // "[string]" GROUP_SECTION_OTHER_INNER = 18, // string
GROUP_SECTION_MAIN = 20, // "[]" GROUP_SECTION_MAIN = 20, // []
} }
static Config config; static Config config;
string path; string path;
bool readed = false; bool readed = false;
ConfigSection[string] sections; ConfigFile[string] configs;
const string pattern = "^( |\\t)*(((\\w(\\w|-)+)(( |\\t)*(=>|=){1}" const string pattern = "^( |\\t)*(((\\w(\\w|-)+)(( |\\t)*(=>|=){1}"
~ "( |\\t)*)(?!\\/(\\/|\\*))(([^ >\"'=\\n\\t#;].*?)|(\"(.+)\")" ~ "( |\\t)*)(?!\\/(\\/|\\*))(([^ >\"'=\\n\\t#;].*?)|(\"(.+)\")"
@ -37,61 +42,63 @@ private:
/** /**
* Reading the configuration file * Reading the configuration file
*/ */
bool readConfig() bool readConfig(const string configName)
{ {
File configuration; File configuration;
try { try {
configuration = File(this.path, "r"); configuration = File(this.path, "r");
} catch (Exception e) { } catch (Exception e) {
Log.msg.warning("Unable to open the configuration file " ~ this.path); log.w("Unable to open the configuration file " ~ this.path);
Log.msg.error(e); log.e(e);
return false; return false;
} }
if (configName !in this.configs)
this.configs[configName] = ConfigFile(configName);
auto regular = regex(this.pattern, "m"); auto regular = regex(this.pattern, "m");
// reading from the main section // reading from the main section
string sectionName = "[]"; string sectionName = mainSection;
while (!configuration.eof()) while (!configuration.eof())
{ {
string line = configuration.readln(); string line = configuration.readln();
auto match = matchFirst(line, regular); auto match = matchFirst(line, regular);
if (match)
if (match.length == 0)
continue;
// if again main section
if (match[GROUP_SECTION_MAIN].length)
{ {
// if again main section sectionName = match[GROUP_SECTION_MAIN];
if (match[GROUP_SECTION_MAIN].length) continue;
{
sectionName = match[GROUP_SECTION_MAIN];
continue;
}
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]));
} }
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;
this.configs[configName].add(sectionName, ConfigParameter(match[GROUP_PARAMETER], match[group]));
} }
try { try {
configuration.close(); configuration.close();
this.readed = true; this.readed = true;
} catch (Exception e) { } catch (Exception e) {
Log.msg.warning("Unable to close the configuration file " ~ this.path); log.w("Unable to close the configuration file " ~ this.path);
Log.msg.error(e); log.e(e);
this.configs.remove(configName);
this.readed = false; this.readed = false;
} }
@ -117,38 +124,123 @@ public:
* Read the configuration file * Read the configuration file
* Params: * Params:
* path = the path to the configuration file * path = the path to the configuration file
* configName = a specific name to bind to the configuration file (default file name)
* Returns: `true` if the file was read successfully
*/ */
bool read(string path) bool read(string path, string configName = "")
{ {
this.path = path; this.path = path;
if (!path.exists) if (!path.exists)
throw new Exception("The configuration file does not exist: " ~ path); throw new Exception("The configuration file does not exist: " ~ path);
return readConfig(); 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);
}
/**
* Accessing the read configuration file
* Params:
* configName = specific name to bind to the configuration file
* (if the read files are > 1, then specify a specific name, otherwise default file name)
* Returns: configuration file object ConfigFile
*/
@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");
}
return configName in configs ? configs[configName] : ConfigFile(configName);
}
/**
* Get the read configuration file
* Params:
* configName = specific name to bind to the configuration file
* (if the read files are > 1, then specify a specific name, otherwise default file name)
* Returns: configuration file object ConfigFile
*/
alias cf = configFile;
@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);
}
}
struct ConfigFile
{
private string name;
private ConfigSection[string] sections;
@property bool exist()
{
return this.sections.length > 0;
} }
/** /**
* Get the section * Get the section
* Params: * Params:
* section = section name (default main "[]") * section = section name (default main section)
* Returns: the object of the configuration file section ConfigSection
*/ */
@property ConfigSection sectionName(string section = "[]") @property ConfigSection sectionName(string section = mainSection)
{ {
return section in sections ? sections[section] : ConfigSection(); 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);
} }
/** /**
* Section name
*
* Get the section * Get the section
* Params: * Params:
* section = section name (default main "[]") * section = section name (default main section)
* Returns: the object of the configuration file section ConfigSection
*/ */
alias sn = sectionName; alias sn = sectionName;
private void add(string sectionName, ConfigParameter parameter)
{
if (sectionName !in this.sections)
this.sections[sectionName] = ConfigSection(sectionName);
this.sections[sectionName].add(parameter);
}
@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);
}
} }
struct ConfigSection struct ConfigSection
{ {
private string name = "[]"; private string name = mainSection;
private ConfigParameter[string] parameters; private ConfigParameter[string] parameters;
/** /**
@ -164,16 +256,20 @@ struct ConfigSection
* Get the parameter value * Get the parameter value
* Params: * Params:
* 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 object of the parameter ConfigParameter
*/ */
ConfigParameter key(string key) ConfigParameter key(string key)
{ {
return key in this.parameters ? this.parameters[key] : ConfigParameter(); 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);
} }
/** /**
* Get all keys and their values * Get all keys and their values
* Returns: collection of properties structures PP * Returns: collection of parameters
*/ */
ConfigParameter[string] keys() ConfigParameter[string] keys()
{ {
@ -183,14 +279,20 @@ struct ConfigSection
private void add(ConfigParameter parameter) private void add(ConfigParameter parameter)
{ {
if (parameter.property in parameters) if (parameter.property in parameters)
Log.msg.warning("The parameter exists but will be overwritten"); log.w("The parameter exists but will be overwritten");
this.parameters[parameter.property] = parameter; this.parameters[parameter.property] = parameter;
} }
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);
}
} }
/**
* Parameter and its value with the ability to convert to the desired data type
*/
struct ConfigParameter struct ConfigParameter
{ {
private string property; private string property;
@ -202,7 +304,7 @@ struct ConfigParameter
*/ */
@property bool empty() @property bool empty()
{ {
return this.property.length == 0 || this.value.length == 0; return this.value.length == 0;
} }
/** /**
@ -221,8 +323,8 @@ struct ConfigParameter
try { try {
return this.value.to!T; return this.value.to!T;
} catch (Exception e) { } catch (Exception e) {
Log.msg.warning("Cannot convert type"); log.w("Cannot convert type");
Log.msg.error(e); log.e(e);
return T.init; return T.init;
} }
} }

View File

@ -1,34 +1,121 @@
import readconf; import readconf;
import std.conv;
unittest
{
rc.read("./examples/simple.conf");
auto configFile = rc.cf;
auto mainSection = configFile.sn;
assert(mainSection.key("parameter1") == "value1");
assert(mainSection["parameter2"] == "value2");
assert(mainSection.key("parameter3") == "value3");
assert(mainSection["parameter4"] == "value4");
assert(mainSection.key("_parameter5") == "value5");
assert(mainSection["parameter6"] == "value6");
assert(mainSection.key("parameter7") == "value7");
assert(mainSection["parameter8"] == "value8");
assert(mainSection.key("parameter9") == "value9");
assert(mainSection["parameter-10"] == "value10");
assert(mainSection.key("parameter11") == "value11");
assert(mainSection["parameter12_"] == "value12");
}
unittest
{
rc.read("./examples/sections.conf");
auto configFile = rc.cf;
auto mainSection = configFile.sn;
auto firstSection = configFile.sn("first-section");
auto secondSection = configFile["second-section"];
auto section = configFile["_section"];
assert(mainSection.key("parameter1") == "value8");
assert(mainSection["parameter_2"] == "value2");
assert(mainSection["parameter3"] == "value7");
assert(firstSection["parameter1"] == "value3");
assert(firstSection["parameter_2"] == "value4");
assert(firstSection["parameter3"] == "value9");
assert(firstSection["parameter4"] == "value10");
assert(secondSection["parameter1"] == "value5");
assert(secondSection["parameter_2"] == "value6");
assert(section["parameter1"] == "value11");
assert(section["parameter2"] == "value12");
}
unittest
{
rc.read("./examples/simple.conf", "simple");
rc.read("./examples/sections.conf");
rc.read("./examples/comments.conf", "comments");
auto simpleConfig = rc.cf("simple");
auto sectionsConfig = rc["sections.conf"];
auto commentsConfig = rc["comments"];
auto simConMaiSec = simpleConfig.sn;
assert(simConMaiSec.key("parameter1") == "value1");
assert(simConMaiSec["parameter2"] == "value2");
assert(simConMaiSec.key("parameter3") == "value3");
assert(simConMaiSec["parameter4"] == "value4");
assert(simConMaiSec.key("_parameter5") == "value5");
assert(simConMaiSec["parameter6"] == "value6");
assert(simConMaiSec.key("parameter7") == "value7");
assert(simConMaiSec["parameter8"] == "value8");
assert(simConMaiSec.key("parameter9") == "value9");
assert(simConMaiSec["parameter-10"] == "value10");
assert(simConMaiSec.key("parameter11") == "value11");
assert(simConMaiSec["parameter12_"] == "value12");
auto secConMaiSec = sectionsConfig.sn;
auto secConFirSec = sectionsConfig.sn("first-section");
auto secConSecSec = sectionsConfig["second-section"];
auto secConSec = sectionsConfig["_section"];
assert(secConMaiSec.key("parameter1") == "value8");
assert(secConMaiSec["parameter_2"] == "value2");
assert(secConMaiSec["parameter3"] == "value7");
assert(secConFirSec["parameter1"] == "value3");
assert(secConFirSec["parameter_2"] == "value4");
assert(secConFirSec["parameter3"] == "value9");
assert(secConFirSec["parameter4"] == "value10");
assert(secConSecSec["parameter1"] == "value5");
assert(secConSecSec["parameter_2"] == "value6");
assert(secConSec["parameter1"] == "value11");
assert(secConSec["parameter2"] == "value12");
auto comConMaiSec = commentsConfig.sn;
assert(comConMaiSec["parameter1"] == "value1");
assert(comConMaiSec["parameter2"] == "value2");
assert(comConMaiSec["parameter3"] == "value3");
assert(comConMaiSec["parameter4"] == "value4");
assert(comConMaiSec["parameter5"] == "value5;This will not be a comment");
assert(comConMaiSec["parameter6"] == "value6// This will also be a whole value");
assert(comConMaiSec["parameter8"] == "//value8");
assert(comConMaiSec["parameter9"] == ";value9");
assert(comConMaiSec["parameter10"] == "\"value10\"");
}
unittest unittest
{ {
rc.read("./tests/settings.conf"); rc.read("./tests/settings.conf");
assert(rc.sn.key("value1") == "text without quotes"); assert(rc.cf.sn.key("value1") == "text without quotes");
assert(rc.sn.key("value2") == "Yes!"); assert(rc[][]["value2"] == "Yes!");
assert(rc.sn.key("value3") == "value in apostrophes"); assert(rc.cf.sn.key("value3") == "value in apostrophes");
assert(rc.sn.key("value4") == "1000"); assert(rc[][]["value4"] == "1000");
assert(rc.sn.key("value5") == "0.000"); assert(rc.cf.sn["value5"] == "0.000");
assert(rc.sn.key("value7") == "//path"); assert(rc[][].key("value7") == "//path");
assert(rc.sn.key("value8") == "\"Hey!\""); assert(rc.cf.sn.key("value8") == "\"Hey!\"");
assert(rc.sn("part2").key("value1") == "this value will be in the new section"); assert(rc[]["part2"]["value1"] == "this value will be in the new section");
assert(rc.sn("part2").key("value3") == "good value!"); assert(rc.cf.sn("part2").key("value3") == "good value!");
assert(rc.sn("part3").key("value1") == "-2"); assert(rc[].sn("part3").key("value1") == "-2");
assert(rc.sn("part3").key("value3") == "100"); assert(rc.cf["part3"]["value3"] == "100");
} }
// void main()
// {
// import std.stdio;
// rc.read("./tests/settings.conf");
// foreach (key, param; rc.sn.keys())
// writefln("%s => %s", key, param);
// writeln(rc.sn.key("value1"));
// foreach (key, param; rc.sn("part2").keys())
// writefln("%s => %s", key, param);
// writeln(rc.sn("part2").key("value1"));
// }