Custom configuration options

This commit is contained in:
Booklordofthedings 2025-05-08 13:52:49 +02:00
parent 50abe7a88b
commit 3a191cbd37
2 changed files with 197 additions and 0 deletions

168
src/Config.bf Normal file
View file

@ -0,0 +1,168 @@
namespace Common;
using System;
using System.Collections;
/*
Simple configuration syntax
"
key = value
#comment
key = multi
line
value
[Section]
key = value
"
this(String input)
ToString(String buffer)
SetValue(section, name, value)
GetValue(section, name) : StringValue
*/
class Config
{
private (StringView, StringView) _latest = ("", "");
private Dictionary<String, Dictionary<String, String>> _data = new .()
{
(new String(""), new .()) //Unsectioned data
};
public this(StringView content)
{
for (var line in content.Split('\n'))
_ParseLine(line);
}
public ~this()
{
for (var d in _data)
DeleteDictionaryAndKeysAndValues!(d.value);
DeleteDictionaryAndKeys!(_data);
}
public void Serialize(String buffer)
{
buffer.Clear();
for (var entry in _data[""])
{
if (!entry.value.Contains('\n'))
buffer.Append(scope $"{entry.key} = {entry.value}\n");
else
{
var parts = entry.value.Split('\n');
buffer.Append(scope $"{entry.key} = {parts.GetNext().Value}\n");
for (var part in parts)
buffer.Append(scope $" {part}\n");
}
}
for (var section in _data)
{
if (section.key == "")
continue;
buffer.Append(scope $"[{section.key}]\n");
for (var entry in section.value)
{
if (!entry.value.Contains('\n'))
buffer.Append(scope $"{entry.key} = {entry.value}\n");
else
{
var parts = entry.value.Split('\n');
buffer.Append(scope $"{entry.key} = {parts.GetNext().Value}\n");
for (var part in parts)
buffer.Append(scope $" {part}\n");
}
}
}
while (buffer.EndsWith('\n'))
buffer.RemoveFromEnd(1);
}
public Result<StringView> GetValue(StringView section, StringView name)
{
if (!_data.ContainsKeyAlt<StringView>(section))
return .Err;
var dict = _data[scope .(section)];
if (!dict.ContainsKeyAlt<StringView>(name))
return .Err;
return .Ok(dict[scope .(name)]);
}
public void SetValue(StringView section, StringView name, StringView value)
{
if (!_data.ContainsKeyAlt<StringView>(section))
_data.Add(new .(section), new .());
var dict = _data[scope .(section)];
if (!dict.ContainsKeyAlt<StringView>(name))
{
dict.Add(new .(name), new .(value));
return;
}
dict[scope .(name)].Set(value);
}
private void _ParseLine(StringView line)
{
var line;
if (line.IsEmpty || line.StartsWith('#') || (line[0] == '\t' && line.Length == 1))
return;
if (line.StartsWith('\t'))
{
if (_latest.0 == "" && _latest.1 == "")
return;
//This is safe under the assumption, that _latest did not get changed by external uses
_data.GetValue(scope .(_latest.0))
.Value.GetValue(scope .(_latest.1))
.Value.Append(scope $"\n{line.Substring(1)}");
return;
}
if (line.StartsWith('[') && !line.EndsWith(']'))
return;
else if (line.StartsWith('[') && line.EndsWith(']'))
{
if (line.Length - 2 < 0)
return;
var section = new String(line.Substring(1, line.Length - 2));
if (!_data.ContainsKeyAlt<StringView>(section))
_data.Add(section, new .());
_latest.0 = section;
_latest.1 = "";
}
if (!line.Contains(" = "))
return;
var parts = line.Split(" = ");
var name = parts.GetNext();
var text = new String(parts.Remnant);
if (_data.GetValue(scope .(_latest.0)) case .Ok(var val))
if (!val.ContainsKeyAlt<StringView>(name))
{
val.Add(new .(name), text);
_latest.1 = name;
}
}
}

29
src/Tests/Config.bf Normal file
View file

@ -0,0 +1,29 @@
namespace Common.Tests;
using System;
static
{
[Test(Name = "Common.Config.Default")]
public static void ParseConfig()
{
Config c = scope .("""
#this is a comment
#empty lines are fine
#even empty lines with tabs
name = value
name_1 = other value
multiline = this
can be done
like this
[Section]
stuff = inside of sections
""");
}
}