getopt.net
A port of getopt in pure C#.
Loading...
Searching...
No Matches
GetOpt.cs
Go to the documentation of this file.
1using System;
2
3namespace getopt.net {
4
5 using System.IO;
6 using System.Text.RegularExpressions;
7
15 public partial class GetOpt {
16
20 public const char MissingArgChar = '?';
21
25 public const char InvalidOptChar = '!';
26
30 public const char NonOptChar = (char)1;
31
35 public const string DoubleDash = "--";
36
41 public const char SingleDash = '-';
42
47 public const char SingleSlash = '/';
48
52 public const char WinArgSeparator = ':';
53
57 public const char GnuArgSeparator = '=';
58
62 public const string ArgSplitRegex = @"([\s]|[=])";
63
68 public const char SingleAtSymbol = '@';
69
73 public Option[] Options { get; set; } = Array.Empty<Option>();
74
78 public string? ShortOpts { get; set; } = null;
79
84 public bool DoubleDashStopsParsing { get; set; } = true;
85
86 private string[] m_appArgs = Array.Empty<string>();
87
91 public string[] AppArgs {
92 get => m_appArgs;
93 set => m_appArgs = value;
94 }
95
100 public bool OnlyShortOpts { get; set; } = false;
101
106 public bool IgnoreEmptyOptions { get; set; } = true;
107
115 public bool IgnoreMissingArgument { get; set; } = false;
116
124 public bool IgnoreInvalidOptions { get; set; } = true;
125
130 public bool IgnoreEmptyAppArgs { get; set; } = true;
131
139 public bool StopParsingOptions { get; set; } = false;
140
149 public bool AllowWindowsConventions { get; set; } = false;
150
160 public bool AllowPowershellConventions { get; set; } = false;
161
178 public bool AllowParamFiles { get; set; } = false;
179
191
196
201 public GetOpt() { }
202
209 public GetOpt(string[] appArgs, string shortOpts, params Option[] options) {
210 AppArgs = appArgs;
211 ShortOpts = shortOpts;
212 Options = options;
213 }
214
218 protected int m_currentIndex = 0;
219
223 protected int m_optPosition = 1; // this applies to short opts only, where [0] == '-'
224
229#if NET7_0_OR_GREATER
230 [GeneratedRegex(ArgSplitRegex, RegexOptions.IgnoreCase | RegexOptions.Compiled)]
231 protected static partial Regex ArgumentSplitter();
232#else
233 protected static Regex ArgumentSplitter() => new(ArgSplitRegex, RegexOptions.IgnoreCase | RegexOptions.Compiled);
234#endif
235
239 protected void ResetOptPosition() => m_optPosition = 1;
240
254 protected bool MustReturnChar1() => ShortOpts?.Length > 0 && ShortOpts[0] == '-';
255
263 protected bool MustStopParsing() => ShortOpts?.Length > 0 && ShortOpts[0] == '+' || Environment.GetEnvironmentVariable("POSIXLY_CORRECT") is not null;
264
271 public int GetNextOpt(out string? outOptArg) {
272 if (AppArgs.Length == 0) {
273 if (!IgnoreEmptyAppArgs) {
274 throw new ParseException("No arguments found for parsing!");
275 } else {
276 outOptArg = null;
277 return -1;
278 }
279 }
280
281 outOptArg = null; // pre-set this here so we don't have to set it during every condition
282
283 if (CurrentIndex >= AppArgs.Length) { return -1; }
284
285 if (string.IsNullOrEmpty(AppArgs[m_currentIndex])) {
286 if (!IgnoreEmptyOptions) {
287 throw new ParseException("Encountered null or empty argument!");
288 } else { return -1; }
289 } else if (DoubleDashStopsParsing && AppArgs[CurrentIndex].Equals(DoubleDash, StringComparison.InvariantCultureIgnoreCase)) {
291 StopParsingOptions = true;
292 return GetNextOpt(out outOptArg);
293 }
294
295 // Check here if StopParsingOptions is true
296 // if so, then simply return NonOptChar and set outOptArg to the value of the argument
297 if (StopParsingOptions) {
298 outOptArg = AppArgs[CurrentIndex];
300 return NonOptChar;
301 }
302
303 // Now check if the current argument is a paramfile argument
304 if (IsParamFileArg(AppArgs[CurrentIndex], out var paramFile) && paramFile is not null) {
305 ReadParamFile(new FileInfo(paramFile));
307 return GetNextOpt(out outOptArg); // We don't need to pass this back to the application. Instead just continue on
308 }
309
311 if (Options.Length == 0) { throw new ParseException("Cannot parse long option! No option list provided!"); }
312 return ParseLongOption(out outOptArg);
313 } else if (IsShortOption(AppArgs[CurrentIndex])) {
314 // check if both arg lists are empty
315 if (string.IsNullOrEmpty(ShortOpts) && Options.Length == 0) { throw new ParseException("Cannot parse short option! No option list provided!"); }
316 return ParseShortOption(out outOptArg);
317 }
318
320 outOptArg = AppArgs[CurrentIndex];
322 if (MustReturnChar1()) { return NonOptChar; }
323 else { return InvalidOptChar; }
324 } else {
325 throw new ParseException(AppArgs[CurrentIndex], "Unexpected option argument!");
326 }
327 }
328
334 var optChar = GetNextOpt(out var optArg);
335
336 if (optArg is null) {
337 return new CommandOption(optChar);
338 }
339
340 if (bool.TryParse(optArg, out bool outBool)) {
341 return new CommandOption(optChar, outBool);
342 } else if (double.TryParse(optArg, out double outDouble)) {
343 return new CommandOption(optChar, outDouble);
344 } else if (float.TryParse(optArg, out float outFloat)) {
345 return new CommandOption(optChar, outFloat);
346 } else if (int.TryParse(optArg, out int outInt)) {
347 return new CommandOption(optChar, outInt);
348 }
349
350 return new CommandOption(optChar, optArg);
351 }
352
363 protected int ParseLongOption(out string? optArg) {
364 if (HasArgumentInOption(out var optName, out optArg)) {
365 AppArgs[m_currentIndex] = optName;
366 }
367
369
370 var nullableOpt = Options.FindOptionOrDefault(AppArgs[m_currentIndex]);
371 if (nullableOpt is null) {
374 throw new ParseException(AppArgs[m_currentIndex], "Invalid option found!");
375 }
376
377 optArg = AppArgs[m_currentIndex];
378 return InvalidOptChar;
379 }
380
381 var opt = (Option)nullableOpt;
382 switch (opt.ArgumentType) {
383 case ArgumentType.Required:
384 if (optArg == null && (IsLongOption(AppArgs[CurrentIndex + 1]) || IsShortOption(AppArgs[CurrentIndex + 1]))) {
387 return MissingArgChar;
388 } else {
389 throw new ParseException(AppArgs[CurrentIndex], "Missing required argument!");
390 }
391 } else if (optArg != null) { break; }
392
393 optArg = AppArgs[CurrentIndex + 1];
394 if (MustStopParsing()) { // POSIX behaviour desired
395 m_currentIndex = AppArgs.Length;
396 break;
397 }
398
400 break;
401 case ArgumentType.Optional:
402 // DRY this off at some point
403 if (optArg == null && !IsLongOption(AppArgs[CurrentIndex + 1]) && !IsShortOption(AppArgs[CurrentIndex + 1])) {
404 optArg = AppArgs[CurrentIndex + 1];
406 }
407 break;
408 default: // this case will handle cases where developers carelessly cast integers to the enum type
409 optArg = null;
410 break;
411 }
412
414 return opt.Value;
415 }
416
425 protected bool TryGetArgumentForShortOption(ref string? arg, out bool incrementCurrentIndex) {
426 incrementCurrentIndex = false; // pre-set this
427
428 if (m_optPosition + 1 < AppArgs[CurrentIndex].Length) {
429 arg = AppArgs[CurrentIndex].Substring(m_optPosition + 1);
430 incrementCurrentIndex = true;
431 return true;
432 }
433
434 if (CurrentIndex + 1 >= AppArgs.Length) {
435 return false;
436 }
437
439 arg = AppArgs[CurrentIndex + 1];
440
441 if (MustStopParsing()) {
442 m_currentIndex = AppArgs.Length; // POSIX behaviour desired
443 } else {
444 m_currentIndex += 2;
445 }
446
447 return true;
448 }
449
450 return false;
451 }
452
459 protected int ParseShortOption(out string? optArg) {
460 optArg = null;
461 var curOpt = AppArgs[CurrentIndex][m_optPosition];
462
463 bool incrementCurrentIndex = false;
464 var argType = ShortOptRequiresArg(curOpt);
465 if (argType is null) {
468 return InvalidOptChar;
469 } else if (argType is ArgumentType type) {
470 switch (type) {
471 default:
472 if (AppArgs[CurrentIndex].Length > AppArgs[CurrentIndex].IndexOf(curOpt) + 1) {
474 return curOpt;
475 }
476 break;
477 case ArgumentType.Optional:
478 if (TryGetArgumentForShortOption(ref optArg, out incrementCurrentIndex)) {
480 if (incrementCurrentIndex) { m_currentIndex++; }
481 return curOpt;
482 }
483 break;
484 case ArgumentType.Required:
485 if (!TryGetArgumentForShortOption(ref optArg, out incrementCurrentIndex)) {
486 if (incrementCurrentIndex) { m_currentIndex++; }
488 else { throw new ParseException(curOpt.ToString(), "Missing argument for option!"); }
489 }
490 break;
491 }
492 }
493
496
497 return curOpt;
498 }
499
506 protected ArgumentType? ShortOptRequiresArg(char shortOpt) {
507 if (ShortOpts is not null && !string.IsNullOrEmpty(ShortOpts)) {
508 var posInStr = ShortOpts.IndexOf(shortOpt);
509 if (posInStr == -1) {
510 goto CheckLongOpt;
511 }
512
513 try {
514
515 char charToCheck;
516 if (posInStr < ShortOpts.Length - 1) {
517 charToCheck = ShortOpts[posInStr + 1];
518 } else {
519 charToCheck = ShortOpts[posInStr];
520 }
521
522 switch (charToCheck) {
523 case ':':
524 return ArgumentType.Required;
525 case ';':
526 return ArgumentType.Optional;
527 default:
528 return ArgumentType.None;
529 }
530 } catch {
531 goto CheckLongOpt;
532 }
533 }
534
535 CheckLongOpt:
536 if (Options.Length == 0) {
538 return null;
539 } else {
540 throw new ParseException(shortOpt.ToString(), "Invalid option list!");
541 }
542 }
543 var nullableOpt = Options.FindOptionOrDefault(shortOpt);
544
545 if (nullableOpt == null) {
547 return ArgumentType.None;
548 } else { throw new ParseException(shortOpt.ToString(), "Encountered unknown option!"); }
549 }
550
551 var opt = (Option)nullableOpt;
552 return opt.ArgumentType ?? ArgumentType.None;
553 }
554
561 protected bool HasArgumentInOption(out string optName, out string? argVal) {
562 var curArg = AppArgs[CurrentIndex];
563 var splitString = default(string[]);
564
566 // if we're allowing Windows conventions, we have to replace
567 // the first occurrence of ':' in the arg string with '='
568 var indexOfSeparator = curArg.IndexOf(WinArgSeparator);
569 if (indexOfSeparator != -1) {
570 curArg = $"{ curArg.Substring(0, indexOfSeparator) }{ GnuArgSeparator }{ curArg.Substring(indexOfSeparator + 1) }";
571 }
572 }
573
574 splitString = ArgumentSplitter().Split(curArg);
575
576 if (splitString.Length == 1) {
577 optName = StripDashes(true); // we can set this to true, because this method will only ever be called for long opts
578 argVal = null;
579 return false;
580 }
581
582 optName = splitString[0];
583#if NET6_0_OR_GREATER
584 argVal = string.Join("", splitString[2..]);
585#else
586 argVal = string.Join("", splitString.Skip(2));
587#endif
588 return true;
589 }
590
599 protected string StripDashes(bool isLongOpt) {
600 var curArg = AppArgs[m_currentIndex];
601
603 curArg.StartsWith(SingleSlash.ToString())) {
604 return curArg.Substring(1);
605 }
606
607 if (!curArg.StartsWith(DoubleDash) &&
608 !curArg.StartsWith(SingleDash.ToString())) {
609 return curArg;
610 }
611
612 if (isLongOpt && curArg.StartsWith(DoubleDash)) {
613 return curArg.Substring(2);
614 } else if (curArg.StartsWith(SingleDash.ToString())) {
615 return curArg.Substring(1);
616 }
617
618 return curArg;
619 }
620
626 protected static bool ShallStopParsing(ref string arg) {
627 return !string.IsNullOrEmpty(arg) &&
628 arg.Equals("--", StringComparison.CurrentCultureIgnoreCase);
629 }
630
636 protected bool IsLongOption(string arg) {
637 if (string.IsNullOrEmpty(arg)) { return false; }
638
639 if (
641 arg.Length > 1 &&
642 arg[0] == SingleSlash &&
643 Options.Length != 0 &&
644 Options.Any(o => o.Name == arg.Split(WinArgSeparator, GnuArgSeparator, ' ')[0].Substring(1)) // We only need this option when parsing options following Windows' conventions
645 ) { return true; }
646
647 // Check for Powershell-style arguments.
648 // Powershell arguments are weird and extra checks are needed.
649 // Powershell-style arguments would theoretically interfere with short opts,
650 // so a check to determine whether or not the option is found in Options is required.
651 if (
653 arg.Length > 1 &&
654 arg[0] == SingleDash &&
655 Options.Length != 0 &&
656 Options.Any(o => o.Name == arg.Split(WinArgSeparator, GnuArgSeparator, ' ')[0].Substring(1)) // We only need this when parsing options following Powershell's conventions
657 // This parsing method is really similar to Windows option parsing...
658 ) { return true; }
659
660 return arg.Length > 2 &&
661 arg[0] == SingleDash &&
662 arg[1] == SingleDash;
663 }
664
670 protected bool IsShortOption(string arg) {
671 if (string.IsNullOrEmpty(arg)) { return false; }
672
673 if (
675 arg.Length > 1 &&
676 arg[0] == SingleSlash &&
677 arg[1] != SingleSlash
678 ) { return true; }
679
680 return arg.Length > 1 &&
681 arg[0] == SingleDash &&
682 arg[1] != SingleDash;
683 }
684
696 protected bool IsParamFileArg(string arg, out string? paramFile) {
697 paramFile = null; // pre-set this so we don't have to do it everywhere
698 if (string.IsNullOrEmpty(arg) || !AllowParamFiles || arg.Length < 2) { return false; }
699
700 if (arg[0] != SingleAtSymbol) { return false; }
701
702 arg = arg.TrimStart('@');
703
704 if (File.Exists(arg)) {
705 paramFile = arg;
706 }
707
708 return true;
709 }
710
715 protected void ReadParamFile(FileInfo paramFile) {
716 if (paramFile == null || !paramFile.Exists) { return; }
717
718 var lastIndex = AppArgs.Length;
719 var lines = File.ReadAllLines(paramFile.FullName);
720 Array.Resize(ref m_appArgs, lines.Length + AppArgs.Length);
721
722 for (int i = lastIndex, j = 0; i < m_appArgs.Length && j < lines.Length; i++, j++) {
723 if (string.IsNullOrEmpty(lines[j].Trim())) { continue; }
724 if (lines[j].Trim()[0] == '#') { continue; }
725
726 m_appArgs[i] = lines[j];
727 }
728 }
729 }
730}
731
Represents a single argument received via command-line options.
GetOpt-like class for handling getopt-like command-line arguments in .net.
Definition GetOpt.cs:15
const char GnuArgSeparator
The argument separator used by POSIX / GNU getopt.
Definition GetOpt.cs:57
bool TryGetArgumentForShortOption(ref string? arg, out bool incrementCurrentIndex)
Attempts to retrieve the argument for the current short option.
Definition GetOpt.cs:425
static Regex ArgumentSplitter()
Compiled Regex.
bool IsParamFileArg(string arg, out string? paramFile)
Gets a value indicating whether or not a given option is a paramfile option. AllowParamFiles.
Definition GetOpt.cs:696
bool AllExceptionsDisabled
Either enables or disabled exceptions entirely. For more specific control over exceptions,...
Definition GetOpt.cs:185
bool HasArgumentInOption(out string optName, out string? argVal)
Determines whether or not the current option contains its argument with the string or not.
Definition GetOpt.cs:561
bool OnlyShortOpts
Gets or sets a value indicating whether or not to only parse short options. Default:
Definition GetOpt.cs:100
bool MustStopParsing()
If the first character of ShortOpts is '+' or the environment variable POSIXLY_CORRECT is set,...
bool IsShortOption(string arg)
Gets a value indicating whether or not the current string is/contains a (or more) short option.
Definition GetOpt.cs:670
string StripDashes(bool isLongOpt)
Strips leading dashes from strings.
Definition GetOpt.cs:599
const char SingleSlash
A single slash. This is the char that is searched for when parsing arguments with the Windows convent...
Definition GetOpt.cs:47
GetOpt()
Default constructor; it is recommended to use this constructor and to use brace-initialiser-lists to ...
Definition GetOpt.cs:201
bool DoubleDashStopsParsing
Gets or sets a value indicating whether or not "--" stops parsing. Default:
Definition GetOpt.cs:84
CommandOption GetNextOpt()
Gets the next option in the list, returning an object containing more detailled information about the...
Definition GetOpt.cs:333
Option[] Options
An optional list of long options to go with the short options.
Definition GetOpt.cs:73
const char WinArgSeparator
The argument separator used by Windows.
Definition GetOpt.cs:52
ArgumentType? ShortOptRequiresArg(char shortOpt)
Gets a value indicating whether or not a short option requires an argument.
Definition GetOpt.cs:506
bool AllowParamFiles
Gets or sets a value indicating whether or not parameter files are accepted as a valid form of input.
Definition GetOpt.cs:178
void ReadParamFile(FileInfo paramFile)
Reads the incoming param file and adds the contents to AppArgs
Definition GetOpt.cs:715
bool IgnoreEmptyOptions
Gets or sets a value indicating whether or not to ignore empty values. Default:
Definition GetOpt.cs:106
bool IgnoreInvalidOptions
Gets or sets a value indicating whether or not invalid arguments should be ignored or not....
Definition GetOpt.cs:124
const char NonOptChar
The character that is returned when a non-option value is encountered and it is not the argument to a...
Definition GetOpt.cs:30
int ParseLongOption(out string? optArg)
Parses long options.
Definition GetOpt.cs:363
int ParseShortOption(out string? optArg)
Parses a single short option.
Definition GetOpt.cs:459
int m_currentIndex
The current index while traversing AppArgs
Definition GetOpt.cs:218
const string ArgSplitRegex
The regex used by ArgumentSplitter to split arguments into a key-value pair.
Definition GetOpt.cs:62
bool IgnoreMissingArgument
Gets or sets a value indicating whether or not to ignore missing arguments. Default:
Definition GetOpt.cs:115
string[] AppArgs
Gets or sets the arguments to parse.
Definition GetOpt.cs:91
bool StopParsingOptions
Gets or sets a value indicating whether or not option parsing shall stop or not. Default:
Definition GetOpt.cs:139
bool AllowWindowsConventions
Gets or sets a value indicating whether or not Windows argument conventions are allowed....
Definition GetOpt.cs:149
bool AllowPowershellConventions
Gets or sets a value indicating whether or not Powershell-style arguments are allowed....
Definition GetOpt.cs:160
const char SingleAtSymbol
A single "at" character. This character is used when AllowParamFiles is enabled, to determine whether...
Definition GetOpt.cs:68
const char MissingArgChar
The character that is returned when an option is missing a required argument.
Definition GetOpt.cs:20
bool IgnoreEmptyAppArgs
Gets or sets a value indicating whether or not empty AppArgs are ignored or throw an exception....
Definition GetOpt.cs:130
const char SingleDash
A single dash character. This is the character that is searched for, when parsing POSIX-/GNU-like opt...
Definition GetOpt.cs:41
static bool ShallStopParsing(ref string arg)
Gets a value indicating whether or not the parser shall stop here.
Definition GetOpt.cs:626
string[] m_appArgs
Definition GetOpt.cs:86
const char InvalidOptChar
The character that is returned when an invalid option is returned.
Definition GetOpt.cs:25
void ResetOptPosition()
Resets the option position to 1.
string? ShortOpts
The short opts to use.
Definition GetOpt.cs:78
int CurrentIndex
Gets the current index of the app arguments being parsed.
Definition GetOpt.cs:195
const string DoubleDash
This is the string getopt.net looks for when DoubleDashStopsParsing is enabled.
Definition GetOpt.cs:35
bool IsLongOption(string arg)
Gets a value indicating whether or not the current string is a long option.
Definition GetOpt.cs:636
bool MustReturnChar1()
Gets a value indicating whether or not non-options should be handled as if they were the argument of ...
int m_optPosition
The current position in a multi-option string such as "-xvzRf" when parsing short options.
Definition GetOpt.cs:223
GetOpt(string[] appArgs, string shortOpts, params Option[] options)
Specialised constructor.
Definition GetOpt.cs:209
int GetNextOpt(out string? outOptArg)
Gets the next option in the list.
Definition GetOpt.cs:271
Generic exception class that is thrown when the parser is not configured to ignore errors.
ArgumentType
Enumeration containing the argument types possible for getopt.
Represents a single long option for getopt.
Definition Option.cs:8