NTP Analyzer  0.8.2
Analyze the operation of time servers
Options.cs
Go to the documentation of this file.
1 //
2 // Copyright (c) 2013-2017 Carsten Sonne Larsen <cs@innolan.net>
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 
22 using System;
23 using System.Collections;
24 using System.Collections.Generic;
25 using System.Collections.ObjectModel;
26 using System.ComponentModel;
27 using System.Diagnostics.CodeAnalysis;
28 using System.IO;
29 using System.Runtime.Serialization;
30 using System.Security.Permissions;
31 using System.Text;
32 using System.Text.RegularExpressions;
33 using MessageLocalizerConverter = System.Converter<string, string>;
34 
162 namespace Ntp.Common.IO
163 {
164  internal static class StringCoda
165  {
166  public static IEnumerable<string> WrappedLines(string self, params int[] widths)
167  {
168  IEnumerable<int> w = widths;
169  return WrappedLines(self, w);
170  }
171 
172  public static IEnumerable<string> WrappedLines(string self, IEnumerable<int> widths)
173  {
174  if (widths == null)
175  throw new ArgumentNullException(nameof(widths));
176  return CreateWrappedLinesIterator(self, widths);
177  }
178 
179  private static IEnumerable<string> CreateWrappedLinesIterator(string self, IEnumerable<int> widths)
180  {
181  if (string.IsNullOrEmpty(self))
182  {
183  yield return string.Empty;
184  yield break;
185  }
186  using (var ewidths = widths.GetEnumerator())
187  {
188  bool? hw = null;
189  var width = GetNextWidth(ewidths, int.MaxValue, ref hw);
190  var start = 0;
191  do
192  {
193  var end = GetLineEnd(start, width, self);
194  var c = self[end - 1];
195  if (char.IsWhiteSpace(c))
196  --end;
197  var needContinuation = end != self.Length && !IsEolChar(c);
198  var continuation = "";
199  if (needContinuation)
200  {
201  --end;
202  continuation = "-";
203  }
204  var line = self.Substring(start, end - start) + continuation;
205  yield return line;
206  start = end;
207  if (char.IsWhiteSpace(c))
208  ++start;
209  width = GetNextWidth(ewidths, width, ref hw);
210  } while (start < self.Length);
211  }
212  }
213 
214  private static int GetLineEnd(int start, int length, string description)
215  {
216  var end = Math.Min(start + length, description.Length);
217  var sep = -1;
218  for (var i = start; i < end; ++i)
219  {
220  if (description[i] == '\n')
221  return i + 1;
222  if (IsEolChar(description[i]))
223  sep = i + 1;
224  }
225  if (sep == -1 || end == description.Length)
226  return end;
227  return sep;
228  }
229 
230  private static int GetNextWidth(IEnumerator<int> ewidths, int curWidth, ref bool? eValid)
231  {
232  if (eValid.HasValue && !eValid.Value)
233  return curWidth;
234 
235  curWidth = (eValid = ewidths.MoveNext()).Value ? ewidths.Current : curWidth;
236  // '.' is any character, - is for a continuation
237  const string minWidth = ".-";
238  if (curWidth < minWidth.Length)
239  throw new ArgumentOutOfRangeException(nameof(curWidth),
240  $"Element must be >= {minWidth.Length}, was {curWidth}.");
241  return curWidth;
242  // no more elements, use the last element.
243  }
244 
245  private static bool IsEolChar(char c)
246  {
247  return !char.IsLetterOrDigit(c);
248  }
249  }
250 
251  public sealed class OptionValueCollection : IList, IList<string>
252  {
254  {
255  this.c = c;
256  }
257 
258  private readonly OptionContext c;
259 
260  private readonly List<string> values = new List<string>();
261 
262  #region IEnumerable
263 
264  IEnumerator IEnumerable.GetEnumerator()
265  {
266  return values.GetEnumerator();
267  }
268 
269  #endregion
270 
271  #region IEnumerable<T>
272 
273  public IEnumerator<string> GetEnumerator()
274  {
275  return values.GetEnumerator();
276  }
277 
278  #endregion
279 
280  public string[] ToArray()
281  {
282  return values.ToArray();
283  }
284 
285  public List<string> ToList()
286  {
287  return new List<string>(values);
288  }
289 
290  public override string ToString()
291  {
292  return string.Join(", ", values.ToArray());
293  }
294 
295  #region ICollection
296 
297  void ICollection.CopyTo(Array array, int index)
298  {
299  (values as ICollection).CopyTo(array, index);
300  }
301 
302  bool ICollection.IsSynchronized => (values as ICollection).IsSynchronized;
303  object ICollection.SyncRoot => (values as ICollection).SyncRoot;
304 
305  #endregion
306 
307  #region ICollection<T>
308 
309  public void Add(string item)
310  {
311  values.Add(item);
312  }
313 
314  public void Clear()
315  {
316  values.Clear();
317  }
318 
319  public bool Contains(string item)
320  {
321  return values.Contains(item);
322  }
323 
324  public void CopyTo(string[] array, int arrayIndex)
325  {
326  values.CopyTo(array, arrayIndex);
327  }
328 
329  public bool Remove(string item)
330  {
331  return values.Remove(item);
332  }
333 
334  public int Count => values.Count;
335  public bool IsReadOnly => false;
336 
337  #endregion
338 
339  #region IList
340 
341  int IList.Add(object value)
342  {
343  return (values as IList).Add(value);
344  }
345 
346  bool IList.Contains(object value)
347  {
348  return (values as IList).Contains(value);
349  }
350 
351  int IList.IndexOf(object value)
352  {
353  return (values as IList).IndexOf(value);
354  }
355 
356  void IList.Insert(int index, object value)
357  {
358  (values as IList).Insert(index, value);
359  }
360 
361  void IList.Remove(object value)
362  {
363  (values as IList).Remove(value);
364  }
365 
366  void IList.RemoveAt(int index)
367  {
368  (values as IList).RemoveAt(index);
369  }
370 
371  bool IList.IsFixedSize => false;
372 
373  object IList.this[int index]
374  {
375  get { return this[index]; }
376  set { (values as IList)[index] = value; }
377  }
378 
379  #endregion
380 
381  #region IList<T>
382 
383  public int IndexOf(string item)
384  {
385  return values.IndexOf(item);
386  }
387 
388  public void Insert(int index, string item)
389  {
390  values.Insert(index, item);
391  }
392 
393  public void RemoveAt(int index)
394  {
395  values.RemoveAt(index);
396  }
397 
398  private void AssertValid(int index)
399  {
400  if (c.Option == null)
401  throw new InvalidOperationException("OptionContext.Option is null.");
402  if (index >= c.Option.MaxValueCount)
403  throw new ArgumentOutOfRangeException(nameof(index));
404  if (c.Option.OptionValueType == OptionValueType.Required &&
405  index >= values.Count)
406  throw new OptionException(string.Format(
407  c.OptionSet.MessageLocalizer("Missing required value for option '{0}'."), c.OptionName),
408  c.OptionName);
409  }
410 
411  public string this[int index]
412  {
413  get
414  {
415  AssertValid(index);
416  return index >= values.Count ? null : values[index];
417  }
418  set { values[index] = value; }
419  }
420 
421  #endregion
422  }
423 
424  public class OptionContext
425  {
427  {
428  OptionSet = set;
429  OptionValues = new OptionValueCollection(this);
430  }
431 
432  public Option Option { get; set; }
433 
434  public string OptionName { get; set; }
435 
436  public int OptionIndex { get; set; }
437 
438  public OptionSet OptionSet { get; }
439 
440  public OptionValueCollection OptionValues { get; }
441  }
442 
443  public enum OptionValueType
444  {
445  None,
446  Optional,
447  Required
448  }
449 
450  public abstract class Option
451  {
452  protected Option(string prototype, string description)
453  : this(prototype, description, 1, false)
454  {
455  }
456 
457  protected Option(string prototype, string description, int maxValueCount)
458  : this(prototype, description, maxValueCount, false)
459  {
460  }
461 
462  [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
463  protected Option(string prototype, string description, int maxValueCount, bool hidden)
464  {
465  if (prototype == null)
466  throw new ArgumentNullException(nameof(prototype));
467  if (prototype.Length == 0)
468  throw new ArgumentException("Cannot be the empty string.", nameof(prototype));
469  if (maxValueCount < 0)
470  throw new ArgumentOutOfRangeException(nameof(maxValueCount));
471 
472  Prototype = prototype;
473  Description = description;
474  MaxValueCount = maxValueCount;
475  Names = this is OptionSet.Category
476  // append GetHashCode() so that "duplicate" categories have distinct
477  // names, e.g. adding multiple "" categories should be valid.
478  // ReSharper disable once VirtualMemberCallInConstructor
479  ? new[] {prototype + GetHashCode()}
480  : prototype.Split('|');
481 
482  if (this is OptionSet.Category)
483  return;
484 
485  OptionValueType = ParsePrototype();
486  Hidden = hidden;
487 
488  if (MaxValueCount == 0 && OptionValueType != OptionValueType.None)
489  throw new ArgumentException(
490  "Cannot provide maxValueCount of 0 for OptionValueType.Required or " +
491  "OptionValueType.Optional.",
492  nameof(maxValueCount));
493 
494  if (OptionValueType == OptionValueType.None && maxValueCount > 1)
495  throw new ArgumentException(
496  $"Cannot provide maxValueCount of {maxValueCount} for OptionValueType.None.",
497  nameof(maxValueCount));
498 
499  if (Array.IndexOf(Names, "<>") >= 0 &&
500  ((Names.Length == 1 && OptionValueType != OptionValueType.None) ||
501  (Names.Length > 1 && MaxValueCount > 1)))
502  throw new ArgumentException(
503  "The default option handler '<>' cannot require values.",
504  nameof(prototype));
505  }
506 
507  private static readonly char[] NameTerminator = {'=', ':'};
508 
509  public string Prototype { get; }
510 
511  public string Description { get; }
512 
514 
515  public int MaxValueCount { get; }
516 
517  public bool Hidden { get; }
518 
519  internal string[] Names { get; }
520 
521  internal string[] ValueSeparators { get; private set; }
522 
523  public string[] GetNames()
524  {
525  return (string[]) Names.Clone();
526  }
527 
528  public string[] GetValueSeparators()
529  {
530  if (ValueSeparators == null)
531  return new string[0];
532  return (string[]) ValueSeparators.Clone();
533  }
534 
535  public void Invoke(OptionContext c)
536  {
537  OnParseComplete(c);
538  c.OptionName = null;
539  c.Option = null;
540  c.OptionValues.Clear();
541  }
542 
543  public override string ToString()
544  {
545  return Prototype;
546  }
547 
548  protected abstract void OnParseComplete(OptionContext c);
549 
550  protected static T Parse<T>(string value, OptionContext c)
551  {
552  var tt = typeof(T);
553  var ti = tt;
554 
555  var nullable =
556  ti.IsValueType &&
557  ti.IsGenericType &&
558  !ti.IsGenericTypeDefinition &&
559  ti.GetGenericTypeDefinition() == typeof(Nullable<>);
560 
561  var targetType = nullable ? tt.GetGenericArguments()[0] : tt;
562  var t = default(T);
563  try
564  {
565  if (value != null)
566  {
567  var conv = TypeDescriptor.GetConverter(targetType);
568  t = (T) conv.ConvertFromString(value);
569  }
570  }
571  catch (Exception e)
572  {
573  throw new OptionException(
574  string.Format(
575  c.OptionSet.MessageLocalizer("Could not convert string `{0}' to type {1} for option `{2}'."),
576  value, targetType.Name, c.OptionName),
577  c.OptionName, e);
578  }
579  return t;
580  }
581 
582  private static void AddSeparators(string name, int end, ICollection<string> seps)
583  {
584  var start = -1;
585  for (var i = end + 1; i < name.Length; ++i)
586  {
587  switch (name[i])
588  {
589  case '{':
590  if (start != -1)
591  throw new ArgumentException($"Ill-formed name/value separator found in \"{name}\".");
592  start = i + 1;
593  break;
594  case '}':
595  if (start == -1)
596  throw new ArgumentException($"Ill-formed name/value separator found in \"{name}\".");
597  seps.Add(name.Substring(start, i - start));
598  start = -1;
599  break;
600  default:
601  if (start == -1)
602  seps.Add(name[i].ToString());
603  break;
604  }
605  }
606 
607  if (start != -1)
608  throw new ArgumentException($"Ill-formed name/value separator found in \"{name}\".");
609  }
610 
612  {
613  var type = '\0';
614  var seps = new List<string>();
615 
616  for (var i = 0; i < Names.Length; ++i)
617  {
618  var name = Names[i];
619  if (name.Length == 0)
620  throw new ArgumentException("Empty option names are not supported.");
621 
622  var end = name.IndexOfAny(NameTerminator);
623  if (end == -1)
624  continue;
625  Names[i] = name.Substring(0, end);
626  if (type == '\0' || type == name[end])
627  type = name[end];
628  else
629  throw new ArgumentException($"Conflicting option types: '{type}' vs. '{name[end]}'.");
630  AddSeparators(name, end, seps);
631  }
632 
633  if (type == '\0')
634  return OptionValueType.None;
635 
636  if (MaxValueCount <= 1 && seps.Count != 0)
637  throw new ArgumentException(
638  $"Cannot provide key/value separators for Options taking {MaxValueCount} value(s).");
639 
640  if (MaxValueCount > 1)
641  {
642  if (seps.Count == 0)
643  ValueSeparators = new[] {":", "="};
644  else if (seps.Count == 1 && seps[0].Length == 0)
645  ValueSeparators = null;
646  else
647  ValueSeparators = seps.ToArray();
648  }
649 
650  return type == '=' ? OptionValueType.Required : OptionValueType.Optional;
651  }
652  }
653 
654  public abstract class ArgumentSource
655  {
656  public abstract string Description { get; }
657  public abstract bool GetArguments(string value, out IEnumerable<string> replacement);
658 
659  public static IEnumerable<string> GetArguments(TextReader reader)
660  {
661  return GetArguments(reader, false);
662  }
663 
664  public static IEnumerable<string> GetArgumentsFromFile(string file)
665  {
666  return GetArguments(File.OpenText(file), true);
667  }
668 
669  public abstract string[] GetNames();
670 
671  // Cribbed from mcs/driver.cs:LoadArgs(string)
672  private static IEnumerable<string> GetArguments(TextReader reader, bool close)
673  {
674  try
675  {
676  var arg = new StringBuilder();
677 
678  string line;
679  while ((line = reader.ReadLine()) != null)
680  {
681  var t = line.Length;
682 
683  for (var i = 0; i < t; i++)
684  {
685  var c = line[i];
686 
687  if (c == '"' || c == '\'')
688  {
689  var end = c;
690 
691  for (i++; i < t; i++)
692  {
693  c = line[i];
694 
695  if (c == end)
696  break;
697  arg.Append(c);
698  }
699  }
700  else if (c == ' ')
701  {
702  if (arg.Length > 0)
703  {
704  yield return arg.ToString();
705  arg.Length = 0;
706  }
707  }
708  else
709  arg.Append(c);
710  }
711  if (arg.Length > 0)
712  {
713  yield return arg.ToString();
714  arg.Length = 0;
715  }
716  }
717  }
718  finally
719  {
720  if (close)
721  reader.Dispose();
722  }
723  }
724  }
725 
727  {
728  public override string Description => "Read response file for more options.";
729 
730  public override bool GetArguments(string value, out IEnumerable<string> replacement)
731  {
732  if (string.IsNullOrEmpty(value) || !value.StartsWith("@"))
733  {
734  replacement = null;
735  return false;
736  }
737  replacement = GetArgumentsFromFile(value.Substring(1));
738  return true;
739  }
740 
741  public override string[] GetNames()
742  {
743  return new[] {"@file"};
744  }
745  }
746 
747  [Serializable]
748  public class OptionException : Exception
749  {
751  {
752  }
753 
754  public OptionException(string message, string optionName)
755  : base(message)
756  {
757  OptionName = optionName;
758  }
759 
760  public OptionException(string message, string optionName, Exception innerException)
761  : base(message, innerException)
762  {
763  OptionName = optionName;
764  }
765 
766  protected OptionException(SerializationInfo info, StreamingContext context)
767  : base(info, context)
768  {
769  OptionName = info.GetString("OptionName");
770  }
771 
772  public string OptionName { get; }
773 #pragma warning disable 618 // SecurityPermissionAttribute is obsolete
774  [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)]
775 #pragma warning restore 618
776  public override void GetObjectData(SerializationInfo info, StreamingContext context)
777  {
778  base.GetObjectData(info, context);
779  info.AddValue("OptionName", OptionName);
780  }
781  }
782 
783  public delegate void OptionAction<in TKey, in TValue>(TKey key, TValue value);
784 
785  public class OptionSet : KeyedCollection<string, Option>
786  {
787  public OptionSet()
788  : this(f => f)
789  {
790  }
791 
793  {
794  MessageLocalizer = localizer;
795  ArgumentSources = new ReadOnlyCollection<ArgumentSource>(sources);
796  }
797 
798  private const int OptionWidth = 29;
799  private const int DescriptionFirstWidth = 80 - OptionWidth;
800  private const int DescriptionRemWidth = 80 - OptionWidth - 2;
801 
802  private readonly List<ArgumentSource> sources = new List<ArgumentSource>();
803 
804  private readonly Regex valueOption = new Regex(
805  @"^(?<flag>--|-|/)(?<name>[^:=]+)((?<sep>[:=])(?<value>.*))?$");
806 
807  public MessageLocalizerConverter MessageLocalizer { get; }
808 
809  public ReadOnlyCollection<ArgumentSource> ArgumentSources { get; }
810 
811  public OptionSet Add(string header)
812  {
813  if (header == null)
814  throw new ArgumentNullException(nameof(header));
815  Add(new Category(header));
816  return this;
817  }
818 
819 
820  public new OptionSet Add(Option option)
821  {
822  base.Add(option);
823  return this;
824  }
825 
826  public OptionSet Add(string prototype, Action<string> action)
827  {
828  return Add(prototype, null, action);
829  }
830 
831  public OptionSet Add(string prototype, string description, Action<string> action)
832  {
833  return Add(prototype, description, action, false);
834  }
835 
836  public OptionSet Add(string prototype, string description, Action<string> action, bool hidden)
837  {
838  if (action == null)
839  throw new ArgumentNullException(nameof(action));
840  Option p = new ActionOption(prototype, description, 1,
841  delegate(OptionValueCollection v) { action(v[0]); }, hidden);
842  base.Add(p);
843  return this;
844  }
845 
846  public OptionSet Add(string prototype, OptionAction<string, string> action)
847  {
848  return Add(prototype, null, action);
849  }
850 
851  public OptionSet Add(string prototype, string description, OptionAction<string, string> action)
852  {
853  return Add(prototype, description, action, false);
854  }
855 
856  public OptionSet Add(string prototype, string description, OptionAction<string, string> action, bool hidden)
857  {
858  if (action == null)
859  throw new ArgumentNullException(nameof(action));
860  Option p = new ActionOption(prototype, description, 2,
861  delegate(OptionValueCollection v) { action(v[0], v[1]); }, hidden);
862  base.Add(p);
863  return this;
864  }
865 
866  public OptionSet Add<T>(string prototype, Action<T> action)
867  {
868  return Add(prototype, null, action);
869  }
870 
871  public OptionSet Add<T>(string prototype, string description, Action<T> action)
872  {
873  return Add(new ActionOption<T>(prototype, description, action));
874  }
875 
876  public OptionSet Add<TKey, TValue>(string prototype, OptionAction<TKey, TValue> action)
877  {
878  return Add(prototype, null, action);
879  }
880 
881  public OptionSet Add<TKey, TValue>(string prototype, string description, OptionAction<TKey, TValue> action)
882  {
883  return Add(new ActionOption<TKey, TValue>(prototype, description, action));
884  }
885 
886  public OptionSet Add(ArgumentSource source)
887  {
888  if (source == null)
889  throw new ArgumentNullException(nameof(source));
890  sources.Add(source);
891  return this;
892  }
893 
894  public List<string> Parse(IEnumerable<string> arguments)
895  {
896  if (arguments == null)
897  throw new ArgumentNullException(nameof(arguments));
898 
899  var c = CreateOptionContext();
900  c.OptionIndex = -1;
901  var process = true;
902  var unprocessed = new List<string>();
903  var def = Contains("<>") ? this["<>"] : null;
904  var ae = new ArgumentEnumerator(arguments);
905 
906  foreach (var argument in ae)
907  {
908  ++c.OptionIndex;
909  if (argument == "--")
910  {
911  process = false;
912  continue;
913  }
914  if (!process)
915  {
916  Unprocessed(unprocessed, def, c, argument);
917  continue;
918  }
919  if (AddSource(ae, argument))
920  continue;
921  if (!Parse(argument, c))
922  Unprocessed(unprocessed, def, c, argument);
923  }
924 
925  c.Option?.Invoke(c);
926  return unprocessed;
927  }
928 
929  public void WriteOptionDescriptions(TextWriter o)
930  {
931  foreach (var p in this)
932  {
933  var written = 0;
934 
935  if (p.Hidden)
936  continue;
937 
938  var c = p as Category;
939  if (c != null)
940  {
941  WriteDescription(o, p.Description, "", 80, 80);
942  continue;
943  }
944 
945  if (!WriteOptionPrototype(o, p, ref written))
946  continue;
947 
948  if (written < OptionWidth)
949  o.Write(new string(' ', OptionWidth - written));
950  else
951  {
952  o.WriteLine();
953  o.Write(new string(' ', OptionWidth));
954  }
955 
956  WriteDescription(o, p.Description, new string(' ', OptionWidth + 2),
957  DescriptionFirstWidth, DescriptionRemWidth);
958  }
959 
960  foreach (var s in sources)
961  {
962  var names = s.GetNames();
963  if (names == null || names.Length == 0)
964  continue;
965 
966  var written = 0;
967 
968  Write(o, ref written, " ");
969  Write(o, ref written, names[0]);
970  for (var i = 1; i < names.Length; ++i)
971  {
972  Write(o, ref written, ", ");
973  Write(o, ref written, names[i]);
974  }
975 
976  if (written < OptionWidth)
977  o.Write(new string(' ', OptionWidth - written));
978  else
979  {
980  o.WriteLine();
981  o.Write(new string(' ', OptionWidth));
982  }
983 
984  WriteDescription(o, s.Description, new string(' ', OptionWidth + 2),
985  DescriptionFirstWidth, DescriptionRemWidth);
986  }
987  }
988 
989  protected virtual OptionContext CreateOptionContext()
990  {
991  return new OptionContext(this);
992  }
993 
994 
995  protected override string GetKeyForItem(Option item)
996  {
997  if (item == null)
998  throw new ArgumentNullException(nameof(item));
999  if (item.Names != null && item.Names.Length > 0)
1000  return item.Names[0];
1001  // This should never happen, as it's invalid for Option to be
1002  // constructed w/o any names.
1003  throw new InvalidOperationException("Option has no names!");
1004  }
1005 
1006  [Obsolete("Use KeyedCollection.this[string]")]
1007  protected Option GetOptionForName(string option)
1008  {
1009  if (option == null)
1010  throw new ArgumentNullException(nameof(option));
1011  try
1012  {
1013  return base[option];
1014  }
1015  catch (KeyNotFoundException)
1016  {
1017  return null;
1018  }
1019  }
1020 
1021  protected bool GetOptionParts(string argument, out string flag, out string name, out string sep,
1022  out string value)
1023  {
1024  if (argument == null)
1025  throw new ArgumentNullException(nameof(argument));
1026 
1027  flag = name = sep = value = null;
1028  var m = valueOption.Match(argument);
1029  if (!m.Success)
1030  {
1031  return false;
1032  }
1033  flag = m.Groups["flag"].Value;
1034  name = m.Groups["name"].Value;
1035  if (m.Groups["sep"].Success && m.Groups["value"].Success)
1036  {
1037  sep = m.Groups["sep"].Value;
1038  value = m.Groups["value"].Value;
1039  }
1040  return true;
1041  }
1042 
1043  protected override void InsertItem(int index, Option item)
1044  {
1045  base.InsertItem(index, item);
1046  AddImpl(item);
1047  }
1048 
1049  protected virtual bool Parse(string argument, OptionContext c)
1050  {
1051  if (c.Option != null)
1052  {
1053  ParseValue(argument, c);
1054  return true;
1055  }
1056 
1057  string f, n, s, v;
1058  if (!GetOptionParts(argument, out f, out n, out s, out v))
1059  return false;
1060 
1061  if (Contains(n))
1062  {
1063  var p = this[n];
1064  c.OptionName = f + n;
1065  c.Option = p;
1066  switch (p.OptionValueType)
1067  {
1068  case OptionValueType.None:
1069  c.OptionValues.Add(n);
1070  c.Option.Invoke(c);
1071  break;
1072  case OptionValueType.Optional:
1073  case OptionValueType.Required:
1074  ParseValue(v, c);
1075  break;
1076  }
1077  return true;
1078  }
1079 
1080  // no match; is it a bool option?
1081  return ParseBool(argument, n, c) || ParseBundledValue(f, $"{n}{s}{v}", c);
1082  }
1083 
1084  protected override void RemoveItem(int index)
1085  {
1086  var p = Items[index];
1087  base.RemoveItem(index);
1088  // KeyedCollection.RemoveItem() handles the 0th item
1089  for (var i = 1; i < p.Names.Length; ++i)
1090  {
1091  Dictionary.Remove(p.Names[i]);
1092  }
1093  }
1094 
1095  protected override void SetItem(int index, Option item)
1096  {
1097  base.SetItem(index, item);
1098  AddImpl(item);
1099  }
1100 
1101  private void AddImpl(Option option)
1102  {
1103  if (option == null)
1104  throw new ArgumentNullException(nameof(option));
1105  var added = new List<string>(option.Names.Length);
1106  try
1107  {
1108  // KeyedCollection.InsertItem/SetItem handle the 0th name.
1109  for (var i = 1; i < option.Names.Length; ++i)
1110  {
1111  Dictionary.Add(option.Names[i], option);
1112  added.Add(option.Names[i]);
1113  }
1114  }
1115  catch (Exception)
1116  {
1117  foreach (var name in added)
1118  Dictionary.Remove(name);
1119  throw;
1120  }
1121  }
1122 
1123  private bool AddSource(ArgumentEnumerator ae, string argument)
1124  {
1125  foreach (var source in sources)
1126  {
1127  IEnumerable<string> replacement;
1128  if (!source.GetArguments(argument, out replacement))
1129  continue;
1130  ae.Add(replacement);
1131  return true;
1132  }
1133  return false;
1134  }
1135 
1136  private static string GetArgumentName(int index, int maxIndex, string description)
1137  {
1138  if (description == null)
1139  return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
1140 
1141  var nameStart = maxIndex == 1 ? new[] {"{0:", "{"} : new[] {"{" + index + ":"};
1142 
1143  foreach (var t in nameStart)
1144  {
1145  int start, j = 0;
1146  do
1147  {
1148  start = description.IndexOf(t, j, StringComparison.Ordinal);
1149  } while (start >= 0 && j != 0 && description[j++ - 1] == '{');
1150  if (start == -1)
1151  continue;
1152  var end = description.IndexOf("}", start, StringComparison.Ordinal);
1153  if (end == -1)
1154  continue;
1155  return description.Substring(start + t.Length, end - start - t.Length);
1156  }
1157 
1158  return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
1159  }
1160 
1161  private static string GetDescription(string description)
1162  {
1163  if (description == null)
1164  return string.Empty;
1165  var sb = new StringBuilder(description.Length);
1166  var start = -1;
1167  for (var i = 0; i < description.Length; ++i)
1168  {
1169  switch (description[i])
1170  {
1171  case '{':
1172  if (i == start)
1173  {
1174  sb.Append('{');
1175  start = -1;
1176  }
1177  else if (start < 0)
1178  start = i + 1;
1179  break;
1180  case '}':
1181  if (start < 0)
1182  {
1183  if (i + 1 == description.Length || description[i + 1] != '}')
1184  throw new InvalidOperationException("Invalid option description: " + description);
1185  ++i;
1186  sb.Append("}");
1187  }
1188  else
1189  {
1190  sb.Append(description.Substring(start, i - start));
1191  start = -1;
1192  }
1193  break;
1194  case ':':
1195  if (start < 0)
1196  goto default;
1197  start = i + 1;
1198  break;
1199  default:
1200  if (start < 0)
1201  sb.Append(description[i]);
1202  break;
1203  }
1204  }
1205  return sb.ToString();
1206  }
1207 
1208  private static IEnumerable<string> GetLines(string description, int firstWidth, int remWidth)
1209  {
1210  return StringCoda.WrappedLines(description, firstWidth, remWidth);
1211  }
1212 
1213  private static int GetNextOptionIndex(string[] names, int i)
1214  {
1215  while (i < names.Length && names[i] == "<>")
1216  {
1217  ++i;
1218  }
1219  return i;
1220  }
1221 
1222  private static void Invoke(OptionContext c, string name, string value, Option option)
1223  {
1224  c.OptionName = name;
1225  c.Option = option;
1226  c.OptionValues.Add(value);
1227  option.Invoke(c);
1228  }
1229 
1230  private bool ParseBool(string option, string n, OptionContext c)
1231  {
1232  string rn;
1233  if (n.Length >= 1 && (n[n.Length - 1] == '+' || n[n.Length - 1] == '-') &&
1234  Contains(rn = n.Substring(0, n.Length - 1)))
1235  {
1236  var p = this[rn];
1237  var v = n[n.Length - 1] == '+' ? option : null;
1238  c.OptionName = option;
1239  c.Option = p;
1240  c.OptionValues.Add(v);
1241  p.Invoke(c);
1242  return true;
1243  }
1244  return false;
1245  }
1246 
1247  private bool ParseBundledValue(string f, string n, OptionContext c)
1248  {
1249  if (f != "-")
1250  return false;
1251  for (var i = 0; i < n.Length; ++i)
1252  {
1253  var opt = f + n[i];
1254  var rn = n[i].ToString();
1255  if (!Contains(rn))
1256  {
1257  if (i == 0)
1258  return false;
1259  throw new OptionException(string.Format(MessageLocalizer(
1260  "Cannot use unregistered option '{0}' in bundle '{1}'."), rn, f + n), null);
1261  }
1262  var p = this[rn];
1263  switch (p.OptionValueType)
1264  {
1265  case OptionValueType.None:
1266  Invoke(c, opt, n, p);
1267  break;
1268  case OptionValueType.Optional:
1269  case OptionValueType.Required:
1270  {
1271  var v = n.Substring(i + 1);
1272  c.Option = p;
1273  c.OptionName = opt;
1274  ParseValue(v.Length != 0 ? v : null, c);
1275  return true;
1276  }
1277  default:
1278  throw new InvalidOperationException("Unknown OptionValueType: " + p.OptionValueType);
1279  }
1280  }
1281  return true;
1282  }
1283 
1284  private void ParseValue(string option, OptionContext c)
1285  {
1286  if (option != null)
1287  foreach (var o in c.Option.ValueSeparators != null
1288  ? option.Split(c.Option.ValueSeparators, c.Option.MaxValueCount - c.OptionValues.Count,
1289  StringSplitOptions.None)
1290  : new[] {option})
1291  {
1292  c.OptionValues.Add(o);
1293  }
1294  if (c.OptionValues.Count == c.Option.MaxValueCount ||
1295  c.Option.OptionValueType == OptionValueType.Optional)
1296  c.Option.Invoke(c);
1297  else if (c.OptionValues.Count > c.Option.MaxValueCount)
1298  {
1299  throw new OptionException(MessageLocalizer(
1300  $"Error: Found {c.OptionValues.Count} option values when expecting {c.Option.MaxValueCount}."),
1301  c.OptionName);
1302  }
1303  }
1304 
1305  private static void Unprocessed(ICollection<string> extra, Option def, OptionContext c, string argument)
1306  {
1307  if (def == null)
1308  {
1309  extra.Add(argument);
1310  return;
1311  }
1312  c.OptionValues.Add(argument);
1313  c.Option = def;
1314  c.Option.Invoke(c);
1315  }
1316 
1317  private static void Write(TextWriter o, ref int n, string s)
1318  {
1319  n += s.Length;
1320  o.Write(s);
1321  }
1322 
1323  private void WriteDescription(TextWriter o, string value, string prefix, int firstWidth, int remWidth)
1324  {
1325  var indent = false;
1326  foreach (var line in GetLines(MessageLocalizer(GetDescription(value)), firstWidth, remWidth))
1327  {
1328  if (indent)
1329  o.Write(prefix);
1330  o.WriteLine(line);
1331  indent = true;
1332  }
1333  }
1334 
1335  private bool WriteOptionPrototype(TextWriter o, Option p, ref int written)
1336  {
1337  var names = p.Names;
1338 
1339  var i = GetNextOptionIndex(names, 0);
1340  if (i == names.Length)
1341  return false;
1342 
1343  if (names[i].Length == 1)
1344  {
1345  Write(o, ref written, " -");
1346  Write(o, ref written, names[0]);
1347  }
1348  else
1349  {
1350  Write(o, ref written, " --");
1351  Write(o, ref written, names[0]);
1352  }
1353 
1354  for (i = GetNextOptionIndex(names, i + 1);
1355  i < names.Length;
1356  i = GetNextOptionIndex(names, i + 1))
1357  {
1358  Write(o, ref written, ", ");
1359  Write(o, ref written, names[i].Length == 1 ? "-" : "--");
1360  Write(o, ref written, names[i]);
1361  }
1362 
1363  if (p.OptionValueType == OptionValueType.Optional ||
1364  p.OptionValueType == OptionValueType.Required)
1365  {
1366  if (p.OptionValueType == OptionValueType.Optional)
1367  {
1368  Write(o, ref written, MessageLocalizer("["));
1369  }
1370  Write(o, ref written, MessageLocalizer("=" + GetArgumentName(0, p.MaxValueCount, p.Description)));
1371  var sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0
1372  ? p.ValueSeparators[0]
1373  : " ";
1374  for (var c = 1; c < p.MaxValueCount; ++c)
1375  {
1376  Write(o, ref written, MessageLocalizer(sep + GetArgumentName(c, p.MaxValueCount, p.Description)));
1377  }
1378  if (p.OptionValueType == OptionValueType.Optional)
1379  {
1380  Write(o, ref written, MessageLocalizer("]"));
1381  }
1382  }
1383  return true;
1384  }
1385 
1386  internal sealed class Category : Option
1387  {
1388  // Prototype starts with '=' because this is an invalid prototype
1389  // (see Option.ParsePrototype(), and thus it'll prevent Category
1390  // instances from being accidentally used as normal options.
1391  public Category(string description)
1392  : base("=:Category:= " + description, description)
1393  {
1394  }
1395 
1396  protected override void OnParseComplete(OptionContext c)
1397  {
1398  throw new NotSupportedException("Category.OnParseComplete should not be invoked.");
1399  }
1400  }
1401 
1402  private sealed class ActionOption : Option
1403  {
1404  public ActionOption(string prototype, string description, int count, Action<OptionValueCollection> action,
1405  bool hidden)
1406  : base(prototype, description, count, hidden)
1407  {
1408  if (action == null)
1409  throw new ArgumentNullException(nameof(action));
1410  this.action = action;
1411  }
1412 
1413  private readonly Action<OptionValueCollection> action;
1414 
1415  protected override void OnParseComplete(OptionContext c)
1416  {
1417  action(c.OptionValues);
1418  }
1419  }
1420 
1421  private sealed class ActionOption<T> : Option
1422  {
1423  public ActionOption(string prototype, string description, Action<T> action)
1424  : base(prototype, description, 1)
1425  {
1426  if (action == null)
1427  throw new ArgumentNullException(nameof(action));
1428  this.action = action;
1429  }
1430 
1431  private readonly Action<T> action;
1432 
1433  protected override void OnParseComplete(OptionContext c)
1434  {
1435  action(Parse<T>(c.OptionValues[0], c));
1436  }
1437  }
1438 
1439  private sealed class ActionOption<TKey, TValue> : Option
1440  {
1441  public ActionOption(string prototype, string description, OptionAction<TKey, TValue> action)
1442  : base(prototype, description, 2)
1443  {
1444  if (action == null)
1445  throw new ArgumentNullException(nameof(action));
1446  this.action = action;
1447  }
1448 
1449  private readonly OptionAction<TKey, TValue> action;
1450 
1451  protected override void OnParseComplete(OptionContext c)
1452  {
1453  action(
1454  Parse<TKey>(c.OptionValues[0], c),
1455  Parse<TValue>(c.OptionValues[1], c));
1456  }
1457  }
1458 
1459  private class ArgumentEnumerator : IEnumerable<string>
1460  {
1461  public ArgumentEnumerator(IEnumerable<string> arguments)
1462  {
1463  sources.Add(arguments.GetEnumerator());
1464  }
1465 
1466  private readonly List<IEnumerator<string>> sources = new List<IEnumerator<string>>();
1467 
1468  public IEnumerator<string> GetEnumerator()
1469  {
1470  do
1471  {
1472  var c = sources[sources.Count - 1];
1473  if (c.MoveNext())
1474  yield return c.Current;
1475  else
1476  {
1477  c.Dispose();
1478  sources.RemoveAt(sources.Count - 1);
1479  }
1480  } while (sources.Count > 0);
1481  }
1482 
1483  IEnumerator IEnumerable.GetEnumerator()
1484  {
1485  return GetEnumerator();
1486  }
1487 
1488  public void Add(IEnumerable<string> arguments)
1489  {
1490  sources.Add(arguments.GetEnumerator());
1491  }
1492  }
1493  }
1494 }
static int GetNextOptionIndex(string[] names, int i)
Definition: Options.cs:1213
bool WriteOptionPrototype(TextWriter o, Option p, ref int written)
Definition: Options.cs:1335
string[] GetValueSeparators()
Definition: Options.cs:528
OptionValueType ParsePrototype()
Definition: Options.cs:611
readonly OptionAction< TKey, TValue > action
Definition: Options.cs:1449
OptionValueType OptionValueType
Definition: Options.cs:513
string[] ValueSeparators
Definition: Options.cs:521
static string GetDescription(string description)
Definition: Options.cs:1161
IEnumerator< string > GetEnumerator()
Definition: Options.cs:273
IEnumerator< string > GetEnumerator()
Definition: Options.cs:1468
static void Unprocessed(ICollection< string > extra, Option def, OptionContext c, string argument)
Definition: Options.cs:1305
void AddImpl(Option option)
Definition: Options.cs:1101
OptionSet Add(ArgumentSource source)
Definition: Options.cs:886
d d d prototype close
Definition: bootstrap.min.js:6
OptionSet Add(string header)
Definition: Options.cs:811
Option GetOptionForName(string option)
Definition: Options.cs:1007
static void Invoke(OptionContext c, string name, string value, Option option)
Definition: Options.cs:1222
ActionOption(string prototype, string description, int count, Action< OptionValueCollection > action, bool hidden)
Definition: Options.cs:1404
void Insert(int index, string item)
Definition: Options.cs:388
static IEnumerable< string > GetArguments(TextReader reader, bool close)
Definition: Options.cs:672
override string ToString()
Definition: Options.cs:543
static IEnumerable< string > GetLines(string description, int firstWidth, int remWidth)
Definition: Options.cs:1208
if(typeof jQuery=== 'undefined')
Definition: bootstrap.js:7
static string GetArgumentName(int index, int maxIndex, string description)
Definition: Options.cs:1136
static int GetLineEnd(int start, int length, string description)
Definition: Options.cs:214
bool ParseBool(string option, string n, OptionContext c)
Definition: Options.cs:1230
ActionOption(string prototype, string description, Action< T > action)
Definition: Options.cs:1423
void CopyTo(string[] array, int arrayIndex)
Definition: Options.cs:324
var e
Definition: bootstrap.min.js:6
bool GetOptionParts(string argument, out string flag, out string name, out string sep, out string value)
Definition: Options.cs:1021
ArgumentEnumerator(IEnumerable< string > arguments)
Definition: Options.cs:1461
OptionSet Add(string prototype, string description, OptionAction< string, string > action, bool hidden)
Definition: Options.cs:856
Option(string prototype, string description, int maxValueCount)
Definition: Options.cs:457
Option(string prototype, string description, int maxValueCount, bool hidden)
Definition: Options.cs:463
void WriteOptionDescriptions(TextWriter o)
Definition: Options.cs:929
virtual bool Parse(string argument, OptionContext c)
Definition: Options.cs:1049
OptionSet Add(string prototype, string description, Action< string > action, bool hidden)
Definition: Options.cs:836
static IEnumerable< string > WrappedLines(string self, IEnumerable< int > widths)
Definition: Options.cs:172
Category(string description)
Definition: Options.cs:1391
static IEnumerable< string > GetArgumentsFromFile(string file)
Definition: Options.cs:664
OptionException(string message, string optionName, Exception innerException)
Definition: Options.cs:760
virtual OptionContext CreateOptionContext()
Definition: Options.cs:989
OptionSet Add(string prototype, Action< string > action)
Definition: Options.cs:826
readonly Action< OptionValueCollection > action
Definition: Options.cs:1413
Option(string prototype, string description)
Definition: Options.cs:452
var c
Definition: bootstrap.min.js:6
static IEnumerable< string > GetArguments(TextReader reader)
Definition: Options.cs:659
bool AddSource(ArgumentEnumerator ae, string argument)
Definition: Options.cs:1123
OptionException(string message, string optionName)
Definition: Options.cs:754
void Invoke(OptionContext c)
Definition: Options.cs:535
override void SetItem(int index, Option item)
Definition: Options.cs:1095
static void AddSeparators(string name, int end, ICollection< string > seps)
Definition: Options.cs:582
override void OnParseComplete(OptionContext c)
Definition: Options.cs:1415
OptionSet Add(string prototype, string description, OptionAction< string, string > action)
Definition: Options.cs:851
static void Write(TextWriter o, ref int n, string s)
Definition: Options.cs:1317
string[] GetNames()
Definition: Options.cs:523
new OptionSet Add(Option option)
Definition: Options.cs:820
void WriteDescription(TextWriter o, string value, string prefix, int firstWidth, int remWidth)
Definition: Options.cs:1323
bool ParseBundledValue(string f, string n, OptionContext c)
Definition: Options.cs:1247
override void GetObjectData(SerializationInfo info, StreamingContext context)
Definition: Options.cs:776
override string[] GetNames()
Definition: Options.cs:741
OptionContext(OptionSet set)
Definition: Options.cs:426
static int GetNextWidth(IEnumerator< int > ewidths, int curWidth, ref bool?eValid)
Definition: Options.cs:230
override void OnParseComplete(OptionContext c)
Definition: Options.cs:1396
override void RemoveItem(int index)
Definition: Options.cs:1084
OptionSet(MessageLocalizerConverter localizer)
Definition: Options.cs:792
override string GetKeyForItem(Option item)
Definition: Options.cs:995
OptionSet Add(string prototype, string description, Action< string > action)
Definition: Options.cs:831
ActionOption(string prototype, string description, OptionAction< TKey, TValue > action)
Definition: Options.cs:1441
readonly OptionContext c
Definition: Options.cs:258
OptionValueCollection(OptionContext c)
Definition: Options.cs:253
override bool GetArguments(string value, out IEnumerable< string > replacement)
Definition: Options.cs:730
delegate void OptionAction< in TKey, in TValue >(TKey key, TValue value)
OptionValueCollection OptionValues
Definition: Options.cs:440
override void InsertItem(int index, Option item)
Definition: Options.cs:1043
MessageLocalizerConverter MessageLocalizer
Definition: Options.cs:807
List< string > Parse(IEnumerable< string > arguments)
Definition: Options.cs:894
void Add(IEnumerable< string > arguments)
Definition: Options.cs:1488
static bool IsEolChar(char c)
Definition: Options.cs:245
readonly Action< T > action
Definition: Options.cs:1431
function s(a)
Definition: jquery.min.js:2
OptionSet Add(string prototype, OptionAction< string, string > action)
Definition: Options.cs:846
static IEnumerable< string > WrappedLines(string self, params int[] widths)
Definition: Options.cs:166
static IEnumerable< string > CreateWrappedLinesIterator(string self, IEnumerable< int > widths)
Definition: Options.cs:179
OptionException(SerializationInfo info, StreamingContext context)
Definition: Options.cs:766
void ParseValue(string option, OptionContext c)
Definition: Options.cs:1284