Skip to content

Commit

Permalink
Described syntax of persisting data (#420)
Browse files Browse the repository at this point in the history
* Create a document describing the syntax of persisting data. 
* Added a new method to get and set a list of string in values.
  • Loading branch information
aregtech authored Oct 29, 2024
1 parent 46ec934 commit 64d476c
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 6 deletions.
88 changes: 88 additions & 0 deletions docs/wiki/persistence-syntax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Key-Value Data Persistence Syntax in AREG SDK

## Overview

The AREG Framework simplifies data persistence with lightweight Key-Value pairs, stored in plain text. Unlike complex formats like XML or JSON, AREG's [persistence module](./../../framework/areg/persist) is optimized for efficient data handling and quick initialization. The persistence module is not suited for large, complex data structures but is ideal for straightforward configuration storage.

---

## Table of Contents
1. [General Information](#general-information)
2. [Syntax Basics](#syntax-basics)
3. [Property Key](#property-key)
4. [Property Value](#property-value)
5. [Examples](#examples)

---

## General Information

The **AREG SDK** leverages simple key-value pairs, called *Properties* for data persistence. For example, the **persistence module** parses these pairs from configuration files like [areg.init](./../../framework/areg/resources/areg.init), enabling AREG SDK to handle configuration data without the complexities of structured data formats. This design focuses on simplicity and efficiency for essential configurations.

---

## Syntax Basics

The parser in AREG Framework adheres to specific key-value syntax rules:

- **Format**: `<property key> = <property value>`
- **Comments**: Begin with `#` followed by space and are ignored.
- **End of Assignment**: An optional `;` at the end of the value.
- **Whitespace**: Spaces and tabs are ignored.
- **Key Separator**: Uses `::` to organize the key.
- **Value Delimiter**: Uses `|` for lists (similar to logical OR).

### Example
```text
log::myapp::scope::scope_one = DEBUG | SCOPE; # Ends with `;`
log::myapp::scope::scope_two = WARN | SCOPE # No `;`
```

- **Keys** (e.g., `log::myapp::scope::scope_one`) identify specific categories and configurations.
- **Values** (e.g., `DEBUG | SCOPE`) define properties and behaviors.

---

## Property Key

**Property Key** format: `section::(module|*)::property[::position]` with `::` separators for organization.

- **section** (required): Defines the configuration category.
- **module** (required): Specifies the executable it applies to, or `*` for all executables.
- **property** (required): Indicates the configuration setting.
- **position** (optional): Specifies sub-categories for further organization.

Each key must include *section*, *property*, and either a specific *module* or `*`. For example:
- `log::myapp::scope::scope_one` defines the `scope` property under `log` for the `myapp` executable, specifically for `scope_one`.

> [!NOTE]
> Using `*` in a key (e.g., `log::*::scope::*`) applies the setting globally across all executables and sub-categories.
---

## Property Value

Property values begin after the `=` symbol and extend until `;`, `#`, or line end. Use `|` to separate values in lists, for instance:

```text
location::*::files = ./config/ | ~/config/ | /usr/bin/; # Multiple file paths
```

In this case, the `location::*::files` key has a list of paths [`./config/`, `~/config/`, `/usr/bin/`].

---

## Examples

Sample key-value pairs within the AREG SDK that specifies the format of logs:
```text
log::*::layout::enter = %d: [ %t %x.%z: Enter --> ]%n
log::*::layout::message = %d: [ %t %p >>> ] %m%n
log::*::layout::exit = %d: [ %t %x.%z: Exit <-- ]%n
```

These configurations define logging layouts applicable to all executables (`*`), with values specifying various message formats.

---

For more details, refer to the **AREG SDK documentation** on [GitHub](https://github.com/aregtech/areg-sdk).
5 changes: 5 additions & 0 deletions framework/areg/persist/NEPersistence.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ namespace NEPersistence
**/
constexpr char SYNTAX_WHITESPACE_DELIMITER { ' ' };

/**
* \brief List delimiter in the value.
**/
constexpr char SYNTAX_VALUE_LIST_DELIMITER { '|' };

/**
* \brief Tab delimiter
**/
Expand Down
2 changes: 1 addition & 1 deletion framework/areg/persist/PropertyKey.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
* The Property Key is valid if at least section and property exist.
* The other parts are optional and can be omitted if not used.
* The Property Key format:
* section.(module|*).property[.position]
* section::(module|*)::property[::position]
**/
class AREG_API PropertyKey
{
Expand Down
18 changes: 17 additions & 1 deletion framework/areg/persist/PropertyValue.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ class AREG_API PropertyValue
/**
* \brief Parses the value and returns the list of identifiers.
* \param lookupList The lookup list to convert literal identifiers into integer values.
* \return Combined with logical OR operation digital value of the identifiers.
* \return Combined with logical OR ('|') operation digital value of the identifiers.
**/
TEArrayList<Identifier> getIdentifierList(const std::vector<Identifier>& lookupList) const;

Expand All @@ -264,6 +264,22 @@ class AREG_API PropertyValue
**/
void setIdentifierList(unsigned int idBits, const std::vector<Identifier>& lookupList);

/**
* \brief Parses the value and returns the list of string. The strings in the value should be
* separated by logical OR ('|') symbol to get the list. Otherwise, the returned array
* has one entry and that entry is equal to the value.
* \param makeUnique Flag, indicating whether the entries in the result should be unique or not.
* If the flag is 'false', the entries in the result list are not checked.
* \return Returns the list of strings.
**/
TEArrayList<String> getValueList(bool makeUnique = false) const;

/**
* \brief Sets a list of string as a value.
* \param list The list of strings to set as a value;
**/
void setValueList(const std::vector<String>& list);

/**
* \brief Parses given string, extracts Value data.
* \param value The string, which contains data for Value.
Expand Down
47 changes: 43 additions & 4 deletions framework/areg/persist/private/PropertyValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ unsigned int PropertyValue::getIndetifier( const std::vector<Identifier> & idLis
unsigned int result = Identifier::BAD_IDENTIFIER_VALUE;
if ( (idList.empty() == false) && (mValue.isEmpty() == false) )
{
std::vector<TEString<char>> list { mValue.split(NEPersistence::SYNTAX_LOGICAL_OR) };
std::vector<TEString<char>> list { mValue.split(NEPersistence::SYNTAX_VALUE_LIST_DELIMITER) };
for (auto& entry : list)
{
String value{ entry.trimAll() };
Expand Down Expand Up @@ -240,7 +240,7 @@ TEArrayList<Identifier> PropertyValue::getIdentifierList(const std::vector<Ident
TEArrayList<Identifier> result;
if ((lookupList.empty() == false) && (mValue.isEmpty() == false))
{
std::vector<TEString<char>> list{ mValue.split(NEPersistence::SYNTAX_LOGICAL_OR) };
std::vector<TEString<char>> list{ mValue.split(NEPersistence::SYNTAX_VALUE_LIST_DELIMITER) };
for (auto& entry : list)
{
String value{ entry.trimAll() };
Expand Down Expand Up @@ -269,7 +269,7 @@ void PropertyValue::setIdentifierList(unsigned int idBits, const std::vector<Ide
if (mValue.isEmpty() == false)
{
mValue.append(NEPersistence::SYNTAX_WHITESPACE_DELIMITER)
.append(NEPersistence::SYNTAX_LOGICAL_OR)
.append(NEPersistence::SYNTAX_VALUE_LIST_DELIMITER)
.append(NEPersistence::SYNTAX_WHITESPACE_DELIMITER);
}

Expand All @@ -286,14 +286,53 @@ void PropertyValue::setIndentifier(const std::vector<Identifier> & idList)
if ( mValue.isEmpty() == false )
{
mValue.append(NEPersistence::SYNTAX_WHITESPACE_DELIMITER)
.append(NEPersistence::SYNTAX_LOGICAL_OR)
.append(NEPersistence::SYNTAX_VALUE_LIST_DELIMITER)
.append(NEPersistence::SYNTAX_WHITESPACE_DELIMITER);
}

mValue += entry.getName();
}
}

TEArrayList<String> PropertyValue::getValueList(bool makeUnique /*= false*/) const
{
TEArrayList<String> result;
if (mValue.isEmpty() == false)
{
std::vector<TEString<char>> list{ mValue.split(NEPersistence::SYNTAX_VALUE_LIST_DELIMITER) };
for (auto& entry : list)
{
String value{ entry.trimAll() };
if (makeUnique)
{
result.addIfUnique(value);
}
else
{
result.add(value);
}
}
}

return result;
}

void PropertyValue::setValueList(const std::vector<String>& list)
{
mValue.clear();
for (const auto& entry : list)
{
if (mValue.isEmpty() == false)
{
mValue.append(NEPersistence::SYNTAX_WHITESPACE_DELIMITER)
.append(NEPersistence::SYNTAX_VALUE_LIST_DELIMITER)
.append(NEPersistence::SYNTAX_WHITESPACE_DELIMITER);
}

mValue += entry;
}
}

void PropertyValue::parseValue(const char * value)
{
mValue = value;
Expand Down

0 comments on commit 64d476c

Please sign in to comment.