From a3d34b39f49b5d46263806ef5ba5ed64dfc60ead Mon Sep 17 00:00:00 2001 From: JSv4 Date: Tue, 10 Feb 2026 01:27:00 -0500 Subject: [PATCH] Add CLI flags for all WmlComparerSettings options Support --detail-threshold, --case-insensitive, --detect-moves, --simplify-move-markup, --move-similarity-threshold, --move-minimum-word-count, --no-detect-format-changes, --no-conflate-spaces, and --date-time flags. Rework argument parsing to partition into positional args and flags, maintaining backward compatibility with legacy 4-arg format. --- tools/redline/Program.cs | 152 ++++++++++++++++++++++++++++++--------- 1 file changed, 119 insertions(+), 33 deletions(-) diff --git a/tools/redline/Program.cs b/tools/redline/Program.cs index 363b661..a17c3fe 100644 --- a/tools/redline/Program.cs +++ b/tools/redline/Program.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Globalization; using System.IO; using Docxodus; @@ -9,7 +10,7 @@ namespace Redline; class Program { - const string Version = "1.0.0"; + const string Version = "1.1.0"; static int Main(string[] args) { @@ -25,41 +26,47 @@ static int Main(string[] args) return 0; } - if (args.Length < 3 || args.Length > 4) + // Partition args into positional and flags + var positional = new System.Collections.Generic.List(); + var flags = new System.Collections.Generic.List(); + + foreach (var arg in args) { - Console.Error.WriteLine("Error: Invalid number of arguments."); - Console.Error.WriteLine(); - PrintUsage(); - return 1; + if (arg.StartsWith("--")) + flags.Add(arg); + else + positional.Add(arg); } - // Parse arguments: redline [--author=] - // Or legacy format: redline + // Detect legacy 4-arg format: redline + // vs new format: redline [--flags...] string authorTag; string originalFilePath; string modifiedFilePath; string outputFilePath; - if (args.Length == 4 && !args[3].StartsWith("--")) + if (positional.Count == 4 && flags.Count == 0) { // Legacy format: redline - authorTag = args[0]; - originalFilePath = args[1]; - modifiedFilePath = args[2]; - outputFilePath = args[3]; + authorTag = positional[0]; + originalFilePath = positional[1]; + modifiedFilePath = positional[2]; + outputFilePath = positional[3]; } - else + else if (positional.Count == 3) { - // New format: redline [--author=] - originalFilePath = args[0]; - modifiedFilePath = args[1]; - outputFilePath = args[2]; + // New format: redline [--flags...] + originalFilePath = positional[0]; + modifiedFilePath = positional[1]; + outputFilePath = positional[2]; authorTag = "Redline"; - - if (args.Length == 4 && args[3].StartsWith("--author=")) - { - authorTag = args[3]["--author=".Length..]; - } + } + else + { + Console.Error.WriteLine("Error: Invalid number of arguments."); + Console.Error.WriteLine(); + PrintUsage(); + return 1; } if (!File.Exists(originalFilePath)) @@ -74,6 +81,80 @@ static int Main(string[] args) return 1; } + // Build settings from flags + var settings = new WmlComparerSettings + { + DetailThreshold = 0 + }; + + foreach (var flag in flags) + { + if (flag.StartsWith("--author=")) + { + authorTag = flag["--author=".Length..]; + } + else if (flag.StartsWith("--detail-threshold=")) + { + if (!double.TryParse(flag["--detail-threshold=".Length..], NumberStyles.Float, CultureInfo.InvariantCulture, out var val)) + { + Console.Error.WriteLine($"Error: Invalid value for --detail-threshold: {flag["--detail-threshold=".Length..]}"); + return 1; + } + settings.DetailThreshold = val; + } + else if (flag == "--case-insensitive") + { + settings.CaseInsensitive = true; + } + else if (flag == "--detect-moves") + { + settings.DetectMoves = true; + } + else if (flag == "--simplify-move-markup") + { + settings.SimplifyMoveMarkup = true; + } + else if (flag.StartsWith("--move-similarity-threshold=")) + { + if (!double.TryParse(flag["--move-similarity-threshold=".Length..], NumberStyles.Float, CultureInfo.InvariantCulture, out var val)) + { + Console.Error.WriteLine($"Error: Invalid value for --move-similarity-threshold: {flag["--move-similarity-threshold=".Length..]}"); + return 1; + } + settings.MoveSimilarityThreshold = val; + } + else if (flag.StartsWith("--move-minimum-word-count=")) + { + if (!int.TryParse(flag["--move-minimum-word-count=".Length..], out var val)) + { + Console.Error.WriteLine($"Error: Invalid value for --move-minimum-word-count: {flag["--move-minimum-word-count=".Length..]}"); + return 1; + } + settings.MoveMinimumWordCount = val; + } + else if (flag == "--no-detect-format-changes") + { + settings.DetectFormatChanges = false; + } + else if (flag == "--no-conflate-spaces") + { + settings.ConflateBreakingAndNonbreakingSpaces = false; + } + else if (flag.StartsWith("--date-time=")) + { + settings.DateTimeForRevisions = flag["--date-time=".Length..]; + } + else + { + Console.Error.WriteLine($"Error: Unknown flag: {flag}"); + Console.Error.WriteLine(); + PrintUsage(); + return 1; + } + } + + settings.AuthorForRevisions = authorTag; + try { var originalBytes = File.ReadAllBytes(originalFilePath); @@ -81,12 +162,6 @@ static int Main(string[] args) var originalDocument = new WmlDocument(originalFilePath, originalBytes); var modifiedDocument = new WmlDocument(modifiedFilePath, modifiedBytes); - var settings = new WmlComparerSettings - { - AuthorForRevisions = authorTag, - DetailThreshold = 0 - }; - Console.WriteLine($"Comparing documents..."); Console.WriteLine($" Original: {originalFilePath}"); Console.WriteLine($" Modified: {modifiedFilePath}"); @@ -120,7 +195,7 @@ static void PrintUsage() Console.WriteLine($"redline {Version} - Compare Word documents and generate redline diffs"); Console.WriteLine(); Console.WriteLine("Usage:"); - Console.WriteLine(" redline [--author=]"); + Console.WriteLine(" redline [options]"); Console.WriteLine(); Console.WriteLine("Arguments:"); Console.WriteLine(" original.docx Path to the original document"); @@ -128,13 +203,24 @@ static void PrintUsage() Console.WriteLine(" output.docx Path for the output redline document"); Console.WriteLine(); Console.WriteLine("Options:"); - Console.WriteLine(" --author= Author name for tracked changes (default: Redline)"); - Console.WriteLine(" -h, --help Show this help message"); - Console.WriteLine(" -v, --version Show version information"); + Console.WriteLine(" --author= Author name for tracked changes (default: Redline)"); + Console.WriteLine(" --detail-threshold=<0.0-1.0> Comparison granularity (lower = more detailed, default: 0)"); + Console.WriteLine(" --case-insensitive Ignore case differences"); + Console.WriteLine(" --detect-moves Enable move detection"); + Console.WriteLine(" --simplify-move-markup Convert moves to del/ins for Word compatibility"); + Console.WriteLine(" --move-similarity-threshold= Jaccard threshold for move matching (default: 0.8)"); + Console.WriteLine(" --move-minimum-word-count= Min words for move detection (default: 3)"); + Console.WriteLine(" --no-detect-format-changes Disable formatting change detection"); + Console.WriteLine(" --no-conflate-spaces Distinguish breaking/non-breaking spaces"); + Console.WriteLine(" --date-time= Custom timestamp for revisions"); + Console.WriteLine(" -h, --help Show this help message"); + Console.WriteLine(" -v, --version Show version information"); Console.WriteLine(); Console.WriteLine("Examples:"); Console.WriteLine(" redline contract-v1.docx contract-v2.docx redline.docx"); Console.WriteLine(" redline draft.docx final.docx changes.docx --author=\"Legal Review\""); + Console.WriteLine(" redline old.docx new.docx diff.docx --detect-moves --simplify-move-markup"); + Console.WriteLine(" redline old.docx new.docx diff.docx --detail-threshold=0.5 --case-insensitive"); Console.WriteLine(); Console.WriteLine("Environment Variables:"); Console.WriteLine(" REDLINE_DEBUG=1 Show detailed error information");