review(Driver.TwinCAT.Cli): clean parse errors + FlushLogging() in finally

Re-review at 7286d320. -008 (Low): ParseValue maps FormatException/OverflowException to a
clean CommandException (was raw stack trace) + tests. -009: FlushLogging() in all 5 commands'
finally blocks (parity with AbCip.Cli).
This commit is contained in:
Joseph Doherty
2026-06-19 12:08:45 -04:00
parent f8bf067243
commit 7580e37807
7 changed files with 154 additions and 26 deletions
@@ -62,6 +62,7 @@ public sealed class BrowseCommand : TwinCATCommandBase
finally
{
await driver.ShutdownAsync(CancellationToken.None);
FlushLogging();
}
var matched = FilterByPrefix(builder.Variables, Prefix);
@@ -59,6 +59,7 @@ public sealed class ProbeCommand : TwinCATTagCommandBase
finally
{
await driver.ShutdownAsync(CancellationToken.None);
FlushLogging();
}
}
}
@@ -50,6 +50,7 @@ public sealed class ReadCommand : TwinCATTagCommandBase
finally
{
await driver.ShutdownAsync(CancellationToken.None);
FlushLogging();
}
}
@@ -108,6 +108,7 @@ public sealed class SubscribeCommand : TwinCATTagCommandBase
catch { /* teardown best-effort */ }
}
await driver.ShutdownAsync(CancellationToken.None);
FlushLogging();
}
}
@@ -63,33 +63,54 @@ public sealed class WriteCommand : TwinCATTagCommandBase
finally
{
await driver.ShutdownAsync(CancellationToken.None);
FlushLogging();
}
}
/// <summary>Parse <c>--value</c> per <see cref="TwinCATDataType"/>, invariant culture.</summary>
/// <summary>
/// Parse <c>--value</c> per <see cref="TwinCATDataType"/>, invariant culture. Wraps
/// <see cref="FormatException"/> and <see cref="OverflowException"/> in a
/// <see cref="CliFx.Exceptions.CommandException"/> so the operator sees a clean one-line
/// error instead of a raw stack trace (Driver.TwinCAT.Cli-008).
/// </summary>
/// <param name="raw">The raw string value to parse.</param>
/// <param name="type">The target TwinCAT data type.</param>
internal static object ParseValue(string raw, TwinCATDataType type) => type switch
internal static object ParseValue(string raw, TwinCATDataType type)
{
TwinCATDataType.Bool => ParseBool(raw),
TwinCATDataType.SInt => sbyte.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.USInt => byte.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.Int => short.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.UInt => ushort.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.DInt => int.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.UDInt => uint.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.LInt => long.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.ULInt => ulong.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.Real => float.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.LReal => double.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.String or TwinCATDataType.WString => raw,
// IEC 61131-3 time/date types are stored as UDINT on the wire — accept a numeric raw
// value + let the caller handle the encoding semantics.
TwinCATDataType.Time or TwinCATDataType.Date
or TwinCATDataType.DateTime or TwinCATDataType.TimeOfDay
=> uint.Parse(raw, CultureInfo.InvariantCulture),
_ => throw new CliFx.Exceptions.CommandException($"Unsupported DataType '{type}' for write."),
};
try
{
return type switch
{
TwinCATDataType.Bool => ParseBool(raw),
TwinCATDataType.SInt => sbyte.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.USInt => byte.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.Int => short.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.UInt => ushort.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.DInt => int.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.UDInt => uint.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.LInt => long.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.ULInt => ulong.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.Real => float.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.LReal => double.Parse(raw, CultureInfo.InvariantCulture),
TwinCATDataType.String or TwinCATDataType.WString => raw,
// IEC 61131-3 time/date types are stored as UDINT on the wire — accept a numeric raw
// value + let the caller handle the encoding semantics.
TwinCATDataType.Time or TwinCATDataType.Date
or TwinCATDataType.DateTime or TwinCATDataType.TimeOfDay
=> uint.Parse(raw, CultureInfo.InvariantCulture),
_ => throw new CliFx.Exceptions.CommandException($"Unsupported DataType '{type}' for write."),
};
}
catch (CliFx.Exceptions.CommandException)
{
throw; // already a clean user-facing error (Bool parse, Structure, unsupported type)
}
catch (Exception ex) when (ex is FormatException or OverflowException)
{
throw new CliFx.Exceptions.CommandException(
$"Cannot parse '{raw}' as {type}: {ex.Message}");
}
}
private static bool ParseBool(string raw) => raw.Trim().ToLowerInvariant() switch
{