Files
StarterNG/StarterNG/Classes/ConfigFile.cs
maj00r 3b7a8135ea Strip all comments from the C# sources
Removes every // and /// comment from the 28 .cs files, -1331 lines.

Done with a lexer rather than a regex: the sources carry plenty of // inside
string literals (@"//\$decor\b", the //$o trainset directives, sb.Append("// "),
"https://wiki.eu07.pl/") and those must survive. It tracks regular, verbatim and
interpolated strings, interpolation holes and char literals. Every file was
checked by comparing the sequence of string-literal characters before and after
the strip; all 28 match. BOMs preserved, blank-line runs collapsed to one.

Claude-Session: https://claude.ai/code/session_01HamVNWE9mwmz1vs2iEpNQq
2026-08-29 16:42:11 +02:00

201 lines
5.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace StarterNG.Classes;
public sealed class ConfigFile
{
private readonly List<string> _lines = new();
private readonly Dictionary<string, int> _index =
new(StringComparer.OrdinalIgnoreCase);
public static ConfigFile Parse(string text)
{
var cfg = new ConfigFile();
text = text.Replace("\r\n", "\n").Replace('\r', '\n');
foreach (var line in text.Split('\n'))
cfg.AddLine(line);
return cfg;
}
private void AddLine(string raw)
{
int idx = _lines.Count;
_lines.Add(raw);
if (TryParseKey(raw, out string key))
_index[key] = idx;
}
public bool Has(string key) => _index.ContainsKey(key);
public string? GetRaw(string key)
{
if (!_index.TryGetValue(key, out int i))
return null;
SplitLine(_lines[i], out _, out _, out string value, out _);
return value;
}
public string GetString(string key, string fallback) =>
GetRaw(key) is { Length: > 0 } v ? v : fallback;
public bool GetBool(string key, bool fallback)
{
var v = FirstToken(GetRaw(key));
if (v is null) return fallback;
return v.ToLowerInvariant() switch
{
"yes" or "true" or "1" or "tak" => true,
"no" or "false" or "0" or "nie" => false,
_ => fallback
};
}
public int GetInt(string key, int fallback)
{
var v = FirstToken(GetRaw(key));
return v != null && int.TryParse(v, NumberStyles.Integer,
CultureInfo.InvariantCulture, out int n) ? n : fallback;
}
public double GetDouble(string key, double fallback)
{
var v = FirstToken(GetRaw(key));
return v != null && double.TryParse(v, NumberStyles.Float,
CultureInfo.InvariantCulture, out double d) ? d : fallback;
}
public string[] GetTokens(string key)
{
var v = GetRaw(key);
return string.IsNullOrEmpty(v)
? Array.Empty<string>()
: v.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
}
public void Set(string key, string value)
{
value ??= string.Empty;
if (_index.TryGetValue(key, out int i))
{
SplitLine(_lines[i], out string indent, out _, out _, out string comment);
var sb = new StringBuilder();
sb.Append(indent).Append(key);
if (value.Length > 0)
sb.Append(' ').Append(value);
if (comment.Length > 0)
sb.Append('\t').Append(comment);
_lines[i] = sb.ToString();
}
else
{
int idx = _lines.Count;
_lines.Add(value.Length > 0 ? $"{key} {value}" : key);
_index[key] = idx;
}
}
public void Remove(string key)
{
if (!_index.TryGetValue(key, out int i))
return;
SplitLine(_lines[i], out string indent, out _, out string value, out string comment);
var sb = new StringBuilder();
sb.Append(indent).Append("// ").Append(key);
if (value.Length > 0)
sb.Append(' ').Append(value);
if (comment.Length > 0)
sb.Append('\t').Append(comment);
_lines[i] = sb.ToString();
_index.Remove(key);
}
public void SetBool(string key, bool value) => Set(key, value ? "yes" : "no");
public void SetInt(string key, int value) =>
Set(key, value.ToString(CultureInfo.InvariantCulture));
public void SetDouble(string key, double value) =>
Set(key, value.ToString("0.###", CultureInfo.InvariantCulture));
public string ToText() => string.Join(Environment.NewLine, _lines);
private static bool TryParseKey(string raw, out string key)
{
SplitLine(raw, out _, out key, out _, out _);
return key.Length > 0;
}
private static void SplitLine(string raw, out string indent, out string key,
out string value, out string comment)
{
indent = key = value = comment = string.Empty;
int start = 0;
while (start < raw.Length && char.IsWhiteSpace(raw[start]))
start++;
indent = raw.Substring(0, start);
string body = raw.Substring(start);
if (body.StartsWith("//"))
{
comment = body;
return;
}
if (body.Length == 0)
return;
int commentAt = FindCommentStart(body);
string content = commentAt >= 0 ? body.Substring(0, commentAt) : body;
if (commentAt >= 0)
comment = body.Substring(commentAt).TrimEnd();
content = content.TrimEnd();
if (content.Length == 0)
return;
int sp = IndexOfWhitespace(content);
if (sp < 0)
{
key = content;
}
else
{
key = content.Substring(0, sp);
value = content.Substring(sp).Trim();
}
}
private static int FindCommentStart(string body)
{
for (int i = 0; i + 1 < body.Length; i++)
{
if (body[i] == '/' && body[i + 1] == '/' &&
(i == 0 || char.IsWhiteSpace(body[i - 1])))
return i;
}
return -1;
}
private static int IndexOfWhitespace(string s)
{
for (int i = 0; i < s.Length; i++)
if (char.IsWhiteSpace(s[i]))
return i;
return -1;
}
private static string? FirstToken(string? value)
{
if (string.IsNullOrEmpty(value))
return null;
int sp = IndexOfWhitespace(value);
return sp < 0 ? value : value.Substring(0, sp);
}
}