AquaDX/AquaMai/AquaMai.Config/ConfigSerializer.cs

186 lines
7.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System;
using System.Reflection;
using System.Text;
using AquaMai.Config.Attributes;
using AquaMai.Config.Interfaces;
using Tomlet.Models;
namespace AquaMai.Config;
public class ConfigSerializer(IConfigSerializer.Options Options) : IConfigSerializer
{
private const string BANNER_ZH =
"""
AquaMai TOML
- #
- 便使 VSCode 使 #使 ##
- [OptionalCategory.Section]
-
- Disabled = true/
- =
-
-
-
-
使 MaiChartManager AquaMai
https://github.com/clansty/MaiChartManager
""";
private const string BANNER_EN =
"""
This is the TOML configuration file of AquaMai.
- Lines starting with a hash # are comments. Commented content will not take effect.
- For easily editing with editors (e.g. VSCode), commented configuration content uses a single hash #, while comment text uses two hashes ##.
- Lines with square brackets like [OptionalCategory.Section] are sections.
- Uncomment a section that is commented out by default (i.e. disabled by default) to enable it.
- To disable a section that is enabled by default, add a "Disable = true" entry under the section. Removing/commenting it will not work.
- Lines like "Key = Value" is a configuration entry.
- Configuration entries apply to the nearest section above them. Do not enable a configuration entry when its section is commented out (will be added to the previous section, which is invalid).
- Configuration entries take effect when the corresponding section is enabled, regardless of whether they are uncommented.
- Commented configuration entries retain their default values (shown in the comment), which may change with version updates.
- The format and text comments of this file are fixed. The configuration file will be rewritten at startup, and unrecognizable content will be deleted.
""";
private readonly IConfigSerializer.Options Options = Options;
public string Serialize(IConfig config)
{
StringBuilder sb = new();
if (Options.IncludeBanner)
{
var banner = Options.Lang == "zh" ? BANNER_ZH : BANNER_EN;
if (banner != null)
{
AppendComment(sb, banner.TrimEnd());
sb.AppendLine();
}
}
// Version
AppendEntry(sb, null, "Version", "2.0");
foreach (var section in ((Config)config).reflectionManager.SectionValues)
{
var sectionState = config.GetSectionState(section);
// If the state is default, print the example only. If the example is hidden, skip it.
if (sectionState.IsDefault && section.Attribute.ExampleHidden)
{
continue;
}
sb.AppendLine();
AppendComment(sb, section.Attribute.Comment);
if (// If the section is hidden and hidden by default, print it as commented.
sectionState.IsDefault && !sectionState.Enabled &&
// If the section is marked as always enabled, print it normally.
!section.Attribute.AlwaysEnabled)
{
sb.AppendLine($"#[{section.Path}]");
}
else // If the section is overridden, or is enabled by any means, print it normally.
{
sb.AppendLine($"[{section.Path}]");
}
if (!section.Attribute.AlwaysEnabled)
{
// If the section is disabled explicitly, print the "Disabled" meta entry.
if (!sectionState.IsDefault && !sectionState.Enabled)
{
AppendEntry(sb, null, "Disabled", value: true);
}
// If the section is enabled by default, print the "Disabled" meta entry as commented.
else if (sectionState.IsDefault && section.Attribute.DefaultOn)
{
AppendEntry(sb, null, "Disabled", value: false, commented: true);
}
// Otherwise, don't print the "Disabled" meta entry.
}
// Print entries.
foreach (var entry in section.entries)
{
var entryState = config.GetEntryState(entry);
AppendComment(sb, entry.Attribute.Comment);
if (((ConfigEntryAttribute)entry.Attribute).SpecialConfigEntry == SpecialConfigEntry.Locale && Options.OverrideLocaleValue)
{
AppendEntry(sb, section.Path, entry.Name, Options.Lang);
}
else
{
AppendEntry(sb, section.Path, entry.Name, entryState.Value, entryState.IsDefault);
}
}
}
return sb.ToString();
}
private static string SerializeTomlValue(string diagnosticPath, object value)
{
var type = value.GetType();
if (value is bool b)
{
return b ? "true" : "false";
}
else if (value is string str)
{
return new TomlString(str).SerializedValue;
}
else if (type.IsEnum)
{
return new TomlString(value.ToString()).SerializedValue;
}
else if (Utility.IsIntegerType(type))
{
return value.ToString();
}
else if (Utility.IsFloatType(type))
{
return new TomlDouble(Convert.ToDouble(value)).SerializedValue;
}
else
{
var currentMethod = MethodBase.GetCurrentMethod();
throw new NotImplementedException($"Unsupported config entry type: {type.FullName} ({diagnosticPath}). Please implement in {currentMethod.DeclaringType.FullName}.{currentMethod.Name}");
}
}
private void AppendComment(StringBuilder sb, IConfigComment comment)
{
if (comment != null)
{
AppendComment(sb, comment.GetLocalized(Options.Lang));
}
}
private static void AppendComment(StringBuilder sb, string comment)
{
comment = comment.Trim();
if (!string.IsNullOrEmpty(comment))
{
foreach (var line in comment.Split('\n'))
{
sb.AppendLine($"## {line}");
}
}
}
private static void AppendEntry(StringBuilder sb, string diagnosticsSection, string key, object value, bool commented = false)
{
if (commented)
{
sb.Append('#');
}
var diagnosticsPath = string.IsNullOrEmpty(diagnosticsSection)
? key
: $"{diagnosticsSection}.{key}";
sb.AppendLine($"{key} = {SerializeTomlValue(diagnosticsPath, value)}");
}
}