diff --git a/src/Config.bf b/src/Config.bf new file mode 100644 index 0000000..21b6004 --- /dev/null +++ b/src/Config.bf @@ -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> _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 GetValue(StringView section, StringView name) + { + if (!_data.ContainsKeyAlt(section)) + return .Err; + + var dict = _data[scope .(section)]; + + if (!dict.ContainsKeyAlt(name)) + return .Err; + + return .Ok(dict[scope .(name)]); + } + + public void SetValue(StringView section, StringView name, StringView value) + { + if (!_data.ContainsKeyAlt(section)) + _data.Add(new .(section), new .()); + + var dict = _data[scope .(section)]; + + if (!dict.ContainsKeyAlt(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(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(name)) + { + val.Add(new .(name), text); + _latest.1 = name; + } + } + + +} diff --git a/src/Tests/Config.bf b/src/Tests/Config.bf new file mode 100644 index 0000000..a3e0298 --- /dev/null +++ b/src/Tests/Config.bf @@ -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 + """); + } +}