PageRenderTime 71ms CodeModel.GetById 35ms RepoModel.GetById 1ms app.codeStats 0ms

/Options.cs

https://bitbucket.org/peterk87/mist
C# | 1103 lines | 861 code | 114 blank | 128 comment | 209 complexity | 4dc01a58dec9c9082646f7d76ef2ba6a MD5 | raw file
  1. //
  2. // Options.cs
  3. //
  4. // Authors:
  5. // Jonathan Pryor <jpryor@novell.com>
  6. //
  7. // Copyright (C) 2008 Novell (http://www.novell.com)
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining
  10. // a copy of this software and associated documentation files (the
  11. // "Software"), to deal in the Software without restriction, including
  12. // without limitation the rights to use, copy, modify, merge, publish,
  13. // distribute, sublicense, and/or sell copies of the Software, and to
  14. // permit persons to whom the Software is furnished to do so, subject to
  15. // the following conditions:
  16. //
  17. // The above copyright notice and this permission notice shall be
  18. // included in all copies or substantial portions of the Software.
  19. //
  20. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  21. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  22. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  23. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  24. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  25. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  26. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  27. //
  28. // Compile With:
  29. // gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll
  30. // gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll
  31. //
  32. // The LINQ version just changes the implementation of
  33. // OptionSet.Parse(IEnumerable<string>), and confers no semantic changes.
  34. //
  35. // A Getopt::Long-inspired option parsing library for C#.
  36. //
  37. // NDesk.Options.OptionSet is built upon a key/value table, where the
  38. // key is a option format string and the value is a delegate that is
  39. // invoked when the format string is matched.
  40. //
  41. // Option format strings:
  42. // Regex-like BNF Grammar:
  43. // name: .+
  44. // type: [=:]
  45. // sep: ( [^{}]+ | '{' .+ '}' )?
  46. // aliases: ( name type sep ) ( '|' name type sep )*
  47. //
  48. // Each '|'-delimited name is an alias for the associated action. If the
  49. // format string ends in a '=', it has a required value. If the format
  50. // string ends in a ':', it has an optional value. If neither '=' or ':'
  51. // is present, no value is supported. `=' or `:' need only be defined on one
  52. // alias, but if they are provided on more than one they must be consistent.
  53. //
  54. // Each alias portion may also end with a "key/value separator", which is used
  55. // to split option values if the option accepts > 1 value. If not specified,
  56. // it defaults to '=' and ':'. If specified, it can be any character except
  57. // '{' and '}' OR the *string* between '{' and '}'. If no separator should be
  58. // used (i.e. the separate values should be distinct arguments), then "{}"
  59. // should be used as the separator.
  60. //
  61. // Options are extracted either from the current option by looking for
  62. // the option name followed by an '=' or ':', or is taken from the
  63. // following option IFF:
  64. // - The current option does not contain a '=' or a ':'
  65. // - The current option requires a value (i.e. not a Option type of ':')
  66. //
  67. // The `name' used in the option format string does NOT include any leading
  68. // option indicator, such as '-', '--', or '/'. All three of these are
  69. // permitted/required on any named option.
  70. //
  71. // Option bundling is permitted so long as:
  72. // - '-' is used to start the option group
  73. // - all of the bundled options are a single character
  74. // - at most one of the bundled options accepts a value, and the value
  75. // provided starts from the next character to the end of the string.
  76. //
  77. // This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value'
  78. // as '-Dname=value'.
  79. //
  80. // Option processing is disabled by specifying "--". All options after "--"
  81. // are returned by OptionSet.Parse() unchanged and unprocessed.
  82. //
  83. // Unprocessed options are returned from OptionSet.Parse().
  84. //
  85. // Examples:
  86. // int verbose = 0;
  87. // OptionSet p = new OptionSet ()
  88. // .Add ("v", v => ++verbose)
  89. // .Add ("name=|value=", v => Console.WriteLine (v));
  90. // p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"});
  91. //
  92. // The above would parse the argument string array, and would invoke the
  93. // lambda expression three times, setting `verbose' to 3 when complete.
  94. // It would also print out "A" and "B" to standard output.
  95. // The returned array would contain the string "extra".
  96. //
  97. // C# 3.0 collection initializers are supported and encouraged:
  98. // var p = new OptionSet () {
  99. // { "h|?|help", v => ShowHelp () },
  100. // };
  101. //
  102. // System.ComponentModel.TypeConverter is also supported, allowing the use of
  103. // custom data types in the callback type; TypeConverter.ConvertFromString()
  104. // is used to convert the value option to an instance of the specified
  105. // type:
  106. //
  107. // var p = new OptionSet () {
  108. // { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) },
  109. // };
  110. //
  111. // Random other tidbits:
  112. // - Boolean options (those w/o '=' or ':' in the option format string)
  113. // are explicitly enabled if they are followed with '+', and explicitly
  114. // disabled if they are followed with '-':
  115. // string a = null;
  116. // var p = new OptionSet () {
  117. // { "a", s => a = s },
  118. // };
  119. // p.Parse (new string[]{"-a"}); // sets v != null
  120. // p.Parse (new string[]{"-a+"}); // sets v != null
  121. // p.Parse (new string[]{"-a-"}); // sets v == null
  122. //
  123. using System;
  124. using System.Collections;
  125. using System.Collections.Generic;
  126. using System.Collections.ObjectModel;
  127. using System.ComponentModel;
  128. using System.Globalization;
  129. using System.IO;
  130. using System.Runtime.Serialization;
  131. using System.Security.Permissions;
  132. using System.Text;
  133. using System.Text.RegularExpressions;
  134. #if LINQ
  135. using System.Linq;
  136. #endif
  137. #if TEST
  138. using NDesk.Options;
  139. #endif
  140. namespace NDesk.Options {
  141. public class OptionValueCollection : IList, IList<string> {
  142. List<string> values = new List<string> ();
  143. OptionContext c;
  144. internal OptionValueCollection (OptionContext c)
  145. {
  146. this.c = c;
  147. }
  148. #region ICollection
  149. void ICollection.CopyTo (Array array, int index) {(values as ICollection).CopyTo (array, index);}
  150. bool ICollection.IsSynchronized {get {return (values as ICollection).IsSynchronized;}}
  151. object ICollection.SyncRoot {get {return (values as ICollection).SyncRoot;}}
  152. #endregion
  153. #region ICollection<T>
  154. public void Add (string item) {values.Add (item);}
  155. public void Clear () {values.Clear ();}
  156. public bool Contains (string item) {return values.Contains (item);}
  157. public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);}
  158. public bool Remove (string item) {return values.Remove (item);}
  159. public int Count {get {return values.Count;}}
  160. public bool IsReadOnly {get {return false;}}
  161. #endregion
  162. #region IEnumerable
  163. IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();}
  164. #endregion
  165. #region IEnumerable<T>
  166. public IEnumerator<string> GetEnumerator () {return values.GetEnumerator ();}
  167. #endregion
  168. #region IList
  169. int IList.Add (object value) {return (values as IList).Add (value);}
  170. bool IList.Contains (object value) {return (values as IList).Contains (value);}
  171. int IList.IndexOf (object value) {return (values as IList).IndexOf (value);}
  172. void IList.Insert (int index, object value) {(values as IList).Insert (index, value);}
  173. void IList.Remove (object value) {(values as IList).Remove (value);}
  174. void IList.RemoveAt (int index) {(values as IList).RemoveAt (index);}
  175. bool IList.IsFixedSize {get {return false;}}
  176. object IList.this [int index] {get {return this [index];} set {(values as IList)[index] = value;}}
  177. #endregion
  178. #region IList<T>
  179. public int IndexOf (string item) {return values.IndexOf (item);}
  180. public void Insert (int index, string item) {values.Insert (index, item);}
  181. public void RemoveAt (int index) {values.RemoveAt (index);}
  182. private void AssertValid (int index)
  183. {
  184. if (c.Option == null)
  185. throw new InvalidOperationException ("OptionContext.Option is null.");
  186. if (index >= c.Option.MaxValueCount)
  187. throw new ArgumentOutOfRangeException ("index");
  188. if (c.Option.OptionValueType == OptionValueType.Required &&
  189. index >= values.Count)
  190. throw new OptionException (string.Format (
  191. c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName),
  192. c.OptionName);
  193. }
  194. public string this [int index] {
  195. get {
  196. AssertValid (index);
  197. return index >= values.Count ? null : values [index];
  198. }
  199. set {
  200. values [index] = value;
  201. }
  202. }
  203. #endregion
  204. public List<string> ToList ()
  205. {
  206. return new List<string> (values);
  207. }
  208. public string[] ToArray ()
  209. {
  210. return values.ToArray ();
  211. }
  212. public override string ToString ()
  213. {
  214. return string.Join (", ", values.ToArray ());
  215. }
  216. }
  217. public class OptionContext {
  218. private Option option;
  219. private string name;
  220. private int index;
  221. private OptionSet set;
  222. private OptionValueCollection c;
  223. public OptionContext (OptionSet set)
  224. {
  225. this.set = set;
  226. this.c = new OptionValueCollection (this);
  227. }
  228. public Option Option {
  229. get {return option;}
  230. set {option = value;}
  231. }
  232. public string OptionName {
  233. get {return name;}
  234. set {name = value;}
  235. }
  236. public int OptionIndex {
  237. get {return index;}
  238. set {index = value;}
  239. }
  240. public OptionSet OptionSet {
  241. get {return set;}
  242. }
  243. public OptionValueCollection OptionValues {
  244. get {return c;}
  245. }
  246. }
  247. public enum OptionValueType {
  248. None,
  249. Optional,
  250. Required,
  251. }
  252. public abstract class Option {
  253. string prototype, description;
  254. string[] names;
  255. OptionValueType type;
  256. int count;
  257. string[] separators;
  258. protected Option (string prototype, string description)
  259. : this (prototype, description, 1)
  260. {
  261. }
  262. protected Option (string prototype, string description, int maxValueCount)
  263. {
  264. if (prototype == null)
  265. throw new ArgumentNullException ("prototype");
  266. if (prototype.Length == 0)
  267. throw new ArgumentException ("Cannot be the empty string.", "prototype");
  268. if (maxValueCount < 0)
  269. throw new ArgumentOutOfRangeException ("maxValueCount");
  270. this.prototype = prototype;
  271. this.names = prototype.Split ('|');
  272. this.description = description;
  273. this.count = maxValueCount;
  274. this.type = ParsePrototype ();
  275. if (this.count == 0 && type != OptionValueType.None)
  276. throw new ArgumentException (
  277. "Cannot provide maxValueCount of 0 for OptionValueType.Required or " +
  278. "OptionValueType.Optional.",
  279. "maxValueCount");
  280. if (this.type == OptionValueType.None && maxValueCount > 1)
  281. throw new ArgumentException (
  282. string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount),
  283. "maxValueCount");
  284. if (Array.IndexOf (names, "<>") >= 0 &&
  285. ((names.Length == 1 && this.type != OptionValueType.None) ||
  286. (names.Length > 1 && this.MaxValueCount > 1)))
  287. throw new ArgumentException (
  288. "The default option handler '<>' cannot require values.",
  289. "prototype");
  290. }
  291. public string Prototype {get {return prototype;}}
  292. public string Description {get {return description;}}
  293. public OptionValueType OptionValueType {get {return type;}}
  294. public int MaxValueCount {get {return count;}}
  295. public string[] GetNames ()
  296. {
  297. return (string[]) names.Clone ();
  298. }
  299. public string[] GetValueSeparators ()
  300. {
  301. if (separators == null)
  302. return new string [0];
  303. return (string[]) separators.Clone ();
  304. }
  305. protected static T Parse<T> (string value, OptionContext c)
  306. {
  307. TypeConverter conv = TypeDescriptor.GetConverter (typeof (T));
  308. T t = default (T);
  309. try {
  310. if (value != null)
  311. t = (T) conv.ConvertFromString (value);
  312. }
  313. catch (Exception e) {
  314. throw new OptionException (
  315. string.Format (
  316. c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."),
  317. value, typeof (T).Name, c.OptionName),
  318. c.OptionName, e);
  319. }
  320. return t;
  321. }
  322. internal string[] Names {get {return names;}}
  323. internal string[] ValueSeparators {get {return separators;}}
  324. static readonly char[] NameTerminator = new char[]{'=', ':'};
  325. private OptionValueType ParsePrototype ()
  326. {
  327. char type = '\0';
  328. List<string> seps = new List<string> ();
  329. for (int i = 0; i < names.Length; ++i) {
  330. string name = names [i];
  331. if (name.Length == 0)
  332. throw new ArgumentException ("Empty option names are not supported.", "prototype");
  333. int end = name.IndexOfAny (NameTerminator);
  334. if (end == -1)
  335. continue;
  336. names [i] = name.Substring (0, end);
  337. if (type == '\0' || type == name [end])
  338. type = name [end];
  339. else
  340. throw new ArgumentException (
  341. string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]),
  342. "prototype");
  343. AddSeparators (name, end, seps);
  344. }
  345. if (type == '\0')
  346. return OptionValueType.None;
  347. if (count <= 1 && seps.Count != 0)
  348. throw new ArgumentException (
  349. string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count),
  350. "prototype");
  351. if (count > 1) {
  352. if (seps.Count == 0)
  353. this.separators = new string[]{":", "="};
  354. else if (seps.Count == 1 && seps [0].Length == 0)
  355. this.separators = null;
  356. else
  357. this.separators = seps.ToArray ();
  358. }
  359. return type == '=' ? OptionValueType.Required : OptionValueType.Optional;
  360. }
  361. private static void AddSeparators (string name, int end, ICollection<string> seps)
  362. {
  363. int start = -1;
  364. for (int i = end+1; i < name.Length; ++i) {
  365. switch (name [i]) {
  366. case '{':
  367. if (start != -1)
  368. throw new ArgumentException (
  369. string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
  370. "prototype");
  371. start = i+1;
  372. break;
  373. case '}':
  374. if (start == -1)
  375. throw new ArgumentException (
  376. string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
  377. "prototype");
  378. seps.Add (name.Substring (start, i-start));
  379. start = -1;
  380. break;
  381. default:
  382. if (start == -1)
  383. seps.Add (name [i].ToString ());
  384. break;
  385. }
  386. }
  387. if (start != -1)
  388. throw new ArgumentException (
  389. string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
  390. "prototype");
  391. }
  392. public void Invoke (OptionContext c)
  393. {
  394. OnParseComplete (c);
  395. c.OptionName = null;
  396. c.Option = null;
  397. c.OptionValues.Clear ();
  398. }
  399. protected abstract void OnParseComplete (OptionContext c);
  400. public override string ToString ()
  401. {
  402. return Prototype;
  403. }
  404. }
  405. [Serializable]
  406. public class OptionException : Exception {
  407. private string option;
  408. public OptionException ()
  409. {
  410. }
  411. public OptionException (string message, string optionName)
  412. : base (message)
  413. {
  414. this.option = optionName;
  415. }
  416. public OptionException (string message, string optionName, Exception innerException)
  417. : base (message, innerException)
  418. {
  419. this.option = optionName;
  420. }
  421. protected OptionException (SerializationInfo info, StreamingContext context)
  422. : base (info, context)
  423. {
  424. this.option = info.GetString ("OptionName");
  425. }
  426. public string OptionName {
  427. get {return this.option;}
  428. }
  429. [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
  430. public override void GetObjectData (SerializationInfo info, StreamingContext context)
  431. {
  432. base.GetObjectData (info, context);
  433. info.AddValue ("OptionName", option);
  434. }
  435. }
  436. public delegate void OptionAction<TKey, TValue> (TKey key, TValue value);
  437. public class OptionSet : KeyedCollection<string, Option>
  438. {
  439. public OptionSet ()
  440. : this (delegate (string f) {return f;})
  441. {
  442. }
  443. public OptionSet (Converter<string, string> localizer)
  444. {
  445. this.localizer = localizer;
  446. }
  447. Converter<string, string> localizer;
  448. public Converter<string, string> MessageLocalizer {
  449. get {return localizer;}
  450. }
  451. protected override string GetKeyForItem (Option item)
  452. {
  453. if (item == null)
  454. throw new ArgumentNullException ("option");
  455. if (item.Names != null && item.Names.Length > 0)
  456. return item.Names [0];
  457. // This should never happen, as it's invalid for Option to be
  458. // constructed w/o any names.
  459. throw new InvalidOperationException ("Option has no names!");
  460. }
  461. [Obsolete ("Use KeyedCollection.this[string]")]
  462. protected Option GetOptionForName (string option)
  463. {
  464. if (option == null)
  465. throw new ArgumentNullException ("option");
  466. try {
  467. return base [option];
  468. }
  469. catch (KeyNotFoundException) {
  470. return null;
  471. }
  472. }
  473. protected override void InsertItem (int index, Option item)
  474. {
  475. base.InsertItem (index, item);
  476. AddImpl (item);
  477. }
  478. protected override void RemoveItem (int index)
  479. {
  480. base.RemoveItem (index);
  481. Option p = Items [index];
  482. // KeyedCollection.RemoveItem() handles the 0th item
  483. for (int i = 1; i < p.Names.Length; ++i) {
  484. Dictionary.Remove (p.Names [i]);
  485. }
  486. }
  487. protected override void SetItem (int index, Option item)
  488. {
  489. base.SetItem (index, item);
  490. RemoveItem (index);
  491. AddImpl (item);
  492. }
  493. private void AddImpl (Option option)
  494. {
  495. if (option == null)
  496. throw new ArgumentNullException ("option");
  497. List<string> added = new List<string> (option.Names.Length);
  498. try {
  499. // KeyedCollection.InsertItem/SetItem handle the 0th name.
  500. for (int i = 1; i < option.Names.Length; ++i) {
  501. Dictionary.Add (option.Names [i], option);
  502. added.Add (option.Names [i]);
  503. }
  504. }
  505. catch (Exception) {
  506. foreach (string name in added)
  507. Dictionary.Remove (name);
  508. throw;
  509. }
  510. }
  511. public new OptionSet Add (Option option)
  512. {
  513. base.Add (option);
  514. return this;
  515. }
  516. sealed class ActionOption : Option {
  517. Action<OptionValueCollection> action;
  518. public ActionOption (string prototype, string description, int count, Action<OptionValueCollection> action)
  519. : base (prototype, description, count)
  520. {
  521. if (action == null)
  522. throw new ArgumentNullException ("action");
  523. this.action = action;
  524. }
  525. protected override void OnParseComplete (OptionContext c)
  526. {
  527. action (c.OptionValues);
  528. }
  529. }
  530. public OptionSet Add (string prototype, Action<string> action)
  531. {
  532. return Add (prototype, null, action);
  533. }
  534. public OptionSet Add (string prototype, string description, Action<string> action)
  535. {
  536. if (action == null)
  537. throw new ArgumentNullException ("action");
  538. Option p = new ActionOption (prototype, description, 1,
  539. delegate (OptionValueCollection v) { action (v [0]); });
  540. base.Add (p);
  541. return this;
  542. }
  543. public OptionSet Add (string prototype, OptionAction<string, string> action)
  544. {
  545. return Add (prototype, null, action);
  546. }
  547. public OptionSet Add (string prototype, string description, OptionAction<string, string> action)
  548. {
  549. if (action == null)
  550. throw new ArgumentNullException ("action");
  551. Option p = new ActionOption (prototype, description, 2,
  552. delegate (OptionValueCollection v) {action (v [0], v [1]);});
  553. base.Add (p);
  554. return this;
  555. }
  556. sealed class ActionOption<T> : Option {
  557. Action<T> action;
  558. public ActionOption (string prototype, string description, Action<T> action)
  559. : base (prototype, description, 1)
  560. {
  561. if (action == null)
  562. throw new ArgumentNullException ("action");
  563. this.action = action;
  564. }
  565. protected override void OnParseComplete (OptionContext c)
  566. {
  567. action (Parse<T> (c.OptionValues [0], c));
  568. }
  569. }
  570. sealed class ActionOption<TKey, TValue> : Option {
  571. OptionAction<TKey, TValue> action;
  572. public ActionOption (string prototype, string description, OptionAction<TKey, TValue> action)
  573. : base (prototype, description, 2)
  574. {
  575. if (action == null)
  576. throw new ArgumentNullException ("action");
  577. this.action = action;
  578. }
  579. protected override void OnParseComplete (OptionContext c)
  580. {
  581. action (
  582. Parse<TKey> (c.OptionValues [0], c),
  583. Parse<TValue> (c.OptionValues [1], c));
  584. }
  585. }
  586. public OptionSet Add<T> (string prototype, Action<T> action)
  587. {
  588. return Add (prototype, null, action);
  589. }
  590. public OptionSet Add<T> (string prototype, string description, Action<T> action)
  591. {
  592. return Add (new ActionOption<T> (prototype, description, action));
  593. }
  594. public OptionSet Add<TKey, TValue> (string prototype, OptionAction<TKey, TValue> action)
  595. {
  596. return Add (prototype, null, action);
  597. }
  598. public OptionSet Add<TKey, TValue> (string prototype, string description, OptionAction<TKey, TValue> action)
  599. {
  600. return Add (new ActionOption<TKey, TValue> (prototype, description, action));
  601. }
  602. protected virtual OptionContext CreateOptionContext ()
  603. {
  604. return new OptionContext (this);
  605. }
  606. #if LINQ
  607. public List<string> Parse (IEnumerable<string> arguments)
  608. {
  609. bool process = true;
  610. OptionContext c = CreateOptionContext ();
  611. c.OptionIndex = -1;
  612. var def = GetOptionForName ("<>");
  613. var unprocessed =
  614. from argument in arguments
  615. where ++c.OptionIndex >= 0 && (process || def != null)
  616. ? process
  617. ? argument == "--"
  618. ? (process = false)
  619. : !Parse (argument, c)
  620. ? def != null
  621. ? Unprocessed (null, def, c, argument)
  622. : true
  623. : false
  624. : def != null
  625. ? Unprocessed (null, def, c, argument)
  626. : true
  627. : true
  628. select argument;
  629. List<string> r = unprocessed.ToList ();
  630. if (c.Option != null)
  631. c.Option.Invoke (c);
  632. return r;
  633. }
  634. #else
  635. public List<string> Parse (IEnumerable<string> arguments)
  636. {
  637. OptionContext c = CreateOptionContext ();
  638. c.OptionIndex = -1;
  639. bool process = true;
  640. List<string> unprocessed = new List<string> ();
  641. Option def = Contains ("<>") ? this ["<>"] : null;
  642. foreach (string argument in arguments) {
  643. ++c.OptionIndex;
  644. if (argument == "--") {
  645. process = false;
  646. continue;
  647. }
  648. if (!process) {
  649. Unprocessed (unprocessed, def, c, argument);
  650. continue;
  651. }
  652. if (!Parse (argument, c))
  653. Unprocessed (unprocessed, def, c, argument);
  654. }
  655. if (c.Option != null)
  656. c.Option.Invoke (c);
  657. return unprocessed;
  658. }
  659. #endif
  660. private static bool Unprocessed (ICollection<string> extra, Option def, OptionContext c, string argument)
  661. {
  662. if (def == null) {
  663. extra.Add (argument);
  664. return false;
  665. }
  666. c.OptionValues.Add (argument);
  667. c.Option = def;
  668. c.Option.Invoke (c);
  669. return false;
  670. }
  671. private readonly Regex ValueOption = new Regex (
  672. @"^(?<flag>--|-|/)(?<name>[^:=]+)((?<sep>[:=])(?<value>.*))?$");
  673. protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value)
  674. {
  675. if (argument == null)
  676. throw new ArgumentNullException ("argument");
  677. flag = name = sep = value = null;
  678. Match m = ValueOption.Match (argument);
  679. if (!m.Success) {
  680. return false;
  681. }
  682. flag = m.Groups ["flag"].Value;
  683. name = m.Groups ["name"].Value;
  684. if (m.Groups ["sep"].Success && m.Groups ["value"].Success) {
  685. sep = m.Groups ["sep"].Value;
  686. value = m.Groups ["value"].Value;
  687. }
  688. return true;
  689. }
  690. protected virtual bool Parse (string argument, OptionContext c)
  691. {
  692. if (c.Option != null) {
  693. ParseValue (argument, c);
  694. return true;
  695. }
  696. string f, n, s, v;
  697. if (!GetOptionParts (argument, out f, out n, out s, out v))
  698. return false;
  699. Option p;
  700. if (Contains (n)) {
  701. p = this [n];
  702. c.OptionName = f + n;
  703. c.Option = p;
  704. switch (p.OptionValueType) {
  705. case OptionValueType.None:
  706. c.OptionValues.Add (n);
  707. c.Option.Invoke (c);
  708. break;
  709. case OptionValueType.Optional:
  710. case OptionValueType.Required:
  711. ParseValue (v, c);
  712. break;
  713. }
  714. return true;
  715. }
  716. // no match; is it a bool option?
  717. if (ParseBool (argument, n, c))
  718. return true;
  719. // is it a bundled option?
  720. if (ParseBundledValue (f, string.Concat (n + s + v), c))
  721. return true;
  722. return false;
  723. }
  724. private void ParseValue (string option, OptionContext c)
  725. {
  726. if (option != null)
  727. foreach (string o in c.Option.ValueSeparators != null
  728. ? option.Split (c.Option.ValueSeparators, StringSplitOptions.None)
  729. : new string[]{option}) {
  730. c.OptionValues.Add (o);
  731. }
  732. if (c.OptionValues.Count == c.Option.MaxValueCount ||
  733. c.Option.OptionValueType == OptionValueType.Optional)
  734. c.Option.Invoke (c);
  735. else if (c.OptionValues.Count > c.Option.MaxValueCount) {
  736. throw new OptionException (localizer (string.Format (
  737. "Error: Found {0} option values when expecting {1}.",
  738. c.OptionValues.Count, c.Option.MaxValueCount)),
  739. c.OptionName);
  740. }
  741. }
  742. private bool ParseBool (string option, string n, OptionContext c)
  743. {
  744. Option p;
  745. string rn;
  746. if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') &&
  747. Contains ((rn = n.Substring (0, n.Length-1)))) {
  748. p = this [rn];
  749. string v = n [n.Length-1] == '+' ? option : null;
  750. c.OptionName = option;
  751. c.Option = p;
  752. c.OptionValues.Add (v);
  753. p.Invoke (c);
  754. return true;
  755. }
  756. return false;
  757. }
  758. private bool ParseBundledValue (string f, string n, OptionContext c)
  759. {
  760. if (f != "-")
  761. return false;
  762. for (int i = 0; i < n.Length; ++i) {
  763. Option p;
  764. string opt = f + n [i].ToString ();
  765. string rn = n [i].ToString ();
  766. if (!Contains (rn)) {
  767. if (i == 0)
  768. return false;
  769. throw new OptionException (string.Format (localizer (
  770. "Cannot bundle unregistered option '{0}'."), opt), opt);
  771. }
  772. p = this [rn];
  773. switch (p.OptionValueType) {
  774. case OptionValueType.None:
  775. Invoke (c, opt, n, p);
  776. break;
  777. case OptionValueType.Optional:
  778. case OptionValueType.Required: {
  779. string v = n.Substring (i+1);
  780. c.Option = p;
  781. c.OptionName = opt;
  782. ParseValue (v.Length != 0 ? v : null, c);
  783. return true;
  784. }
  785. default:
  786. throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType);
  787. }
  788. }
  789. return true;
  790. }
  791. private static void Invoke (OptionContext c, string name, string value, Option option)
  792. {
  793. c.OptionName = name;
  794. c.Option = option;
  795. c.OptionValues.Add (value);
  796. option.Invoke (c);
  797. }
  798. private const int OptionWidth = 29;
  799. public void WriteOptionDescriptions (TextWriter o)
  800. {
  801. foreach (Option p in this) {
  802. int written = 0;
  803. if (!WriteOptionPrototype (o, p, ref written))
  804. continue;
  805. if (written < OptionWidth)
  806. o.Write (new string (' ', OptionWidth - written));
  807. else {
  808. o.WriteLine ();
  809. o.Write (new string (' ', OptionWidth));
  810. }
  811. List<string> lines = GetLines (localizer (GetDescription (p.Description)));
  812. o.WriteLine (lines [0]);
  813. string prefix = new string (' ', OptionWidth+2);
  814. for (int i = 1; i < lines.Count; ++i) {
  815. o.Write (prefix);
  816. o.WriteLine (lines [i]);
  817. }
  818. }
  819. }
  820. bool WriteOptionPrototype (TextWriter o, Option p, ref int written)
  821. {
  822. string[] names = p.Names;
  823. int i = GetNextOptionIndex (names, 0);
  824. if (i == names.Length)
  825. return false;
  826. if (names [i].Length == 1) {
  827. Write (o, ref written, " -");
  828. Write (o, ref written, names [0]);
  829. }
  830. else {
  831. Write (o, ref written, " --");
  832. Write (o, ref written, names [0]);
  833. }
  834. for ( i = GetNextOptionIndex (names, i+1);
  835. i < names.Length; i = GetNextOptionIndex (names, i+1)) {
  836. Write (o, ref written, ", ");
  837. Write (o, ref written, names [i].Length == 1 ? "-" : "--");
  838. Write (o, ref written, names [i]);
  839. }
  840. if (p.OptionValueType == OptionValueType.Optional ||
  841. p.OptionValueType == OptionValueType.Required) {
  842. if (p.OptionValueType == OptionValueType.Optional) {
  843. Write (o, ref written, localizer ("["));
  844. }
  845. Write (o, ref written, localizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description)));
  846. string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0
  847. ? p.ValueSeparators [0]
  848. : " ";
  849. for (int c = 1; c < p.MaxValueCount; ++c) {
  850. Write (o, ref written, localizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description)));
  851. }
  852. if (p.OptionValueType == OptionValueType.Optional) {
  853. Write (o, ref written, localizer ("]"));
  854. }
  855. }
  856. return true;
  857. }
  858. static int GetNextOptionIndex (string[] names, int i)
  859. {
  860. while (i < names.Length && names [i] == "<>") {
  861. ++i;
  862. }
  863. return i;
  864. }
  865. static void Write (TextWriter o, ref int n, string s)
  866. {
  867. n += s.Length;
  868. o.Write (s);
  869. }
  870. private static string GetArgumentName (int index, int maxIndex, string description)
  871. {
  872. if (description == null)
  873. return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
  874. string[] nameStart;
  875. if (maxIndex == 1)
  876. nameStart = new string[]{"{0:", "{"};
  877. else
  878. nameStart = new string[]{"{" + index + ":"};
  879. for (int i = 0; i < nameStart.Length; ++i) {
  880. int start, j = 0;
  881. do {
  882. start = description.IndexOf (nameStart [i], j);
  883. } while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false);
  884. if (start == -1)
  885. continue;
  886. int end = description.IndexOf ("}", start);
  887. if (end == -1)
  888. continue;
  889. return description.Substring (start + nameStart [i].Length, end - start - nameStart [i].Length);
  890. }
  891. return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
  892. }
  893. private static string GetDescription (string description)
  894. {
  895. if (description == null)
  896. return string.Empty;
  897. StringBuilder sb = new StringBuilder (description.Length);
  898. int start = -1;
  899. for (int i = 0; i < description.Length; ++i) {
  900. switch (description [i]) {
  901. case '{':
  902. if (i == start) {
  903. sb.Append ('{');
  904. start = -1;
  905. }
  906. else if (start < 0)
  907. start = i + 1;
  908. break;
  909. case '}':
  910. if (start < 0) {
  911. if ((i+1) == description.Length || description [i+1] != '}')
  912. throw new InvalidOperationException ("Invalid option description: " + description);
  913. ++i;
  914. sb.Append ("}");
  915. }
  916. else {
  917. sb.Append (description.Substring (start, i - start));
  918. start = -1;
  919. }
  920. break;
  921. case ':':
  922. if (start < 0)
  923. goto default;
  924. start = i + 1;
  925. break;
  926. default:
  927. if (start < 0)
  928. sb.Append (description [i]);
  929. break;
  930. }
  931. }
  932. return sb.ToString ();
  933. }
  934. private static List<string> GetLines (string description)
  935. {
  936. List<string> lines = new List<string> ();
  937. if (string.IsNullOrEmpty (description)) {
  938. lines.Add (string.Empty);
  939. return lines;
  940. }
  941. int length = 80 - OptionWidth - 2;
  942. int start = 0, end;
  943. do {
  944. end = GetLineEnd (start, length, description);
  945. bool cont = false;
  946. if (end < description.Length) {
  947. char c = description [end];
  948. if (c == '-' || (char.IsWhiteSpace (c) && c != '\n'))
  949. ++end;
  950. else if (c != '\n') {
  951. cont = true;
  952. --end;
  953. }
  954. }
  955. lines.Add (description.Substring (start, end - start));
  956. if (cont) {
  957. lines [lines.Count-1] += "-";
  958. }
  959. start = end;
  960. if (start < description.Length && description [start] == '\n')
  961. ++start;
  962. } while (end < description.Length);
  963. return lines;
  964. }
  965. private static int GetLineEnd (int start, int length, string description)
  966. {
  967. int end = Math.Min (start + length, description.Length);
  968. int sep = -1;
  969. for (int i = start; i < end; ++i) {
  970. switch (description [i]) {
  971. case ' ':
  972. case '\t':
  973. case '\v':
  974. case '-':
  975. case ',':
  976. case '.':
  977. case ';':
  978. sep = i;
  979. break;
  980. case '\n':
  981. return i;
  982. }
  983. }
  984. if (sep == -1 || end == description.Length)
  985. return end;
  986. return sep;
  987. }
  988. }
  989. }