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
## [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
- 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
## [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.
## 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
- Reading multiple configuration files
- Separation of parameters by sections
- Access to parameters and sections using keys and indexes
- Commenting on lines
You will get more detailed information on the [wiki](https://git.zhirov.kz/dlang/readconf/wiki).
## Quick start
@ -23,7 +35,7 @@ import std.stdio;
void main()
{
rc.read("./settings.conf");
rc.read("./tests/settings.conf");
foreach (key, param; rc.sn.keys())
writefln("%s => %s", key, param);
@ -33,7 +45,7 @@ void main()
foreach (key, param; rc.sn("part2").keys())
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
```
## 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",
"dependencies": {
"singlog": "~>0.1.0"
"singlog": "~>0.2.1"
}
}

View File

@ -3,6 +3,6 @@
"versions": {
"datefmt": "1.0.4",
"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;
/**
* 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;
private const string mainSection = "[]";
class Config
{
private:
enum {
GROUP_PROPERTY = 4,
GROUP_PARAMETER = 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, // "[]"
GROUP_SECTION_OTHER_OUTER = 17, // [string]
GROUP_SECTION_OTHER_INNER = 18, // string
GROUP_SECTION_MAIN = 20, // []
}
static Config config;
string path;
bool readed = false;
ConfigSection[string] sections;
ConfigFile[string] configs;
const string pattern = "^( |\\t)*(((\\w(\\w|-)+)(( |\\t)*(=>|=){1}"
~ "( |\\t)*)(?!\\/(\\/|\\*))(([^ >\"'=\\n\\t#;].*?)|(\"(.+)\")"
@ -37,61 +42,63 @@ private:
/**
* Reading the configuration file
*/
bool readConfig()
bool readConfig(const string configName)
{
File configuration;
try {
configuration = File(this.path, "r");
} catch (Exception e) {
Log.msg.warning("Unable to open the configuration file " ~ this.path);
Log.msg.error(e);
log.w("Unable to open the configuration file " ~ this.path);
log.e(e);
return false;
}
if (configName !in this.configs)
this.configs[configName] = ConfigFile(configName);
auto regular = regex(this.pattern, "m");
// reading from the main section
string sectionName = "[]";
string sectionName = mainSection;
while (!configuration.eof())
{
string line = configuration.readln();
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
if (match[GROUP_SECTION_MAIN].length)
{
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]));
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;
this.configs[configName].add(sectionName, ConfigParameter(match[GROUP_PARAMETER], match[group]));
}
try {
configuration.close();
this.readed = true;
} catch (Exception e) {
Log.msg.warning("Unable to close the configuration file " ~ this.path);
Log.msg.error(e);
log.w("Unable to close the configuration file " ~ this.path);
log.e(e);
this.configs.remove(configName);
this.readed = false;
}
@ -117,38 +124,123 @@ public:
* Read the configuration file
* Params:
* 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;
if (!path.exists)
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
* 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
* Params:
* section = section name (default main "[]")
* section = section name (default main section)
* Returns: the object of the configuration file section ConfigSection
*/
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
{
private string name = "[]";
private string name = mainSection;
private ConfigParameter[string] parameters;
/**
@ -164,16 +256,20 @@ struct ConfigSection
* Get the parameter value
* Params:
* 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)
{
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
* Returns: collection of properties structures PP
* Returns: collection of parameters
*/
ConfigParameter[string] keys()
{
@ -183,14 +279,20 @@ struct ConfigSection
private void add(ConfigParameter parameter)
{
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;
}
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
{
private string property;
@ -202,7 +304,7 @@ struct ConfigParameter
*/
@property bool empty()
{
return this.property.length == 0 || this.value.length == 0;
return this.value.length == 0;
}
/**
@ -221,8 +323,8 @@ struct ConfigParameter
try {
return this.value.to!T;
} catch (Exception e) {
Log.msg.warning("Cannot convert type");
Log.msg.error(e);
log.w("Cannot convert type");
log.e(e);
return T.init;
}
}

View File

@ -1,34 +1,121 @@
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
{
rc.read("./tests/settings.conf");
assert(rc.sn.key("value1") == "text without quotes");
assert(rc.sn.key("value2") == "Yes!");
assert(rc.sn.key("value3") == "value in apostrophes");
assert(rc.sn.key("value4") == "1000");
assert(rc.sn.key("value5") == "0.000");
assert(rc.sn.key("value7") == "//path");
assert(rc.sn.key("value8") == "\"Hey!\"");
assert(rc.sn("part2").key("value1") == "this value will be in the new section");
assert(rc.sn("part2").key("value3") == "good value!");
assert(rc.sn("part3").key("value1") == "-2");
assert(rc.sn("part3").key("value3") == "100");
assert(rc.cf.sn.key("value1") == "text without quotes");
assert(rc[][]["value2"] == "Yes!");
assert(rc.cf.sn.key("value3") == "value in apostrophes");
assert(rc[][]["value4"] == "1000");
assert(rc.cf.sn["value5"] == "0.000");
assert(rc[][].key("value7") == "//path");
assert(rc.cf.sn.key("value8") == "\"Hey!\"");
assert(rc[]["part2"]["value1"] == "this value will be in the new section");
assert(rc.cf.sn("part2").key("value3") == "good value!");
assert(rc[].sn("part3").key("value1") == "-2");
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"));
// }