Files
RobustToolbox/Robust.Shared/Configuration/ConfigurationCommands.cs
PJB3005 034728258c Add config rollback system
This is intended for content-side settings menus, so we can show users a "does this look correct" prompt after changing sensitive settings like graphics or UI, without risking an untimely config save *storing* broken CVar config.
2025-10-22 14:09:40 +02:00

280 lines
8.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Utility;
namespace Robust.Shared.Configuration
{
public static class CVarCommandUtil
{
/// <summary>
/// Parses a string into an object of the given type.
/// </summary>
/// <exception cref="FormatException">Thrown if the string could not be parsed into the given type.</exception>
/// <exception cref="NotSupportedException">Thrown if the type is not supported.</exception>
public static object ParseObject(Type type, string input)
{
if (type == typeof(bool))
{
if (bool.TryParse(input, out var val))
return val;
if (Parse.TryInt32(input, out var intVal))
{
if (intVal == 0) return false;
if (intVal == 1) return true;
}
throw new FormatException($"Could not parse bool value: {input}");
}
if (type == typeof(string))
{
return input;
}
if (type == typeof(int))
{
return Parse.Int32(input);
}
if (type == typeof(float))
{
return Parse.Float(input);
}
if (type == typeof(long))
{
return long.Parse(input);
}
if (type == typeof(ushort))
{
return ushort.Parse(input);
}
throw new NotSupportedException();
}
internal static IEnumerable<CompletionOption> GetCVarCompletionOptions(IConfigurationManager cfg)
{
return cfg.GetRegisteredCVars()
.Select(c => new CompletionOption(c, GetCVarValueHint(cfg, c)));
}
private static string GetCVarValueHint(IConfigurationManager cfg, string cVar)
{
var flags = cfg.GetCVarFlags(cVar);
if ((flags & CVar.CONFIDENTIAL) != 0)
return Loc.GetString("cmd-cvar-value-hidden");
var value = cfg.GetCVar<object>(cVar).ToString() ?? "";
if (value.Length > 50)
value = $"{value[..51]}…";
return value;
}
}
[SuppressMessage("ReSharper", "StringLiteralTypo")]
internal sealed class CVarCommand : LocalizedCommands
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
public override string Command => "cvar";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length is < 1 or > 2)
{
shell.WriteError(Loc.GetString("cmd-cvar-invalid-args"));
return;
}
var name = args[0];
if (name == "?")
{
var cvars = _cfg.GetRegisteredCVars().OrderBy(c => c);
shell.WriteLine(string.Join("\n", cvars));
return;
}
if (!_cfg.IsCVarRegistered(name))
{
shell.WriteError(Loc.GetString("cmd-cvar-not-registered", ("cvar", name)));
return;
}
if (args.Length == 1)
{
// Read CVar
var value = _cfg.GetCVar<object>(name);
shell.WriteLine(value.ToString()!);
}
else
{
// Write CVar
var value = args[1];
var type = _cfg.GetCVarType(name);
try
{
var parsed = CVarCommandUtil.ParseObject(type, value);
_cfg.SetCVar(name, parsed);
}
catch (FormatException)
{
shell.WriteError(Loc.GetString("cmd-cvar-parse-error", ("type", type)));
}
}
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
{
var helpQuestion = Loc.GetString("cmd-cvar-compl-list");
return CompletionResult.FromHintOptions(
CVarCommandUtil.GetCVarCompletionOptions(_cfg)
.Union(new[] { new CompletionOption("?", helpQuestion) })
.OrderBy(c => c.Value),
Loc.GetString("cmd-cvar-arg-name"));
}
var cvar = args[0];
if (!_cfg.IsCVarRegistered(cvar))
return CompletionResult.Empty;
var type = _cfg.GetCVarType(cvar);
return CompletionResult.FromHint($"<{type.Name}>");
}
}
internal sealed class CVarSubsCommand : LocalizedCommands
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
public override string Command => "cvar_subs";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 1)
{
shell.WriteError(Loc.GetString("cmd-cvar_subs-invalid-args"));
return;
}
var name = args[0];
var subs = ((ConfigurationManager)_cfg).GetSubs(name);
foreach (var @delegate in subs)
{
shell.WriteLine(ShowDelegateInfo(@delegate));
}
}
private static string ShowDelegateInfo(Delegate del)
{
return $"{del}: {del.Method} -> {del.Target}";
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
{
return CompletionResult.FromHintOptions(
_cfg.GetRegisteredCVars()
.Select(c => new CompletionOption(c))
.OrderBy(c => c.Value),
Loc.GetString("cmd-cvar_subs-arg-name"));
}
return CompletionResult.Empty;
}
}
internal sealed class ConfigMarkRollbackCommand : IConsoleCommand
{
[Dependency] private readonly IConfigurationManager _cfg = null!;
public string Command => "config_rollback_mark";
public string Description => "";
public string Help => "";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length is < 1 or > 2)
{
shell.WriteError(Loc.GetString("cmd-invalid-arg-number-error"));
return;
}
_cfg.MarkForRollback(args[0]);
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
{
return CompletionResult.FromOptions(
CVarCommandUtil.GetCVarCompletionOptions(_cfg)
.OrderBy(c => c.Value));
}
return CompletionResult.Empty;
}
}
internal sealed class ConfigUnmarkRollbackCommand : IConsoleCommand
{
[Dependency] private readonly IConfigurationManager _cfg = null!;
public string Command => "config_rollback_unmark";
public string Description => "";
public string Help => "";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length is < 1 or > 2)
{
shell.WriteError(Loc.GetString("cmd-invalid-arg-number-error"));
return;
}
_cfg.UnmarkForRollback(args[0]);
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
{
return CompletionResult.FromOptions(
CVarCommandUtil.GetCVarCompletionOptions(_cfg)
.OrderBy(c => c.Value));
}
return CompletionResult.Empty;
}
}
internal sealed class ConfigApplyRollbackCommand : IConsoleCommand
{
[Dependency] private readonly IConfigurationManager _cfg = null!;
public string Command => "config_rollback_apply";
public string Description => "";
public string Help => "";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
_cfg.ApplyRollback();
}
}
}