From e4c0c885139c854860df1947a1f21c092d867742 Mon Sep 17 00:00:00 2001 From: BRUNER Patrick Date: Thu, 28 May 2026 16:48:48 +0200 Subject: [PATCH 1/2] Replace custom CLI parser with System.CommandLine 2.0.8 The hand-rolled CmdLine parser under Classes/CommandLine/ (~460 LOC) has a //TODO from years ago suggesting replacement. Swap it for System.CommandLine 2.0.8 (stable since 2026-05-12). Surface preserved: - --config / -c (new canonical forms) - -config accepted as a hidden alias for back-compat with existing shortcuts/scripts - Positional args still accept log files and session files (.lxj) Also fixes a latent bug: the original code checked configFile.Exists before cmdLine.Parse(args) was called, so the config-file-import block was effectively dead. The System.CommandLine port parses first, so -config FILE actually imports now. Also fixes an edge case in FileOperationService.AddMultiFileTab where .lxj session files passed alongside log files in MultiFile mode were treated as raw log lines. Sessions are now split out and dispatched to the session loader before the multi-file tab is created. --- src/Directory.Packages.props | 1 + .../FileOperationService.cs | 13 ++ src/LogExpert/Classes/CommandLine/CmdLine.cs | 199 ------------------ .../Classes/CommandLine/CmdLineException.cs | 34 --- .../Classes/CommandLine/CmdLineInt.cs | 102 --------- .../Classes/CommandLine/CmdLineParameter.cs | 65 ------ .../Classes/CommandLine/ConsoleCmdLine.cs | 60 ------ src/LogExpert/LogExpert.csproj | 1 + src/LogExpert/Program.cs | 49 +++-- 9 files changed, 52 insertions(+), 472 deletions(-) delete mode 100644 src/LogExpert/Classes/CommandLine/CmdLine.cs delete mode 100644 src/LogExpert/Classes/CommandLine/CmdLineException.cs delete mode 100644 src/LogExpert/Classes/CommandLine/CmdLineInt.cs delete mode 100644 src/LogExpert/Classes/CommandLine/CmdLineParameter.cs delete mode 100644 src/LogExpert/Classes/CommandLine/ConsoleCmdLine.cs diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 1fb4dceb..3c79dd57 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -23,6 +23,7 @@ + diff --git a/src/LogExpert.UI/Services/FileOperationService/FileOperationService.cs b/src/LogExpert.UI/Services/FileOperationService/FileOperationService.cs index 1e4bae80..56a6a3f6 100644 --- a/src/LogExpert.UI/Services/FileOperationService/FileOperationService.cs +++ b/src/LogExpert.UI/Services/FileOperationService/FileOperationService.cs @@ -182,6 +182,19 @@ public LogWindow AddTempFileTab (string fileName, string title) return null; } + // Session files (.lxj) aren't logs — dispatch them to the project loader and + // continue with only the log files. Without this, the .lxj would be treated as + // a raw log line in the multi-file tab. + if (fileNames.Any(f => f.EndsWith(".lxj", StringComparison.OrdinalIgnoreCase))) + { + foreach (var sessionFile in fileNames.Where(f => f.EndsWith(".lxj", StringComparison.OrdinalIgnoreCase))) + { + _projectFileCallback(sessionFile, false); + } + var logs = fileNames.Where(f => !f.EndsWith(".lxj", StringComparison.OrdinalIgnoreCase)).ToArray(); + return logs.Length > 0 ? AddMultiFileTab(logs) : null; + } + EncodingOptions encodingOptions = new(); FillDefaultEncodingFromSettings(encodingOptions); diff --git a/src/LogExpert/Classes/CommandLine/CmdLine.cs b/src/LogExpert/Classes/CommandLine/CmdLine.cs deleted file mode 100644 index d5358431..00000000 --- a/src/LogExpert/Classes/CommandLine/CmdLine.cs +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Taken from https://cmdline.codeplex.com/ - * - */ - -//TODO: Replace with https://github.com/commandlineparser/commandline -//TODO: or with this https://github.com/natemcmaster/CommandLineUtils -namespace LogExpert.Classes.CommandLine; - -/// -/// Represents an string command line parameter. -/// -public class CmdLineString (string name, bool required, string helpMessage) : CmdLineParameter(name, required, helpMessage) -{ - - #region Public methods - - public static implicit operator string (CmdLineString s) - { - ArgumentNullException.ThrowIfNull(s, nameof(s)); - return s.Value; - } - - public override string ToString () - { - return Value; - } - - #endregion -} - -/// -/// Provides a simple strongly typed interface to work with command line parameters. -/// -public class CmdLine -{ - #region Fields - - // A private dictonary containing the parameters. - private readonly Dictionary parameters = []; - - #endregion - - #region cTor - - /// - /// Creats a new empty command line object. - /// - public CmdLine() - { - } - - #endregion - - #region Properties - - /// - /// Returns a command line parameter by the name. - /// - /// The name of the parameter (the word after the initial hyphen (-). - /// A reference to the named comman line object. - public CmdLineParameter this[string name] - { - get - { - if (parameters.TryGetValue(name, out var value) == false) - { - throw new CmdLineException(name, "Not a registered parameter."); - } - - return value; - } - } - - #endregion - - #region Public methods - - /// - /// Registers a parameter to be used and adds it to the help screen. - /// - /// The parameter to add. - public void RegisterParameter(CmdLineParameter parameter) - { - if (parameters.ContainsKey(parameter.Name)) - { - throw new CmdLineException(parameter.Name, "Parameter is already registered."); - } - - parameters.Add(parameter.Name, parameter); - } - - /// - /// Registers parameters to be used and adds hem to the help screen. - /// - /// The parameter to add. - public void RegisterParameter(CmdLineParameter[] parameters) - { - foreach (var p in parameters) - { - RegisterParameter(p); - } - } - - - /// - /// Parses the command line and sets the value of each registered parmaters. - /// - /// The arguments array sent to main() - /// Any reminding strings after arguments has been processed. - public string[] Parse(string[] args) - { - var i = 0; - - List new_args = []; - - while (i < args.Length) - { - if (args[i].Length > 1 && args[i][0] == '-') - { - // The current string is a parameter name - var key = args[i][1..].ToLower(); - var argsValue = string.Empty; - i++; - if (i < args.Length) - { - if (args[i].Length > 0 && args[i][0] == '-') - { - // The next string is a new parameter, do not nothing - } - else - { - // The next string is a value, read the value and move forward - argsValue = args[i]; - i++; - } - } - - if (parameters.TryGetValue(key, out var cmdLineParameter) == false) - { - throw new CmdLineException(key, "Parameter is not allowed."); - } - - if (cmdLineParameter.Exists) - { - throw new CmdLineException(key, "Parameter is specified more than once."); - } - - cmdLineParameter.SetValue(argsValue); - } - else - { - new_args.Add(args[i]); - i++; - } - } - - - // Check that required parameters are present in the command line. - foreach (var key in parameters.Keys) - { - if (parameters[key].Required && parameters[key].Exists == false) - { - throw new CmdLineException(key, "Required parameter is not found."); - } - } - - return new_args.ToArray(); - } - - /// - /// Generates the help screen. - /// - public string HelpScreen() - { - var len = 0; - foreach (var key in parameters.Keys) - { - len = Math.Max(len, key.Length); - } - - var help = "\nParameters:\n\n"; - foreach (var key in parameters.Keys) - { - var s = "-" + parameters[key].Name; - while (s.Length < len + 3) - { - s += " "; - } - - s += parameters[key].Help + "\n"; - help += s; - } - - return help; - } - - #endregion -} \ No newline at end of file diff --git a/src/LogExpert/Classes/CommandLine/CmdLineException.cs b/src/LogExpert/Classes/CommandLine/CmdLineException.cs deleted file mode 100644 index 01e67c7d..00000000 --- a/src/LogExpert/Classes/CommandLine/CmdLineException.cs +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Taken from https://cmdline.codeplex.com/ - * - */ - -//TODO: Replace with https://github.com/commandlineparser/commandline -//TODO: or with this https://github.com/natemcmaster/CommandLineUtils -namespace LogExpert.Classes.CommandLine; - -/// -/// Represents an error occuring during command line parsing. -/// -public class CmdLineException : Exception -{ - #region cTor - - public CmdLineException (string parameter, string message) : base($"Syntax error of parameter -{parameter}: {message}") - { - } - - public CmdLineException (string message) : base(message) - { - } - - public CmdLineException () - { - } - - public CmdLineException (string message, Exception innerException) : base(message, innerException) - { - } - - #endregion -} \ No newline at end of file diff --git a/src/LogExpert/Classes/CommandLine/CmdLineInt.cs b/src/LogExpert/Classes/CommandLine/CmdLineInt.cs deleted file mode 100644 index 897a3f3b..00000000 --- a/src/LogExpert/Classes/CommandLine/CmdLineInt.cs +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Taken from https://cmdline.codeplex.com/ - * - */ - -//TODO: or with this https://github.com/natemcmaster/CommandLineUtils -namespace LogExpert.Classes.CommandLine; - -/// -/// Represents an integer command line parameter. -/// -public class CmdLineInt : CmdLineParameter -{ - #region Fields - - private readonly int _max; - private readonly int _min; - - #endregion - - #region cTor - - /// - /// Creates a new instance of this class. - /// - /// Name of parameter. - /// Require that the parameter is present in the command line. - /// The explanation of the parameter to add to the help screen. - public CmdLineInt (string name, bool required, string helpMessage) - : base(name, required, helpMessage) - { - _max = int.MaxValue; - _min = int.MinValue; - } - - /// - /// Creates a new instance of this class. - /// - /// Name of parameter. - /// Require that the parameter is present in the command line. - /// The explanation of the parameter to add to the help screen. - /// The minimum value of the parameter. - /// The maximum valie of the parameter. - public CmdLineInt (string name, bool required, string helpMessage, int min, int max) - : base(name, required, helpMessage) - { - _max = min; - _max = max; - } - - #endregion - - #region Properties - - /// - /// Returns the current value of the parameter. - /// - public new int Value { get; private set; } - - #endregion - - #region Public methods - - /// - /// Sets the value of the parameter. - /// - /// A string containing a integer expression. - public override void SetValue (string value) - { - base.SetValue(value); - int i; - try - { - i = Convert.ToInt32(value); - } - catch (Exception) - { - throw new CmdLineException(Name, "Value is not an integer."); - } - - if (i < _min) - { - throw new CmdLineException(Name, $"Value must be greather or equal to {_min}."); - } - - if (i > _max) - { - throw new CmdLineException(Name, $"Value must be less or equal to {_max}."); - } - - Value = i; - } - - /// - /// A implicit converion to a int data type. - /// - /// - /// - public static implicit operator int (CmdLineInt s) => s.Value; - - #endregion -} \ No newline at end of file diff --git a/src/LogExpert/Classes/CommandLine/CmdLineParameter.cs b/src/LogExpert/Classes/CommandLine/CmdLineParameter.cs deleted file mode 100644 index 5143ead3..00000000 --- a/src/LogExpert/Classes/CommandLine/CmdLineParameter.cs +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Taken from https://cmdline.codeplex.com/ - * - */ - -//TODO: Replace with https://github.com/commandlineparser/commandline -//TODO: or with this https://github.com/natemcmaster/CommandLineUtils -namespace LogExpert.Classes.CommandLine; - -/// -/// Represents a command line parameter. -/// Parameters are words in the command line beginning with a hyphen (-). -/// The value of the parameter is the next word in -/// -/// -/// Creates a new instance of this class. -/// -/// Name of parameter. -/// Require that the parameter is present in the command line. -/// The explanation of the parameter to add to the help screen. -public class CmdLineParameter(string name, bool required, string helpMessage) -{ - #region Properties - - /// - /// Returns the value of the parameter. - /// - public string Value { get; private set; } = ""; - - /// - /// Returns the help message associated with the parameter. - /// - public string Help { get; } = helpMessage; - - /// - /// Returns true if the parameter was found in the command line. - /// - public bool Exists { get; private set; } - - /// - /// Returns true if the parameter is required in the command line. - /// - public bool Required { get; } = required; - - /// - /// Returns the name of the parameter. - /// - public string Name { get; } = name; - - #endregion - - #region Public methods - - /// - /// Sets the value of the parameter. - /// - /// A string containing a integer expression. - public virtual void SetValue(string value) - { - Value = value; - Exists = true; - } - - #endregion -} \ No newline at end of file diff --git a/src/LogExpert/Classes/CommandLine/ConsoleCmdLine.cs b/src/LogExpert/Classes/CommandLine/ConsoleCmdLine.cs deleted file mode 100644 index 408831e5..00000000 --- a/src/LogExpert/Classes/CommandLine/ConsoleCmdLine.cs +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Taken from https://cmdline.codeplex.com/ - * - */ - -//TODO: Replace with https://github.com/commandlineparser/commandline -//TODO: or with this https://github.com/natemcmaster/CommandLineUtils -namespace LogExpert.Classes.CommandLine; - -/// -/// Represents a CmdLine object to use with console applications. -/// The -help parameter will be registered automatically. -/// Any errors will be written to the console instead of generating exceptions. -/// -public class ConsoleCmdLine : CmdLine -{ - #region cTor - - public ConsoleCmdLine() - { - RegisterParameter(new CmdLineString("help", false, "Prints the help screen.")); - } - - #endregion - - #region Public methods - - public new string[] Parse(string[] args) - { - string[] ret = []; - - var error = string.Empty; - - try - { - ret = base.Parse(args); - } - catch (CmdLineException ex) - { - error = ex.Message; - } - - if (this["help"].Exists) - { - Console.WriteLine(HelpScreen()); - Environment.Exit(0); - } - - if (error != string.Empty) - { - Console.WriteLine(error); - Console.WriteLine("Use -help for more information."); - Environment.Exit(1); - } - - return ret; - } - - #endregion -} \ No newline at end of file diff --git a/src/LogExpert/LogExpert.csproj b/src/LogExpert/LogExpert.csproj index 0ad88329..17427545 100644 --- a/src/LogExpert/LogExpert.csproj +++ b/src/LogExpert/LogExpert.csproj @@ -30,6 +30,7 @@ + diff --git a/src/LogExpert/Program.cs b/src/LogExpert/Program.cs index c9c6ad52..2122957f 100644 --- a/src/LogExpert/Program.cs +++ b/src/LogExpert/Program.cs @@ -1,3 +1,4 @@ +using System.CommandLine; using System.Diagnostics; using System.Globalization; using System.IO.Pipes; @@ -9,7 +10,6 @@ using System.Windows.Forms; using LogExpert.Classes; -using LogExpert.Classes.CommandLine; using LogExpert.Configuration; using LogExpert.Core.Classes.IPC; using LogExpert.Core.Config; @@ -67,18 +67,43 @@ private static void Main (string[] args) CancellationTokenSource cts = new(); try { - CmdLineString configFile = new("config", false, "A configuration (settings) file"); - CmdLine cmdLine = new(); - cmdLine.RegisterParameter(configFile); - if (configFile.Exists) + Option configOption = new("--config", "-c") { - FileInfo cfgFileInfo = new(configFile.Value); - //TODO: The config file import and the try catch for the primary instance and secondary instance should be separated functions - if (cfgFileInfo.Exists) + Description = "A configuration (settings) file" + }; + Option legacyConfigOption = new("-config") + { + Hidden = true + }; + Argument filesArgument = new("files") + { + Description = "Log files (.log etc.) or session files (.lxj) to open" + }; + RootCommand rootCommand = new("LogExpert — log file viewer.") + { + configOption, + legacyConfigOption, + filesArgument + }; + + ParseResult parseResult = rootCommand.Parse(args); + + if (parseResult.Errors.Count > 0) + { + string errorText = string.Join(Environment.NewLine, parseResult.Errors.Select(e => e.Message)); + _logger.Error(CultureInfo.InvariantCulture, $"Command-line error: {errorText}"); + _ = MessageBox.Show(errorText, Resources.LogExpert_Common_UI_Title_LogExpert, MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + FileInfo? configFile = parseResult.GetValue(configOption) ?? parseResult.GetValue(legacyConfigOption); + + if (configFile is not null) + { + if (configFile.Exists) { - ImportResult importResult = ConfigManager.Instance.Import(cfgFileInfo, ExportImportFlags.All); + ImportResult importResult = ConfigManager.Instance.Import(configFile, ExportImportFlags.All); - // Handle import result if (!importResult.Success) { string message = importResult.RequiresUserConfirmation @@ -114,8 +139,8 @@ private static void Main (string[] args) try { Mutex mutex = new(false, "Local\\LogExpertInstanceMutex" + pId, out var isCreated); - var remainingArgs = cmdLine.Parse(args); - var absoluteFilePaths = GenerateAbsoluteFilePaths(remainingArgs); + string[] positionalFiles = parseResult.GetValue(filesArgument) ?? []; + var absoluteFilePaths = GenerateAbsoluteFilePaths(positionalFiles); if (isCreated) { From bd62c3ba28aa8c99e6d4aba2a6f0c6ae43c3509a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 28 May 2026 14:55:21 +0000 Subject: [PATCH 2/2] chore: update plugin hashes [skip ci] --- .../PluginHashGenerator.Generated.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/PluginRegistry/PluginHashGenerator.Generated.cs b/src/PluginRegistry/PluginHashGenerator.Generated.cs index e09a2549..1a47eae3 100644 --- a/src/PluginRegistry/PluginHashGenerator.Generated.cs +++ b/src/PluginRegistry/PluginHashGenerator.Generated.cs @@ -10,7 +10,7 @@ public static partial class PluginValidator { /// /// Gets pre-calculated SHA256 hashes for built-in plugins. - /// Generated: 2026-05-28 08:22:43 UTC + /// Generated: 2026-05-28 14:55:19 UTC /// Configuration: Release /// Plugin count: 21 /// @@ -18,27 +18,27 @@ public static Dictionary GetBuiltInPluginHashes() { return new Dictionary(StringComparer.OrdinalIgnoreCase) { - ["AutoColumnizer.dll"] = "6E377F0AE721301F525FCEA7CD9B7FA30DB329BDB17E96F334EE6FBF1251502B", + ["AutoColumnizer.dll"] = "3EA41A42F0A17C1DDAE3807E899B8F0E4B6A3CFCD0412FB04370C29F218A34C9", ["BouncyCastle.Cryptography.dll"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", ["BouncyCastle.Cryptography.dll (x86)"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", - ["CsvColumnizer.dll"] = "CE335CCA69ACAA72F71B7AD63BF09CAAC3C9492A807B4DDD8053C062AB6433EA", - ["CsvColumnizer.dll (x86)"] = "CE335CCA69ACAA72F71B7AD63BF09CAAC3C9492A807B4DDD8053C062AB6433EA", - ["DefaultPlugins.dll"] = "9F0DDA2C2064BA5FBD0A68DF31EA29A7D30C2462A5742F27908C093CEF2AA7F9", - ["FlashIconHighlighter.dll"] = "83D60C9E885639193A9F609EB94BB735709D95B40A0CB904998AACA51D0D3016", - ["GlassfishColumnizer.dll"] = "2E8601C7A6BBCF2B76EC50850B9F03503FBF336F8332FF3365757B298FFEC94D", - ["JsonColumnizer.dll"] = "6596ED4BEAB67DABD89E8C4D8AE028A67B029594B706CAD0F652BF774EAD212A", - ["JsonCompactColumnizer.dll"] = "12553DC6DE8AE0F688707473608D2D474554C3EBB0FAE96E76A7D37FDEB3CEAE", - ["Log4jXmlColumnizer.dll"] = "B7AB67C37465E50C7FA6F640C7C410664F087C0411D5217B88BEEF91766A45D2", - ["LogExpert.Resources.dll"] = "8D2E41CADDFB81E03E6F2B2D48059CFABF7DB69BE3E33D6242115E5040BF6356", + ["CsvColumnizer.dll"] = "31343A536E79669DBC292CBFC728CB63F77EA661C4E9F36FE4E3025166BA444B", + ["CsvColumnizer.dll (x86)"] = "31343A536E79669DBC292CBFC728CB63F77EA661C4E9F36FE4E3025166BA444B", + ["DefaultPlugins.dll"] = "3820088CC0BFB6584BB48BA3F45710D2DD3076163F82FDC41400BF467611444C", + ["FlashIconHighlighter.dll"] = "07518957DFE748B01D86870CFC3AE05AE8DE2EBE602C268997585A2F6AE3D505", + ["GlassfishColumnizer.dll"] = "77DC1A20B32491055DDE72F159894AE6889218BA2D1F2AF709D3388E83371AF5", + ["JsonColumnizer.dll"] = "846B45CFD8B3E24E12B065AC0D0F0252BFCDEDA8CA8E4E06A67B2200662CB5AE", + ["JsonCompactColumnizer.dll"] = "0E9BC4CEF045D3C2C8BDACA96B40A469EF65BB80326332A0F8325769855D65D8", + ["Log4jXmlColumnizer.dll"] = "665546EBF0D55758214034CBD026D54B6B018FF90D240076659E674B2350E7CD", + ["LogExpert.Resources.dll"] = "202FE9534087C11461DFF3A8653410181EF481C8A3C40AB96868389189B49DCE", ["Microsoft.Extensions.DependencyInjection.Abstractions.dll"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93", ["Microsoft.Extensions.DependencyInjection.Abstractions.dll (x86)"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93", ["Microsoft.Extensions.Logging.Abstractions.dll"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D", ["Microsoft.Extensions.Logging.Abstractions.dll (x86)"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D", - ["RegexColumnizer.dll"] = "AF3A7237BA515877D9A0D0C6814608B653790892ACBFC844BC031DDF8FD91D25", - ["SftpFileSystem.dll"] = "0B3DA99546B5319BA2BE407BC8D7ED65B4AF9E9E133C33B6F6DC315E15E9B8FB", - ["SftpFileSystem.dll (x86)"] = "28B9378D8E6D1EFD6257B61B1281A2870A94091E942DFC50DD373A4E684C47D7", - ["SftpFileSystem.Resources.dll"] = "DFD0ED9BB470D3EBC739E36386D222D51A6772FFEB7AEE7305E4A65789B5A98B", - ["SftpFileSystem.Resources.dll (x86)"] = "DFD0ED9BB470D3EBC739E36386D222D51A6772FFEB7AEE7305E4A65789B5A98B", + ["RegexColumnizer.dll"] = "4461F126C06332E732970E2D90DD8289C2F004B5B4BFF26AAC1EE6132717FEEA", + ["SftpFileSystem.dll"] = "F77A0C6C717E5BDB3CDB03DBE68DF1E39413E004893B4DDD2BEE5E59B7DB140B", + ["SftpFileSystem.dll (x86)"] = "6DD7950C31A3BDEE31B283BE78C17FEEDE8D2CDA13869A98FBEEA2F6C9BFFC16", + ["SftpFileSystem.Resources.dll"] = "4ED850E091351011208213BA1D8DDC2371D115316D87A7410B0C1AC901492BE8", + ["SftpFileSystem.Resources.dll (x86)"] = "4ED850E091351011208213BA1D8DDC2371D115316D87A7410B0C1AC901492BE8", }; }