PageRenderTime 55ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/mcs/tools/lc/Options.cs

https://bitbucket.org/danipen/mono
C# | 1112 lines | 871 code | 113 blank | 128 comment | 213 complexity | 261609f7c353df61072367e3ee1c81e4 MD5 | raw file
Possible License(s): Unlicense, Apache-2.0, LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
  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. #if NDESK_OPTIONS
  141. namespace NDesk.Options
  142. #else
  143. namespace Mono.Options
  144. #endif
  145. {
  146. public class OptionValueCollection : IList, IList<string> {
  147. List<string> values = new List<string> ();
  148. OptionContext c;
  149. internal OptionValueCollection (OptionContext c)
  150. {
  151. this.c = c;
  152. }
  153. #region ICollection
  154. void ICollection.CopyTo (Array array, int index) {(values as ICollection).CopyTo (array, index);}
  155. bool ICollection.IsSynchronized {get {return (values as ICollection).IsSynchronized;}}
  156. object ICollection.SyncRoot {get {return (values as ICollection).SyncRoot;}}
  157. #endregion
  158. #region ICollection<T>
  159. public void Add (string item) {values.Add (item);}
  160. public void Clear () {values.Clear ();}
  161. public bool Contains (string item) {return values.Contains (item);}
  162. public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);}
  163. public bool Remove (string item) {return values.Remove (item);}
  164. public int Count {get {return values.Count;}}
  165. public bool IsReadOnly {get {return false;}}
  166. #endregion
  167. #region IEnumerable
  168. IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();}
  169. #endregion
  170. #region IEnumerable<T>
  171. public IEnumerator<string> GetEnumerator () {return values.GetEnumerator ();}
  172. #endregion
  173. #region IList
  174. int IList.Add (object value) {return (values as IList).Add (value);}
  175. bool IList.Contains (object value) {return (values as IList).Contains (value);}
  176. int IList.IndexOf (object value) {return (values as IList).IndexOf (value);}
  177. void IList.Insert (int index, object value) {(values as IList).Insert (index, value);}
  178. void IList.Remove (object value) {(values as IList).Remove (value);}
  179. void IList.RemoveAt (int index) {(values as IList).RemoveAt (index);}
  180. bool IList.IsFixedSize {get {return false;}}
  181. object IList.this [int index] {get {return this [index];} set {(values as IList)[index] = value;}}
  182. #endregion
  183. #region IList<T>
  184. public int IndexOf (string item) {return values.IndexOf (item);}
  185. public void Insert (int index, string item) {values.Insert (index, item);}
  186. public void RemoveAt (int index) {values.RemoveAt (index);}
  187. private void AssertValid (int index)
  188. {
  189. if (c.Option == null)
  190. throw new InvalidOperationException ("OptionContext.Option is null.");
  191. if (index >= c.Option.MaxValueCount)
  192. throw new ArgumentOutOfRangeException ("index");
  193. if (c.Option.OptionValueType == OptionValueType.Required &&
  194. index >= values.Count)
  195. throw new OptionException (string.Format (
  196. c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName),
  197. c.OptionName);
  198. }
  199. public string this [int index] {
  200. get {
  201. AssertValid (index);
  202. return index >= values.Count ? null : values [index];
  203. }
  204. set {
  205. values [index] = value;
  206. }
  207. }
  208. #endregion
  209. public List<string> ToList ()
  210. {
  211. return new List<string> (values);
  212. }
  213. public string[] ToArray ()
  214. {
  215. return values.ToArray ();
  216. }
  217. public override string ToString ()
  218. {
  219. return string.Join (", ", values.ToArray ());
  220. }
  221. }
  222. public class OptionContext {
  223. private Option option;
  224. private string name;
  225. private int index;
  226. private OptionSet set;
  227. private OptionValueCollection c;
  228. public OptionContext (OptionSet set)
  229. {
  230. this.set = set;
  231. this.c = new OptionValueCollection (this);
  232. }
  233. public Option Option {
  234. get {return option;}
  235. set {option = value;}
  236. }
  237. public string OptionName {
  238. get {return name;}
  239. set {name = value;}
  240. }
  241. public int OptionIndex {
  242. get {return index;}
  243. set {index = value;}
  244. }
  245. public OptionSet OptionSet {
  246. get {return set;}
  247. }
  248. public OptionValueCollection OptionValues {
  249. get {return c;}
  250. }
  251. }
  252. public enum OptionValueType {
  253. None,
  254. Optional,
  255. Required,
  256. }
  257. public abstract class Option {
  258. string prototype, description;
  259. string[] names;
  260. OptionValueType type;
  261. int count;
  262. string[] separators;
  263. protected Option (string prototype, string description)
  264. : this (prototype, description, 1)
  265. {
  266. }
  267. protected Option (string prototype, string description, int maxValueCount)
  268. {
  269. if (prototype == null)
  270. throw new ArgumentNullException ("prototype");
  271. if (prototype.Length == 0)
  272. throw new ArgumentException ("Cannot be the empty string.", "prototype");
  273. if (maxValueCount < 0)
  274. throw new ArgumentOutOfRangeException ("maxValueCount");
  275. this.prototype = prototype;
  276. this.names = prototype.Split ('|');
  277. this.description = description;
  278. this.count = maxValueCount;
  279. this.type = ParsePrototype ();
  280. if (this.count == 0 && type != OptionValueType.None)
  281. throw new ArgumentException (
  282. "Cannot provide maxValueCount of 0 for OptionValueType.Required or " +
  283. "OptionValueType.Optional.",
  284. "maxValueCount");
  285. if (this.type == OptionValueType.None && maxValueCount > 1)
  286. throw new ArgumentException (
  287. string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount),
  288. "maxValueCount");
  289. if (Array.IndexOf (names, "<>") >= 0 &&
  290. ((names.Length == 1 && this.type != OptionValueType.None) ||
  291. (names.Length > 1 && this.MaxValueCount > 1)))
  292. throw new ArgumentException (
  293. "The default option handler '<>' cannot require values.",
  294. "prototype");
  295. }
  296. public string Prototype {get {return prototype;}}
  297. public string Description {get {return description;}}
  298. public OptionValueType OptionValueType {get {return type;}}
  299. public int MaxValueCount {get {return count;}}
  300. public string[] GetNames ()
  301. {
  302. return (string[]) names.Clone ();
  303. }
  304. public string[] GetValueSeparators ()
  305. {
  306. if (separators == null)
  307. return new string [0];
  308. return (string[]) separators.Clone ();
  309. }
  310. protected static T Parse<T> (string value, OptionContext c)
  311. {
  312. Type tt = typeof (T);
  313. bool nullable = tt.IsValueType && tt.IsGenericType &&
  314. !tt.IsGenericTypeDefinition &&
  315. tt.GetGenericTypeDefinition () == typeof (Nullable<>);
  316. Type targetType = nullable ? tt.GetGenericArguments () [0] : typeof (T);
  317. TypeConverter conv = TypeDescriptor.GetConverter (targetType);
  318. T t = default (T);
  319. try {
  320. if (value != null)
  321. t = (T) conv.ConvertFromString (value);
  322. }
  323. catch (Exception e) {
  324. throw new OptionException (
  325. string.Format (
  326. c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."),
  327. value, targetType.Name, c.OptionName),
  328. c.OptionName, e);
  329. }
  330. return t;
  331. }
  332. internal string[] Names {get {return names;}}
  333. internal string[] ValueSeparators {get {return separators;}}
  334. static readonly char[] NameTerminator = new char[]{'=', ':'};
  335. private OptionValueType ParsePrototype ()
  336. {
  337. char type = '\0';
  338. List<string> seps = new List<string> ();
  339. for (int i = 0; i < names.Length; ++i) {
  340. string name = names [i];
  341. if (name.Length == 0)
  342. throw new ArgumentException ("Empty option names are not supported.", "prototype");
  343. int end = name.IndexOfAny (NameTerminator);
  344. if (end == -1)
  345. continue;
  346. names [i] = name.Substring (0, end);
  347. if (type == '\0' || type == name [end])
  348. type = name [end];
  349. else
  350. throw new ArgumentException (
  351. string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]),
  352. "prototype");
  353. AddSeparators (name, end, seps);
  354. }
  355. if (type == '\0')
  356. return OptionValueType.None;
  357. if (count <= 1 && seps.Count != 0)
  358. throw new ArgumentException (
  359. string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count),
  360. "prototype");
  361. if (count > 1) {
  362. if (seps.Count == 0)
  363. this.separators = new string[]{":", "="};
  364. else if (seps.Count == 1 && seps [0].Length == 0)
  365. this.separators = null;
  366. else
  367. this.separators = seps.ToArray ();
  368. }
  369. return type == '=' ? OptionValueType.Required : OptionValueType.Optional;
  370. }
  371. private static void AddSeparators (string name, int end, ICollection<string> seps)
  372. {
  373. int start = -1;
  374. for (int i = end+1; i < name.Length; ++i) {
  375. switch (name [i]) {
  376. case '{':
  377. if (start != -1)
  378. throw new ArgumentException (
  379. string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
  380. "prototype");
  381. start = i+1;
  382. break;
  383. case '}':
  384. if (start == -1)
  385. throw new ArgumentException (
  386. string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
  387. "prototype");
  388. seps.Add (name.Substring (start, i-start));
  389. start = -1;
  390. break;
  391. default:
  392. if (start == -1)
  393. seps.Add (name [i].ToString ());
  394. break;
  395. }
  396. }
  397. if (start != -1)
  398. throw new ArgumentException (
  399. string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
  400. "prototype");
  401. }
  402. public void Invoke (OptionContext c)
  403. {
  404. OnParseComplete (c);
  405. c.OptionName = null;
  406. c.Option = null;
  407. c.OptionValues.Clear ();
  408. }
  409. protected abstract void OnParseComplete (OptionContext c);
  410. public override string ToString ()
  411. {
  412. return Prototype;
  413. }
  414. }
  415. [Serializable]
  416. public class OptionException : Exception {
  417. private string option;
  418. public OptionException ()
  419. {
  420. }
  421. public OptionException (string message, string optionName)
  422. : base (message)
  423. {
  424. this.option = optionName;
  425. }
  426. public OptionException (string message, string optionName, Exception innerException)
  427. : base (message, innerException)
  428. {
  429. this.option = optionName;
  430. }
  431. protected OptionException (SerializationInfo info, StreamingContext context)
  432. : base (info, context)
  433. {
  434. this.option = info.GetString ("OptionName");
  435. }
  436. public string OptionName {
  437. get {return this.option;}
  438. }
  439. [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
  440. public override void GetObjectData (SerializationInfo info, StreamingContext context)
  441. {
  442. base.GetObjectData (info, context);
  443. info.AddValue ("OptionName", option);
  444. }
  445. }
  446. public delegate void OptionAction<TKey, TValue> (TKey key, TValue value);
  447. public class OptionSet : KeyedCollection<string, Option>
  448. {
  449. public OptionSet ()
  450. : this (delegate (string f) {return f;})
  451. {
  452. }
  453. public OptionSet (Converter<string, string> localizer)
  454. {
  455. this.localizer = localizer;
  456. }
  457. Converter<string, string> localizer;
  458. public Converter<string, string> MessageLocalizer {
  459. get {return localizer;}
  460. }
  461. protected override string GetKeyForItem (Option item)
  462. {
  463. if (item == null)
  464. throw new ArgumentNullException ("option");
  465. if (item.Names != null && item.Names.Length > 0)
  466. return item.Names [0];
  467. // This should never happen, as it's invalid for Option to be
  468. // constructed w/o any names.
  469. throw new InvalidOperationException ("Option has no names!");
  470. }
  471. [Obsolete ("Use KeyedCollection.this[string]")]
  472. protected Option GetOptionForName (string option)
  473. {
  474. if (option == null)
  475. throw new ArgumentNullException ("option");
  476. try {
  477. return base [option];
  478. }
  479. catch (KeyNotFoundException) {
  480. return null;
  481. }
  482. }
  483. protected override void InsertItem (int index, Option item)
  484. {
  485. base.InsertItem (index, item);
  486. AddImpl (item);
  487. }
  488. protected override void RemoveItem (int index)
  489. {
  490. base.RemoveItem (index);
  491. Option p = Items [index];
  492. // KeyedCollection.RemoveItem() handles the 0th item
  493. for (int i = 1; i < p.Names.Length; ++i) {
  494. Dictionary.Remove (p.Names [i]);
  495. }
  496. }
  497. protected override void SetItem (int index, Option item)
  498. {
  499. base.SetItem (index, item);
  500. RemoveItem (index);
  501. AddImpl (item);
  502. }
  503. private void AddImpl (Option option)
  504. {
  505. if (option == null)
  506. throw new ArgumentNullException ("option");
  507. List<string> added = new List<string> (option.Names.Length);
  508. try {
  509. // KeyedCollection.InsertItem/SetItem handle the 0th name.
  510. for (int i = 1; i < option.Names.Length; ++i) {
  511. Dictionary.Add (option.Names [i], option);
  512. added.Add (option.Names [i]);
  513. }
  514. }
  515. catch (Exception) {
  516. foreach (string name in added)
  517. Dictionary.Remove (name);
  518. throw;
  519. }
  520. }
  521. public new OptionSet Add (Option option)
  522. {
  523. base.Add (option);
  524. return this;
  525. }
  526. sealed class ActionOption : Option {
  527. Action<OptionValueCollection> action;
  528. public ActionOption (string prototype, string description, int count, Action<OptionValueCollection> action)
  529. : base (prototype, description, count)
  530. {
  531. if (action == null)
  532. throw new ArgumentNullException ("action");
  533. this.action = action;
  534. }
  535. protected override void OnParseComplete (OptionContext c)
  536. {
  537. action (c.OptionValues);
  538. }
  539. }
  540. public OptionSet Add (string prototype, Action<string> action)
  541. {
  542. return Add (prototype, null, action);
  543. }
  544. public OptionSet Add (string prototype, string description, Action<string> action)
  545. {
  546. if (action == null)
  547. throw new ArgumentNullException ("action");
  548. Option p = new ActionOption (prototype, description, 1,
  549. delegate (OptionValueCollection v) { action (v [0]); });
  550. base.Add (p);
  551. return this;
  552. }
  553. public OptionSet Add (string prototype, OptionAction<string, string> action)
  554. {
  555. return Add (prototype, null, action);
  556. }
  557. public OptionSet Add (string prototype, string description, OptionAction<string, string> action)
  558. {
  559. if (action == null)
  560. throw new ArgumentNullException ("action");
  561. Option p = new ActionOption (prototype, description, 2,
  562. delegate (OptionValueCollection v) {action (v [0], v [1]);});
  563. base.Add (p);
  564. return this;
  565. }
  566. sealed class ActionOption<T> : Option {
  567. Action<T> action;
  568. public ActionOption (string prototype, string description, Action<T> action)
  569. : base (prototype, description, 1)
  570. {
  571. if (action == null)
  572. throw new ArgumentNullException ("action");
  573. this.action = action;
  574. }
  575. protected override void OnParseComplete (OptionContext c)
  576. {
  577. action (Parse<T> (c.OptionValues [0], c));
  578. }
  579. }
  580. sealed class ActionOption<TKey, TValue> : Option {
  581. OptionAction<TKey, TValue> action;
  582. public ActionOption (string prototype, string description, OptionAction<TKey, TValue> action)
  583. : base (prototype, description, 2)
  584. {
  585. if (action == null)
  586. throw new ArgumentNullException ("action");
  587. this.action = action;
  588. }
  589. protected override void OnParseComplete (OptionContext c)
  590. {
  591. action (
  592. Parse<TKey> (c.OptionValues [0], c),
  593. Parse<TValue> (c.OptionValues [1], c));
  594. }
  595. }
  596. public OptionSet Add<T> (string prototype, Action<T> action)
  597. {
  598. return Add (prototype, null, action);
  599. }
  600. public OptionSet Add<T> (string prototype, string description, Action<T> action)
  601. {
  602. return Add (new ActionOption<T> (prototype, description, action));
  603. }
  604. public OptionSet Add<TKey, TValue> (string prototype, OptionAction<TKey, TValue> action)
  605. {
  606. return Add (prototype, null, action);
  607. }
  608. public OptionSet Add<TKey, TValue> (string prototype, string description, OptionAction<TKey, TValue> action)
  609. {
  610. return Add (new ActionOption<TKey, TValue> (prototype, description, action));
  611. }
  612. protected virtual OptionContext CreateOptionContext ()
  613. {
  614. return new OptionContext (this);
  615. }
  616. #if LINQ
  617. public List<string> Parse (IEnumerable<string> arguments)
  618. {
  619. bool process = true;
  620. OptionContext c = CreateOptionContext ();
  621. c.OptionIndex = -1;
  622. var def = GetOptionForName ("<>");
  623. var unprocessed =
  624. from argument in arguments
  625. where ++c.OptionIndex >= 0 && (process || def != null)
  626. ? process
  627. ? argument == "--"
  628. ? (process = false)
  629. : !Parse (argument, c)
  630. ? def != null
  631. ? Unprocessed (null, def, c, argument)
  632. : true
  633. : false
  634. : def != null
  635. ? Unprocessed (null, def, c, argument)
  636. : true
  637. : true
  638. select argument;
  639. List<string> r = unprocessed.ToList ();
  640. if (c.Option != null)
  641. c.Option.Invoke (c);
  642. return r;
  643. }
  644. #else
  645. public List<string> Parse (IEnumerable<string> arguments)
  646. {
  647. OptionContext c = CreateOptionContext ();
  648. c.OptionIndex = -1;
  649. bool process = true;
  650. List<string> unprocessed = new List<string> ();
  651. Option def = Contains ("<>") ? this ["<>"] : null;
  652. foreach (string argument in arguments) {
  653. ++c.OptionIndex;
  654. if (argument == "--") {
  655. process = false;
  656. continue;
  657. }
  658. if (!process) {
  659. Unprocessed (unprocessed, def, c, argument);
  660. continue;
  661. }
  662. if (!Parse (argument, c))
  663. Unprocessed (unprocessed, def, c, argument);
  664. }
  665. if (c.Option != null)
  666. c.Option.Invoke (c);
  667. return unprocessed;
  668. }
  669. #endif
  670. private static bool Unprocessed (ICollection<string> extra, Option def, OptionContext c, string argument)
  671. {
  672. if (def == null) {
  673. extra.Add (argument);
  674. return false;
  675. }
  676. c.OptionValues.Add (argument);
  677. c.Option = def;
  678. c.Option.Invoke (c);
  679. return false;
  680. }
  681. private readonly Regex ValueOption = new Regex (
  682. @"^(?<flag>--|-|/)(?<name>[^:=]+)((?<sep>[:=])(?<value>.*))?$");
  683. protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value)
  684. {
  685. if (argument == null)
  686. throw new ArgumentNullException ("argument");
  687. flag = name = sep = value = null;
  688. Match m = ValueOption.Match (argument);
  689. if (!m.Success) {
  690. return false;
  691. }
  692. flag = m.Groups ["flag"].Value;
  693. name = m.Groups ["name"].Value;
  694. if (m.Groups ["sep"].Success && m.Groups ["value"].Success) {
  695. sep = m.Groups ["sep"].Value;
  696. value = m.Groups ["value"].Value;
  697. }
  698. return true;
  699. }
  700. protected virtual bool Parse (string argument, OptionContext c)
  701. {
  702. if (c.Option != null) {
  703. ParseValue (argument, c);
  704. return true;
  705. }
  706. string f, n, s, v;
  707. if (!GetOptionParts (argument, out f, out n, out s, out v))
  708. return false;
  709. Option p;
  710. if (Contains (n)) {
  711. p = this [n];
  712. c.OptionName = f + n;
  713. c.Option = p;
  714. switch (p.OptionValueType) {
  715. case OptionValueType.None:
  716. c.OptionValues.Add (n);
  717. c.Option.Invoke (c);
  718. break;
  719. case OptionValueType.Optional:
  720. case OptionValueType.Required:
  721. ParseValue (v, c);
  722. break;
  723. }
  724. return true;
  725. }
  726. // no match; is it a bool option?
  727. if (ParseBool (argument, n, c))
  728. return true;
  729. // is it a bundled option?
  730. if (ParseBundledValue (f, string.Concat (n + s + v), c))
  731. return true;
  732. return false;
  733. }
  734. private void ParseValue (string option, OptionContext c)
  735. {
  736. if (option != null)
  737. foreach (string o in c.Option.ValueSeparators != null
  738. ? option.Split (c.Option.ValueSeparators, StringSplitOptions.None)
  739. : new string[]{option}) {
  740. c.OptionValues.Add (o);
  741. }
  742. if (c.OptionValues.Count == c.Option.MaxValueCount ||
  743. c.Option.OptionValueType == OptionValueType.Optional)
  744. c.Option.Invoke (c);
  745. else if (c.OptionValues.Count > c.Option.MaxValueCount) {
  746. throw new OptionException (localizer (string.Format (
  747. "Error: Found {0} option values when expecting {1}.",
  748. c.OptionValues.Count, c.Option.MaxValueCount)),
  749. c.OptionName);
  750. }
  751. }
  752. private bool ParseBool (string option, string n, OptionContext c)
  753. {
  754. Option p;
  755. string rn;
  756. if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') &&
  757. Contains ((rn = n.Substring (0, n.Length-1)))) {
  758. p = this [rn];
  759. string v = n [n.Length-1] == '+' ? option : null;
  760. c.OptionName = option;
  761. c.Option = p;
  762. c.OptionValues.Add (v);
  763. p.Invoke (c);
  764. return true;
  765. }
  766. return false;
  767. }
  768. private bool ParseBundledValue (string f, string n, OptionContext c)
  769. {
  770. if (f != "-")
  771. return false;
  772. for (int i = 0; i < n.Length; ++i) {
  773. Option p;
  774. string opt = f + n [i].ToString ();
  775. string rn = n [i].ToString ();
  776. if (!Contains (rn)) {
  777. if (i == 0)
  778. return false;
  779. throw new OptionException (string.Format (localizer (
  780. "Cannot bundle unregistered option '{0}'."), opt), opt);
  781. }
  782. p = this [rn];
  783. switch (p.OptionValueType) {
  784. case OptionValueType.None:
  785. Invoke (c, opt, n, p);
  786. break;
  787. case OptionValueType.Optional:
  788. case OptionValueType.Required: {
  789. string v = n.Substring (i+1);
  790. c.Option = p;
  791. c.OptionName = opt;
  792. ParseValue (v.Length != 0 ? v : null, c);
  793. return true;
  794. }
  795. default:
  796. throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType);
  797. }
  798. }
  799. return true;
  800. }
  801. private static void Invoke (OptionContext c, string name, string value, Option option)
  802. {
  803. c.OptionName = name;
  804. c.Option = option;
  805. c.OptionValues.Add (value);
  806. option.Invoke (c);
  807. }
  808. private const int OptionWidth = 29;
  809. public void WriteOptionDescriptions (TextWriter o)
  810. {
  811. foreach (Option p in this) {
  812. int written = 0;
  813. if (!WriteOptionPrototype (o, p, ref written))
  814. continue;
  815. if (written < OptionWidth)
  816. o.Write (new string (' ', OptionWidth - written));
  817. else {
  818. o.WriteLine ();
  819. o.Write (new string (' ', OptionWidth));
  820. }
  821. List<string> lines = GetLines (localizer (GetDescription (p.Description)));
  822. o.WriteLine (lines [0]);
  823. string prefix = new string (' ', OptionWidth+2);
  824. for (int i = 1; i < lines.Count; ++i) {
  825. o.Write (prefix);
  826. o.WriteLine (lines [i]);
  827. }
  828. }
  829. }
  830. bool WriteOptionPrototype (TextWriter o, Option p, ref int written)
  831. {
  832. string[] names = p.Names;
  833. int i = GetNextOptionIndex (names, 0);
  834. if (i == names.Length)
  835. return false;
  836. if (names [i].Length == 1) {
  837. Write (o, ref written, " -");
  838. Write (o, ref written, names [0]);
  839. }
  840. else {
  841. Write (o, ref written, " --");
  842. Write (o, ref written, names [0]);
  843. }
  844. for ( i = GetNextOptionIndex (names, i+1);
  845. i < names.Length; i = GetNextOptionIndex (names, i+1)) {
  846. Write (o, ref written, ", ");
  847. Write (o, ref written, names [i].Length == 1 ? "-" : "--");
  848. Write (o, ref written, names [i]);
  849. }
  850. if (p.OptionValueType == OptionValueType.Optional ||
  851. p.OptionValueType == OptionValueType.Required) {
  852. if (p.OptionValueType == OptionValueType.Optional) {
  853. Write (o, ref written, localizer ("["));
  854. }
  855. Write (o, ref written, localizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description)));
  856. string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0
  857. ? p.ValueSeparators [0]
  858. : " ";
  859. for (int c = 1; c < p.MaxValueCount; ++c) {
  860. Write (o, ref written, localizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description)));
  861. }
  862. if (p.OptionValueType == OptionValueType.Optional) {
  863. Write (o, ref written, localizer ("]"));
  864. }
  865. }
  866. return true;
  867. }
  868. static int GetNextOptionIndex (string[] names, int i)
  869. {
  870. while (i < names.Length && names [i] == "<>") {
  871. ++i;
  872. }
  873. return i;
  874. }
  875. static void Write (TextWriter o, ref int n, string s)
  876. {
  877. n += s.Length;
  878. o.Write (s);
  879. }
  880. private static string GetArgumentName (int index, int maxIndex, string description)
  881. {
  882. if (description == null)
  883. return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
  884. string[] nameStart;
  885. if (maxIndex == 1)
  886. nameStart = new string[]{"{0:", "{"};
  887. else
  888. nameStart = new string[]{"{" + index + ":"};
  889. for (int i = 0; i < nameStart.Length; ++i) {
  890. int start, j = 0;
  891. do {
  892. start = description.IndexOf (nameStart [i], j);
  893. } while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false);
  894. if (start == -1)
  895. continue;
  896. int end = description.IndexOf ("}", start);
  897. if (end == -1)
  898. continue;
  899. return description.Substring (start + nameStart [i].Length, end - start - nameStart [i].Length);
  900. }
  901. return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
  902. }
  903. private static string GetDescription (string description)
  904. {
  905. if (description == null)
  906. return string.Empty;
  907. StringBuilder sb = new StringBuilder (description.Length);
  908. int start = -1;
  909. for (int i = 0; i < description.Length; ++i) {
  910. switch (description [i]) {
  911. case '{':
  912. if (i == start) {
  913. sb.Append ('{');
  914. start = -1;
  915. }
  916. else if (start < 0)
  917. start = i + 1;
  918. break;
  919. case '}':
  920. if (start < 0) {
  921. if ((i+1) == description.Length || description [i+1] != '}')
  922. throw new InvalidOperationException ("Invalid option description: " + description);
  923. ++i;
  924. sb.Append ("}");
  925. }
  926. else {
  927. sb.Append (description.Substring (start, i - start));
  928. start = -1;
  929. }
  930. break;
  931. case ':':
  932. if (start < 0)
  933. goto default;
  934. start = i + 1;
  935. break;
  936. default:
  937. if (start < 0)
  938. sb.Append (description [i]);
  939. break;
  940. }
  941. }
  942. return sb.ToString ();
  943. }
  944. private static List<string> GetLines (string description)
  945. {
  946. List<string> lines = new List<string> ();
  947. if (string.IsNullOrEmpty (description)) {
  948. lines.Add (string.Empty);
  949. return lines;
  950. }
  951. int length = 80 - OptionWidth - 2;
  952. int start = 0, end;
  953. do {
  954. end = GetLineEnd (start, length, description);
  955. bool cont = false;
  956. if (end < description.Length) {
  957. char c = description [end];
  958. if (c == '-' || (char.IsWhiteSpace (c) && c != '\n'))
  959. ++end;
  960. else if (c != '\n') {
  961. cont = true;
  962. --end;
  963. }
  964. }
  965. lines.Add (description.Substring (start, end - start));
  966. if (cont) {
  967. lines [lines.Count-1] += "-";
  968. }
  969. start = end;
  970. if (start < description.Length && description [start] == '\n')
  971. ++start;
  972. } while (end < description.Length);
  973. return lines;
  974. }
  975. private static int GetLineEnd (int start, int length, string description)
  976. {
  977. int end = System.Math.Min (start + length, description.Length);
  978. int sep = -1;
  979. for (int i = start; i < end; ++i) {
  980. switch (description [i]) {
  981. case ' ':
  982. case '\t':
  983. case '\v':
  984. case '-':
  985. case ',':
  986. case '.':
  987. case ';':
  988. sep = i;
  989. break;
  990. case '\n':
  991. return i;
  992. }
  993. }
  994. if (sep == -1 || end == description.Length)
  995. return end;
  996. return sep;
  997. }
  998. }
  999. }