-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCommandLineParser.cs
More file actions
322 lines (284 loc) · 16.5 KB
/
CommandLineParser.cs
File metadata and controls
322 lines (284 loc) · 16.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.Parsing;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Configuration;
using CheckRelease.Interfaces;
namespace CheckRelease
{
/// <summary>
/// Handles parsing of command line arguments for the CheckRelease application.
/// </summary>
public class CommandLineParser
{
private readonly RootCommand _rootCommand;
private readonly IConfiguration _configuration;
private readonly IConsoleOutput _console;
/// <summary>
/// Initializes a new instance of the <see cref="CommandLineParser"/> class.
/// </summary>
/// <param name="configuration">The configuration to use for default values.</param>
[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
Justification = "AppSettings properties are preserved")]
private static void BindConfiguration(IConfiguration configuration, AppSettings settings)
{
configuration.Bind(settings);
}
public CommandLineParser(IConfiguration configuration, IConsoleOutput console)
{
_configuration = configuration;
_console = console;
_rootCommand = new RootCommand("C# port of check_release.sh - Analyzes commits between Git tags");
// Get default values from configuration
var appSettings = new AppSettings();
BindConfiguration(_configuration, appSettings);
// Add options
var htmlOption = new Option<bool>("--html", "Generate HTML output instead of plain text");
var settingsDiffOption = new Option<string>("--settings-diff", "Include a diff of appsettings.json between tags and specify the path to the settings file");
settingsDiffOption.SetDefaultValue(appSettings.SettingsPath);
var debugOption = new Option<bool>("--debug", "Enable debug mode with verbose output");
var traceOption = new Option<bool>("--trace", "Enable command tracing");
var spanOption = new Option<int>("--span", () => appSettings.SpanDays, $"Number of days to look back for tags");
var prefixOption = new Option<string>("--prefix", () => appSettings.Prefix, $"Prefix for JIRA tickets in commit messages");
var jiraBaseUrlOption = new Option<string>("--jira-base-url", () => appSettings.JiraBaseUrl, $"Base URL for JIRA tickets");
var fromCommonAncestorOption = new Option<bool>("--from-common-ancestor", "Compare to common ancestor with the specified tag(s) or commit(s)");
_rootCommand.AddOption(htmlOption);
_rootCommand.AddOption(settingsDiffOption);
_rootCommand.AddOption(debugOption);
_rootCommand.AddOption(traceOption);
_rootCommand.AddOption(spanOption);
_rootCommand.AddOption(prefixOption);
_rootCommand.AddOption(jiraBaseUrlOption);
_rootCommand.AddOption(fromCommonAncestorOption);
// Add argument for tags or commands
var tagsArgument = new Argument<string[]>("tags", "Tags or commands to use")
{
Arity = ArgumentArity.ZeroOrMore
};
_rootCommand.AddArgument(tagsArgument);
// Set the handler using a simpler approach since we hit the parameter limit
_rootCommand.SetHandler((context) =>
{
var htmlOutput = context.ParseResult.GetValueForOption(htmlOption);
var settingsDiffPath = context.ParseResult.GetValueForOption(settingsDiffOption);
var debugMode = context.ParseResult.GetValueForOption(debugOption);
var traceMode = context.ParseResult.GetValueForOption(traceOption);
var spanDays = context.ParseResult.GetValueForOption(spanOption);
var prefix = context.ParseResult.GetValueForOption(prefixOption);
var jiraBaseUrl = context.ParseResult.GetValueForOption(jiraBaseUrlOption);
var fromCommonAncestor = context.ParseResult.GetValueForOption(fromCommonAncestorOption);
var arguments = context.ParseResult.GetValueForArgument(tagsArgument);
var options = new CommandLineOptions
{
HtmlOutput = htmlOutput,
SettingsDiff = !string.IsNullOrEmpty(settingsDiffPath),
SettingsPath = settingsDiffPath ?? string.Empty,
DebugMode = debugMode,
TraceMode = traceMode,
SpanDays = spanDays,
Prefix = prefix ?? string.Empty,
JiraBaseUrl = jiraBaseUrl ?? string.Empty,
FromCommonAncestor = fromCommonAncestor,
Arguments = arguments?.ToList() ?? new List<string>()
};
ParseResult = options;
});
}
/// <summary>
/// Gets the parse result.
/// </summary>
public CommandLineOptions ParseResult { get; private set; } = new CommandLineOptions();
/// <summary>
/// Parses the command line arguments.
/// </summary>
/// <param name="args">The command line arguments.</param>
/// <returns>The parse result.</returns>
public CommandLineOptions Parse(string[] args)
{
_rootCommand.Invoke(args);
return ParseResult;
}
/// <summary>
/// Validates the command line options.
/// </summary>
/// <param name="options">The command line options.</param>
/// <returns>True if the options are valid, false otherwise.</returns>
public bool Validate(CommandLineOptions options)
{
// Check if using --from-common-ancestor option
if (options.FromCommonAncestor)
{
// When using --from-common-ancestor, require 1-2 arguments
if (options.Arguments.Count == 0)
{
_console.WriteError("Error: --from-common-ancestor requires 1-2 arguments (tag or commit references).");
ShowUsage();
return false;
}
if (options.Arguments.Count > 2)
{
_console.WriteError("Error: --from-common-ancestor accepts at most 2 arguments.");
ShowUsage();
return false;
}
return true;
}
if (options.Arguments.Count == 0)
{
_console.WriteError("Error: No tags or commands specified.");
ShowUsage();
return false;
}
if (options.Arguments.Count == 1)
{
string arg = options.Arguments[0];
// Check if it's the "auto" or "stream" command
if (arg == "auto" || arg == "stream")
{
return true;
}
// Check if it's a valid type
if (GitTagSelector.ValidTypes.Contains(arg))
{
return true;
}
// Check if it's a tag that contains a valid type
if (GitTagSelector.ValidTypes.Any(type => arg.Contains(type)))
{
return true;
}
_console.WriteError($"Error: Invalid argument '{arg}'. Must be 'auto', a valid type ({string.Join(", ", GitTagSelector.ValidTypes)}), or a tag containing a valid type.");
ShowUsage();
return false;
}
if (options.Arguments.Count == 2)
{
// If first argument is "auto", second argument must be a valid type or not provided
if (options.Arguments[0] == "auto")
{
string type = options.Arguments[1];
if (!GitTagSelector.ValidTypes.Contains(type))
{
_console.WriteError($"Error: Invalid type '{type}'. Must be one of: {string.Join(", ", GitTagSelector.ValidTypes)}.");
ShowUsage();
return false;
}
}
return true;
}
if (options.Arguments.Count > 2)
{
_console.WriteError("Error: Too many arguments. Expected at most 2 arguments.");
ShowUsage();
return false;
}
return true;
}
/// <summary>
/// Shows the usage information.
/// </summary>
public void ShowUsage()
{
// Get default values from configuration
var appSettings = new AppSettings();
BindConfiguration(_configuration, appSettings);
_console.WriteLine("Usage:");
_console.WriteLine(" CheckRelease [--html] [--settings-diff=<path>] [--debug] <tag1> <tag2>");
_console.WriteLine(" - Use these two tags directly.");
_console.WriteLine(string.Empty);
_console.WriteLine(" CheckRelease [--html] [--settings-diff=<path>] [--debug] auto [type]");
_console.WriteLine(" - Automatically find the last two weeks tags of the specified type and use them.");
_console.WriteLine(" - If [type] is not provided, defaults to 'production'");
_console.WriteLine(string.Empty);
_console.WriteLine(" CheckRelease [--html] [--settings-diff=<path>] [--debug] stream [type]");
_console.WriteLine($" - Compare HEAD to a commit from --span days ago [default: {appSettings.SpanDays} days[]");
_console.WriteLine(" - If [type] is provided, only consider commits containing that type");
_console.WriteLine(string.Empty);
_console.WriteLine(" CheckRelease [--html] [--settings-diff=<path>] [--debug] <type>");
_console.WriteLine(" - Must be one of: production, uat, qa, dev");
_console.WriteLine(" - Shows all tags of that type from the last month in an interactive menu.");
_console.WriteLine(" - User must select exactly two.");
_console.WriteLine(string.Empty);
_console.WriteLine(" CheckRelease [--html] [--settings-diff=<path>] [--debug] <tag>");
_console.WriteLine(" - Tag must contain one of the valid types.");
_console.WriteLine(" - Finds all tags of the same type from the month prior to <tag>'s creation date.");
_console.WriteLine(" - Shows them in a menu, user picks exactly one to pair with <tag>.");
_console.WriteLine(string.Empty);
_console.WriteLine(" CheckRelease [--html] [--settings-diff=<path>] [--debug] --from-common-ancestor <tag-commit-or-env>");
_console.WriteLine(" - Compare HEAD to common ancestor with the specified tag, commit, or environment.");
_console.WriteLine(" - Environment names (production, uat, qa, dev) resolve to the most recent tag.");
_console.WriteLine(" - Analyzes all commits between HEAD and the nearest common ancestor.");
_console.WriteLine(string.Empty);
_console.WriteLine(" CheckRelease [--html] [--settings-diff=<path>] [--debug] --from-common-ancestor <ref1> <ref2>");
_console.WriteLine(" - Compare ref1 to common ancestor with ref2.");
_console.WriteLine(" - References can be tags, commits, or environment names.");
_console.WriteLine(" - Analyzes all commits between ref1 and the nearest common ancestor.");
_console.WriteLine(string.Empty);
_console.WriteLine("Options:");
_console.WriteLine(" --html Generate HTML output instead of plain text");
_console.WriteLine($" --settings-diff <path> Include a diff of appsettings.json between tags and specify the path [default: {appSettings.SettingsPath}]");
_console.WriteLine(" --debug Enable debug mode with verbose output");
_console.WriteLine(" --trace Enable trace mode");
_console.WriteLine($" --span <days> Number of days to look back for tags [default: {appSettings.SpanDays} days]");
_console.WriteLine($" --prefix <text> Prefix for JIRA tickets in commit messages [default: {appSettings.Prefix}]");
_console.WriteLine($" --jira-base-url <url> Base URL for JIRA tickets [default: {appSettings.JiraBaseUrl}]");
_console.WriteLine(" --from-common-ancestor <ref> Compare HEAD to common ancestor with the specified tag or commit");
_console.WriteLine(string.Empty);
_console.WriteLine("Examples:");
_console.WriteLine(" CheckRelease --html --settings-diff=\"project-dir/appsettings.json\" auto > releases.html");
_console.WriteLine(" CheckRelease --settings-diff=\"path/to/appsettings.json\" auto uat");
_console.WriteLine(" CheckRelease production --settings-diff");
_console.WriteLine(" CheckRelease --settings-diff=\"project-dir/appsettings.json\" release-1.2.3-uat");
_console.WriteLine(" CheckRelease --html --settings-diff=\"project-dir/appsettings.json\" release-1.2.3-uat release-1.2.4-uat");
_console.WriteLine(" CheckRelease --html --from-common-ancestor v1.2.0 > changes-since-v1.2.0.html");
_console.WriteLine(" CheckRelease --html --from-common-ancestor production > changes-since-production.html");
_console.WriteLine(" CheckRelease --from-common-ancestor uat production");
}
/// <summary>
/// Represents the command line options for the CheckRelease application.
/// </summary>
public class CommandLineOptions
{
/// <summary>
/// Gets or sets a value indicating whether to generate HTML output.
/// </summary>
public bool HtmlOutput { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to include settings diff.
/// </summary>
public bool SettingsDiff { get; set; }
/// <summary>
/// Gets or sets the path to the appsettings.json file relative to the repository root.
/// </summary>
public string SettingsPath { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether to enable debug mode.
/// </summary>
public bool DebugMode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to enable trace mode.
/// </summary>
public bool TraceMode { get; set; }
/// <summary>
/// Gets or sets the number of days to look back for tags.
/// </summary>
public int SpanDays { get; set; }
/// <summary>
/// Gets or sets the prefix for JIRA tickets in commit messages.
/// </summary>
public string Prefix { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the base URL for JIRA tickets.
/// </summary>
public string JiraBaseUrl { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether to use common ancestor comparison.
/// </summary>
public bool FromCommonAncestor { get; set; }
/// <summary>
/// Gets or sets the non-option arguments (tags or commands).
/// </summary>
public List<string> Arguments { get; set; } = new List<string>();
}
}
}