diff --git a/src/Uno.Wasm.Bootstrap/Packager/Mono.Cecil.dll b/src/Uno.Wasm.Bootstrap/Packager/Mono.Cecil.dll new file mode 100644 index 0000000..1a0b93a Binary files /dev/null and b/src/Uno.Wasm.Bootstrap/Packager/Mono.Cecil.dll differ diff --git a/src/Uno.Wasm.Bootstrap/Packager/Options.cs b/src/Uno.Wasm.Bootstrap/Packager/Options.cs new file mode 100644 index 0000000..887a9f0 --- /dev/null +++ b/src/Uno.Wasm.Bootstrap/Packager/Options.cs @@ -0,0 +1,2006 @@ +// +// Options.cs +// +// Authors: +// Jonathan Pryor , +// Federico Di Gregorio +// Rolf Bjarne Kvinge +// +// Copyright (C) 2008 Novell (http://www.novell.com) +// Copyright (C) 2009 Federico Di Gregorio. +// Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com) +// Copyright (C) 2017 Microsoft Corporation (http://www.microsoft.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +// Compile With: +// mcs -debug+ -r:System.Core Options.cs -o:Mono.Options.dll -t:library +// mcs -debug+ -d:LINQ -r:System.Core Options.cs -o:Mono.Options.dll -t:library +// +// The LINQ version just changes the implementation of +// OptionSet.Parse(IEnumerable), and confers no semantic changes. + +// +// A Getopt::Long-inspired option parsing library for C#. +// +// Mono.Options.OptionSet is built upon a key/value table, where the +// key is a option format string and the value is a delegate that is +// invoked when the format string is matched. +// +// Option format strings: +// Regex-like BNF Grammar: +// name: .+ +// type: [=:] +// sep: ( [^{}]+ | '{' .+ '}' )? +// aliases: ( name type sep ) ( '|' name type sep )* +// +// Each '|'-delimited name is an alias for the associated action. If the +// format string ends in a '=', it has a required value. If the format +// string ends in a ':', it has an optional value. If neither '=' or ':' +// is present, no value is supported. `=' or `:' need only be defined on one +// alias, but if they are provided on more than one they must be consistent. +// +// Each alias portion may also end with a "key/value separator", which is used +// to split option values if the option accepts > 1 value. If not specified, +// it defaults to '=' and ':'. If specified, it can be any character except +// '{' and '}' OR the *string* between '{' and '}'. If no separator should be +// used (i.e. the separate values should be distinct arguments), then "{}" +// should be used as the separator. +// +// Options are extracted either from the current option by looking for +// the option name followed by an '=' or ':', or is taken from the +// following option IFF: +// - The current option does not contain a '=' or a ':' +// - The current option requires a value (i.e. not a Option type of ':') +// +// The `name' used in the option format string does NOT include any leading +// option indicator, such as '-', '--', or '/'. All three of these are +// permitted/required on any named option. +// +// Option bundling is permitted so long as: +// - '-' is used to start the option group +// - all of the bundled options are a single character +// - at most one of the bundled options accepts a value, and the value +// provided starts from the next character to the end of the string. +// +// This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value' +// as '-Dname=value'. +// +// Option processing is disabled by specifying "--". All options after "--" +// are returned by OptionSet.Parse() unchanged and unprocessed. +// +// Unprocessed options are returned from OptionSet.Parse(). +// +// Examples: +// int verbose = 0; +// OptionSet p = new OptionSet () +// .Add ("v", v => ++verbose) +// .Add ("name=|value=", v => Console.WriteLine (v)); +// p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); +// +// The above would parse the argument string array, and would invoke the +// lambda expression three times, setting `verbose' to 3 when complete. +// It would also print out "A" and "B" to standard output. +// The returned array would contain the string "extra". +// +// C# 3.0 collection initializers are supported and encouraged: +// var p = new OptionSet () { +// { "h|?|help", v => ShowHelp () }, +// }; +// +// System.ComponentModel.TypeConverter is also supported, allowing the use of +// custom data types in the callback type; TypeConverter.ConvertFromString() +// is used to convert the value option to an instance of the specified +// type: +// +// var p = new OptionSet () { +// { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, +// }; +// +// Random other tidbits: +// - Boolean options (those w/o '=' or ':' in the option format string) +// are explicitly enabled if they are followed with '+', and explicitly +// disabled if they are followed with '-': +// string a = null; +// var p = new OptionSet () { +// { "a", s => a = s }, +// }; +// p.Parse (new string[]{"-a"}); // sets v != null +// p.Parse (new string[]{"-a+"}); // sets v != null +// p.Parse (new string[]{"-a-"}); // sets v == null +// + +// +// Mono.Options.CommandSet allows easily having separate commands and +// associated command options, allowing creation of a *suite* along the +// lines of **git**(1), **svn**(1), etc. +// +// CommandSet allows intermixing plain text strings for `--help` output, +// Option values -- as supported by OptionSet -- and Command instances, +// which have a name, optional help text, and an optional OptionSet. +// +// var suite = new CommandSet ("suite-name") { +// // Use strings and option values, as with OptionSet +// "usage: suite-name COMMAND [OPTIONS]+", +// { "v:", "verbosity", (int? v) => Verbosity = v.HasValue ? v.Value : Verbosity+1 }, +// // Commands may also be specified +// new Command ("command-name", "command help") { +// Options = new OptionSet {/*...*/}, +// Run = args => { /*...*/}, +// }, +// new MyCommandSubclass (), +// }; +// return suite.Run (new string[]{...}); +// +// CommandSet provides a `help` command, and forwards `help COMMAND` +// to the registered Command instance by invoking Command.Invoke() +// with `--help` as an option. +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Globalization; +using System.IO; +#if PCL +using System.Reflection; +#else +using System.Runtime.Serialization; +using System.Security.Permissions; +#endif +using System.Text; +using System.Text.RegularExpressions; + +#if LINQ +using System.Linq; +#endif + +#if TEST +using NDesk.Options; +#endif + +#if PCL +using MessageLocalizerConverter = System.Func; +#else +using MessageLocalizerConverter = System.Converter; +#endif + +#if NDESK_OPTIONS +namespace NDesk.Options +#else +namespace Mono.Options +#endif +{ + static class StringCoda { + + public static IEnumerable WrappedLines (string self, params int[] widths) + { + IEnumerable w = widths; + return WrappedLines (self, w); + } + + public static IEnumerable WrappedLines (string self, IEnumerable widths) + { + if (widths == null) + throw new ArgumentNullException ("widths"); + return CreateWrappedLinesIterator (self, widths); + } + + private static IEnumerable CreateWrappedLinesIterator (string self, IEnumerable widths) + { + if (string.IsNullOrEmpty (self)) { + yield return string.Empty; + yield break; + } + using (IEnumerator ewidths = widths.GetEnumerator ()) { + bool? hw = null; + int width = GetNextWidth (ewidths, int.MaxValue, ref hw); + int start = 0, end; + do { + end = GetLineEnd (start, width, self); + // endCorrection is 1 if the line end is '\n', and might be 2 if the line end is '\r\n'. + int endCorrection = 1; + if (end >= 2 && self.Substring (end - 2, 2).Equals ("\r\n")) + endCorrection = 2; + char c = self [end - endCorrection]; + if (char.IsWhiteSpace (c)) + end -= endCorrection; + bool needContinuation = end != self.Length && !IsEolChar (c); + string continuation = ""; + if (needContinuation) { + --end; + continuation = "-"; + } + string line = self.Substring (start, end - start) + continuation; + yield return line; + start = end; + if (char.IsWhiteSpace (c)) + start += endCorrection; + width = GetNextWidth (ewidths, width, ref hw); + } while (start < self.Length); + } + } + + private static int GetNextWidth (IEnumerator ewidths, int curWidth, ref bool? eValid) + { + if (!eValid.HasValue || (eValid.HasValue && eValid.Value)) { + curWidth = (eValid = ewidths.MoveNext ()).Value ? ewidths.Current : curWidth; + // '.' is any character, - is for a continuation + const string minWidth = ".-"; + if (curWidth < minWidth.Length) + throw new ArgumentOutOfRangeException ("widths", + string.Format ("Element must be >= {0}, was {1}.", minWidth.Length, curWidth)); + return curWidth; + } + // no more elements, use the last element. + return curWidth; + } + + private static bool IsEolChar (char c) + { + return !char.IsLetterOrDigit (c); + } + + private static int GetLineEnd (int start, int length, string description) + { + int end = System.Math.Min (start + length, description.Length); + int sep = -1; + for (int i = start; i < end; ++i) { + if (i + 2 <= description.Length && description.Substring (i, 2).Equals ("\r\n")) + return i+2; + if (description [i] == '\n') + return i+1; + if (IsEolChar (description [i])) + sep = i+1; + } + if (sep == -1 || end == description.Length) + return end; + return sep; + } + } + + public class OptionValueCollection : IList, IList { + + List values = new List (); + OptionContext c; + + internal OptionValueCollection (OptionContext c) + { + this.c = c; + } + + #region ICollection + void ICollection.CopyTo (Array array, int index) {(values as ICollection).CopyTo (array, index);} + bool ICollection.IsSynchronized {get {return (values as ICollection).IsSynchronized;}} + object ICollection.SyncRoot {get {return (values as ICollection).SyncRoot;}} + #endregion + + #region ICollection + public void Add (string item) {values.Add (item);} + public void Clear () {values.Clear ();} + public bool Contains (string item) {return values.Contains (item);} + public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);} + public bool Remove (string item) {return values.Remove (item);} + public int Count {get {return values.Count;}} + public bool IsReadOnly {get {return false;}} + #endregion + + #region IEnumerable + IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();} + #endregion + + #region IEnumerable + public IEnumerator GetEnumerator () {return values.GetEnumerator ();} + #endregion + + #region IList + int IList.Add (object value) {return (values as IList).Add (value);} + bool IList.Contains (object value) {return (values as IList).Contains (value);} + int IList.IndexOf (object value) {return (values as IList).IndexOf (value);} + void IList.Insert (int index, object value) {(values as IList).Insert (index, value);} + void IList.Remove (object value) {(values as IList).Remove (value);} + void IList.RemoveAt (int index) {(values as IList).RemoveAt (index);} + bool IList.IsFixedSize {get {return false;}} + object IList.this [int index] {get {return this [index];} set {(values as IList)[index] = value;}} + #endregion + + #region IList + public int IndexOf (string item) {return values.IndexOf (item);} + public void Insert (int index, string item) {values.Insert (index, item);} + public void RemoveAt (int index) {values.RemoveAt (index);} + + private void AssertValid (int index) + { + if (c.Option == null) + throw new InvalidOperationException ("OptionContext.Option is null."); + if (index >= c.Option.MaxValueCount) + throw new ArgumentOutOfRangeException ("index"); + if (c.Option.OptionValueType == OptionValueType.Required && + index >= values.Count) + throw new OptionException (string.Format ( + c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName), + c.OptionName); + } + + public string this [int index] { + get { + AssertValid (index); + return index >= values.Count ? null : values [index]; + } + set { + values [index] = value; + } + } + #endregion + + public List ToList () + { + return new List (values); + } + + public string[] ToArray () + { + return values.ToArray (); + } + + public override string ToString () + { + return string.Join (", ", values.ToArray ()); + } + } + + public class OptionContext { + private Option option; + private string name; + private int index; + private OptionSet set; + private OptionValueCollection c; + + public OptionContext (OptionSet set) + { + this.set = set; + this.c = new OptionValueCollection (this); + } + + public Option Option { + get {return option;} + set {option = value;} + } + + public string OptionName { + get {return name;} + set {name = value;} + } + + public int OptionIndex { + get {return index;} + set {index = value;} + } + + public OptionSet OptionSet { + get {return set;} + } + + public OptionValueCollection OptionValues { + get {return c;} + } + } + + public enum OptionValueType { + None, + Optional, + Required, + } + + public abstract class Option { + string prototype, description; + string[] names; + OptionValueType type; + int count; + string[] separators; + bool hidden; + + protected Option (string prototype, string description) + : this (prototype, description, 1, false) + { + } + + protected Option (string prototype, string description, int maxValueCount) + : this (prototype, description, maxValueCount, false) + { + } + + protected Option (string prototype, string description, int maxValueCount, bool hidden) + { + if (prototype == null) + throw new ArgumentNullException ("prototype"); + if (prototype.Length == 0) + throw new ArgumentException ("Cannot be the empty string.", "prototype"); + if (maxValueCount < 0) + throw new ArgumentOutOfRangeException ("maxValueCount"); + + this.prototype = prototype; + this.description = description; + this.count = maxValueCount; + this.names = (this is OptionSet.Category) + // append GetHashCode() so that "duplicate" categories have distinct + // names, e.g. adding multiple "" categories should be valid. + ? new[]{prototype + this.GetHashCode ()} + : prototype.Split ('|'); + + if (this is OptionSet.Category || this is CommandOption) + return; + + this.type = ParsePrototype (); + this.hidden = hidden; + + if (this.count == 0 && type != OptionValueType.None) + throw new ArgumentException ( + "Cannot provide maxValueCount of 0 for OptionValueType.Required or " + + "OptionValueType.Optional.", + "maxValueCount"); + if (this.type == OptionValueType.None && maxValueCount > 1) + throw new ArgumentException ( + string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), + "maxValueCount"); + if (Array.IndexOf (names, "<>") >= 0 && + ((names.Length == 1 && this.type != OptionValueType.None) || + (names.Length > 1 && this.MaxValueCount > 1))) + throw new ArgumentException ( + "The default option handler '<>' cannot require values.", + "prototype"); + } + + public string Prototype {get {return prototype;}} + public string Description {get {return description;}} + public OptionValueType OptionValueType {get {return type;}} + public int MaxValueCount {get {return count;}} + public bool Hidden {get {return hidden;}} + + public string[] GetNames () + { + return (string[]) names.Clone (); + } + + public string[] GetValueSeparators () + { + if (separators == null) + return new string [0]; + return (string[]) separators.Clone (); + } + + protected static T Parse (string value, OptionContext c) + { + Type tt = typeof (T); +#if PCL + TypeInfo ti = tt.GetTypeInfo (); +#else + Type ti = tt; +#endif + bool nullable = + ti.IsValueType && + ti.IsGenericType && + !ti.IsGenericTypeDefinition && + ti.GetGenericTypeDefinition () == typeof (Nullable<>); +#if PCL + Type targetType = nullable ? tt.GenericTypeArguments [0] : tt; +#else + Type targetType = nullable ? tt.GetGenericArguments () [0] : tt; +#endif + T t = default (T); + try { + if (value != null) { +#if PCL + if (targetType.GetTypeInfo ().IsEnum) + t = (T) Enum.Parse (targetType, value, true); + else + t = (T) Convert.ChangeType (value, targetType); +#else + TypeConverter conv = TypeDescriptor.GetConverter (targetType); + t = (T) conv.ConvertFromString (value); +#endif + } + } + catch (Exception e) { + throw new OptionException ( + string.Format ( + c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."), + value, targetType.Name, c.OptionName), + c.OptionName, e); + } + return t; + } + + internal string[] Names {get {return names;}} + internal string[] ValueSeparators {get {return separators;}} + + static readonly char[] NameTerminator = new char[]{'=', ':'}; + + private OptionValueType ParsePrototype () + { + char type = '\0'; + List seps = new List (); + for (int i = 0; i < names.Length; ++i) { + string name = names [i]; + if (name.Length == 0) + throw new ArgumentException ("Empty option names are not supported.", "prototype"); + + int end = name.IndexOfAny (NameTerminator); + if (end == -1) + continue; + names [i] = name.Substring (0, end); + if (type == '\0' || type == name [end]) + type = name [end]; + else + throw new ArgumentException ( + string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]), + "prototype"); + AddSeparators (name, end, seps); + } + + if (type == '\0') + return OptionValueType.None; + + if (count <= 1 && seps.Count != 0) + throw new ArgumentException ( + string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count), + "prototype"); + if (count > 1) { + if (seps.Count == 0) + this.separators = new string[]{":", "="}; + else if (seps.Count == 1 && seps [0].Length == 0) + this.separators = null; + else + this.separators = seps.ToArray (); + } + + return type == '=' ? OptionValueType.Required : OptionValueType.Optional; + } + + private static void AddSeparators (string name, int end, ICollection seps) + { + int start = -1; + for (int i = end+1; i < name.Length; ++i) { + switch (name [i]) { + case '{': + if (start != -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + start = i+1; + break; + case '}': + if (start == -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + seps.Add (name.Substring (start, i-start)); + start = -1; + break; + default: + if (start == -1) + seps.Add (name [i].ToString ()); + break; + } + } + if (start != -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + } + + public void Invoke (OptionContext c) + { + OnParseComplete (c); + c.OptionName = null; + c.Option = null; + c.OptionValues.Clear (); + } + + protected abstract void OnParseComplete (OptionContext c); + + internal void InvokeOnParseComplete (OptionContext c) + { + OnParseComplete (c); + } + + public override string ToString () + { + return Prototype; + } + } + + public abstract class ArgumentSource { + + protected ArgumentSource () + { + } + + public abstract string[] GetNames (); + public abstract string Description { get; } + public abstract bool GetArguments (string value, out IEnumerable replacement); + +#if !PCL || NETSTANDARD1_3 + public static IEnumerable GetArgumentsFromFile (string file) + { + return GetArguments (File.OpenText (file), true); + } +#endif + + public static IEnumerable GetArguments (TextReader reader) + { + return GetArguments (reader, false); + } + + // Cribbed from mcs/driver.cs:LoadArgs(string) + static IEnumerable GetArguments (TextReader reader, bool close) + { + try { + StringBuilder arg = new StringBuilder (); + + string line; + while ((line = reader.ReadLine ()) != null) { + int t = line.Length; + + for (int i = 0; i < t; i++) { + char c = line [i]; + + if (c == '"' || c == '\'') { + char end = c; + + for (i++; i < t; i++){ + c = line [i]; + + if (c == end) + break; + arg.Append (c); + } + } else if (c == ' ') { + if (arg.Length > 0) { + yield return arg.ToString (); + arg.Length = 0; + } + } else + arg.Append (c); + } + if (arg.Length > 0) { + yield return arg.ToString (); + arg.Length = 0; + } + } + } + finally { + if (close) + reader.Dispose (); + } + } + } + +#if !PCL || NETSTANDARD1_3 + public class ResponseFileSource : ArgumentSource { + + public override string[] GetNames () + { + return new string[]{"@file"}; + } + + public override string Description { + get {return "Read response file for more options.";} + } + + public override bool GetArguments (string value, out IEnumerable replacement) + { + if (string.IsNullOrEmpty (value) || !value.StartsWith ("@")) { + replacement = null; + return false; + } + replacement = ArgumentSource.GetArgumentsFromFile (value.Substring (1)); + return true; + } + } +#endif + +#if !PCL + [Serializable] +#endif + public class OptionException : Exception { + private string option; + + public OptionException () + { + } + + public OptionException (string message, string optionName) + : base (message) + { + this.option = optionName; + } + + public OptionException (string message, string optionName, Exception innerException) + : base (message, innerException) + { + this.option = optionName; + } + +#if !PCL + protected OptionException (SerializationInfo info, StreamingContext context) + : base (info, context) + { + this.option = info.GetString ("OptionName"); + } +#endif + + public string OptionName { + get {return this.option;} + } + +#if !PCL +#pragma warning disable 618 // SecurityPermissionAttribute is obsolete + [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)] +#pragma warning restore 618 + public override void GetObjectData (SerializationInfo info, StreamingContext context) + { + base.GetObjectData (info, context); + info.AddValue ("OptionName", option); + } +#endif + } + + public delegate void OptionAction (TKey key, TValue value); + + public class OptionSet : KeyedCollection + { + public OptionSet () + : this (null) + { + } + + public OptionSet (MessageLocalizerConverter localizer) + { + this.roSources = new ReadOnlyCollection (sources); + this.localizer = localizer; + if (this.localizer == null) { + this.localizer = delegate (string f) { + return f; + }; + } + } + + MessageLocalizerConverter localizer; + + public MessageLocalizerConverter MessageLocalizer { + get {return localizer;} + internal set {localizer = value;} + } + + List sources = new List (); + ReadOnlyCollection roSources; + + public ReadOnlyCollection ArgumentSources { + get {return roSources;} + } + + + protected override string GetKeyForItem (Option item) + { + if (item == null) + throw new ArgumentNullException ("option"); + if (item.Names != null && item.Names.Length > 0) + return item.Names [0]; + // This should never happen, as it's invalid for Option to be + // constructed w/o any names. + throw new InvalidOperationException ("Option has no names!"); + } + + [Obsolete ("Use KeyedCollection.this[string]")] + protected Option GetOptionForName (string option) + { + if (option == null) + throw new ArgumentNullException ("option"); + try { + return base [option]; + } + catch (KeyNotFoundException) { + return null; + } + } + + protected override void InsertItem (int index, Option item) + { + base.InsertItem (index, item); + AddImpl (item); + } + + protected override void RemoveItem (int index) + { + Option p = Items [index]; + base.RemoveItem (index); + // KeyedCollection.RemoveItem() handles the 0th item + for (int i = 1; i < p.Names.Length; ++i) { + Dictionary.Remove (p.Names [i]); + } + } + + protected override void SetItem (int index, Option item) + { + base.SetItem (index, item); + AddImpl (item); + } + + private void AddImpl (Option option) + { + if (option == null) + throw new ArgumentNullException ("option"); + List added = new List (option.Names.Length); + try { + // KeyedCollection.InsertItem/SetItem handle the 0th name. + for (int i = 1; i < option.Names.Length; ++i) { + Dictionary.Add (option.Names [i], option); + added.Add (option.Names [i]); + } + } + catch (Exception) { + foreach (string name in added) + Dictionary.Remove (name); + throw; + } + } + + public OptionSet Add (string header) + { + if (header == null) + throw new ArgumentNullException ("header"); + Add (new Category (header)); + return this; + } + + internal sealed class Category : Option { + + // Prototype starts with '=' because this is an invalid prototype + // (see Option.ParsePrototype(), and thus it'll prevent Category + // instances from being accidentally used as normal options. + public Category (string description) + : base ("=:Category:= " + description, description) + { + } + + protected override void OnParseComplete (OptionContext c) + { + throw new NotSupportedException ("Category.OnParseComplete should not be invoked."); + } + } + + + public new OptionSet Add (Option option) + { + base.Add (option); + return this; + } + + sealed class ActionOption : Option { + Action action; + + public ActionOption (string prototype, string description, int count, Action action) + : this (prototype, description, count, action, false) + { + } + + public ActionOption (string prototype, string description, int count, Action action, bool hidden) + : base (prototype, description, count, hidden) + { + if (action == null) + throw new ArgumentNullException ("action"); + this.action = action; + } + + protected override void OnParseComplete (OptionContext c) + { + action (c.OptionValues); + } + } + + public OptionSet Add (string prototype, Action action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, Action action) + { + return Add (prototype, description, action, false); + } + + public OptionSet Add (string prototype, string description, Action action, bool hidden) + { + if (action == null) + throw new ArgumentNullException ("action"); + Option p = new ActionOption (prototype, description, 1, + delegate (OptionValueCollection v) { action (v [0]); }, hidden); + base.Add (p); + return this; + } + + public OptionSet Add (string prototype, OptionAction action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, OptionAction action) + { + return Add (prototype, description, action, false); + } + + public OptionSet Add (string prototype, string description, OptionAction action, bool hidden) { + if (action == null) + throw new ArgumentNullException ("action"); + Option p = new ActionOption (prototype, description, 2, + delegate (OptionValueCollection v) {action (v [0], v [1]);}, hidden); + base.Add (p); + return this; + } + + sealed class ActionOption : Option { + Action action; + + public ActionOption (string prototype, string description, Action action) + : base (prototype, description, 1) + { + if (action == null) + throw new ArgumentNullException ("action"); + this.action = action; + } + + protected override void OnParseComplete (OptionContext c) + { + action (Parse (c.OptionValues [0], c)); + } + } + + sealed class ActionOption : Option { + OptionAction action; + + public ActionOption (string prototype, string description, OptionAction action) + : base (prototype, description, 2) + { + if (action == null) + throw new ArgumentNullException ("action"); + this.action = action; + } + + protected override void OnParseComplete (OptionContext c) + { + action ( + Parse (c.OptionValues [0], c), + Parse (c.OptionValues [1], c)); + } + } + + public OptionSet Add (string prototype, Action action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, Action action) + { + return Add (new ActionOption (prototype, description, action)); + } + + public OptionSet Add (string prototype, OptionAction action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, OptionAction action) + { + return Add (new ActionOption (prototype, description, action)); + } + + public OptionSet Add (ArgumentSource source) + { + if (source == null) + throw new ArgumentNullException ("source"); + sources.Add (source); + return this; + } + + protected virtual OptionContext CreateOptionContext () + { + return new OptionContext (this); + } + + public List Parse (IEnumerable arguments) + { + if (arguments == null) + throw new ArgumentNullException ("arguments"); + OptionContext c = CreateOptionContext (); + c.OptionIndex = -1; + bool process = true; + List unprocessed = new List (); + Option def = Contains ("<>") ? this ["<>"] : null; + ArgumentEnumerator ae = new ArgumentEnumerator (arguments); + foreach (string argument in ae) { + ++c.OptionIndex; + if (argument == "--") { + process = false; + continue; + } + if (!process) { + Unprocessed (unprocessed, def, c, argument); + continue; + } + if (AddSource (ae, argument)) + continue; + if (!Parse (argument, c)) + Unprocessed (unprocessed, def, c, argument); + } + if (c.Option != null) + c.Option.Invoke (c); + return unprocessed; + } + + class ArgumentEnumerator : IEnumerable { + List> sources = new List> (); + + public ArgumentEnumerator (IEnumerable arguments) + { + sources.Add (arguments.GetEnumerator ()); + } + + public void Add (IEnumerable arguments) + { + sources.Add (arguments.GetEnumerator ()); + } + + public IEnumerator GetEnumerator () + { + do { + IEnumerator c = sources [sources.Count-1]; + if (c.MoveNext ()) + yield return c.Current; + else { + c.Dispose (); + sources.RemoveAt (sources.Count-1); + } + } while (sources.Count > 0); + } + + IEnumerator IEnumerable.GetEnumerator () + { + return GetEnumerator (); + } + } + + bool AddSource (ArgumentEnumerator ae, string argument) + { + foreach (ArgumentSource source in sources) { + IEnumerable replacement; + if (!source.GetArguments (argument, out replacement)) + continue; + ae.Add (replacement); + return true; + } + return false; + } + + private static bool Unprocessed (ICollection extra, Option def, OptionContext c, string argument) + { + if (def == null) { + extra.Add (argument); + return false; + } + c.OptionValues.Add (argument); + c.Option = def; + c.Option.Invoke (c); + return false; + } + + private readonly Regex ValueOption = new Regex ( + @"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$"); + + protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value) + { + if (argument == null) + throw new ArgumentNullException ("argument"); + + flag = name = sep = value = null; + Match m = ValueOption.Match (argument); + if (!m.Success) { + return false; + } + flag = m.Groups ["flag"].Value; + name = m.Groups ["name"].Value; + if (m.Groups ["sep"].Success && m.Groups ["value"].Success) { + sep = m.Groups ["sep"].Value; + value = m.Groups ["value"].Value; + } + return true; + } + + protected virtual bool Parse (string argument, OptionContext c) + { + if (c.Option != null) { + ParseValue (argument, c); + return true; + } + + string f, n, s, v; + if (!GetOptionParts (argument, out f, out n, out s, out v)) + return false; + + Option p; + if (Contains (n)) { + p = this [n]; + c.OptionName = f + n; + c.Option = p; + switch (p.OptionValueType) { + case OptionValueType.None: + c.OptionValues.Add (n); + c.Option.Invoke (c); + break; + case OptionValueType.Optional: + case OptionValueType.Required: + ParseValue (v, c); + break; + } + return true; + } + // no match; is it a bool option? + if (ParseBool (argument, n, c)) + return true; + // is it a bundled option? + if (ParseBundledValue (f, string.Concat (n + s + v), c)) + return true; + + return false; + } + + private void ParseValue (string option, OptionContext c) + { + if (option != null) + foreach (string o in c.Option.ValueSeparators != null + ? option.Split (c.Option.ValueSeparators, c.Option.MaxValueCount - c.OptionValues.Count, StringSplitOptions.None) + : new string[]{option}) { + c.OptionValues.Add (o); + } + if (c.OptionValues.Count == c.Option.MaxValueCount || + c.Option.OptionValueType == OptionValueType.Optional) + c.Option.Invoke (c); + else if (c.OptionValues.Count > c.Option.MaxValueCount) { + throw new OptionException (localizer (string.Format ( + "Error: Found {0} option values when expecting {1}.", + c.OptionValues.Count, c.Option.MaxValueCount)), + c.OptionName); + } + } + + private bool ParseBool (string option, string n, OptionContext c) + { + Option p; + string rn; + if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') && + Contains ((rn = n.Substring (0, n.Length-1)))) { + p = this [rn]; + string v = n [n.Length-1] == '+' ? option : null; + c.OptionName = option; + c.Option = p; + c.OptionValues.Add (v); + p.Invoke (c); + return true; + } + return false; + } + + private bool ParseBundledValue (string f, string n, OptionContext c) + { + if (f != "-") + return false; + for (int i = 0; i < n.Length; ++i) { + Option p; + string opt = f + n [i].ToString (); + string rn = n [i].ToString (); + if (!Contains (rn)) { + if (i == 0) + return false; + throw new OptionException (string.Format (localizer ( + "Cannot use unregistered option '{0}' in bundle '{1}'."), rn, f + n), null); + } + p = this [rn]; + switch (p.OptionValueType) { + case OptionValueType.None: + Invoke (c, opt, n, p); + break; + case OptionValueType.Optional: + case OptionValueType.Required: { + string v = n.Substring (i+1); + c.Option = p; + c.OptionName = opt; + ParseValue (v.Length != 0 ? v : null, c); + return true; + } + default: + throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType); + } + } + return true; + } + + private static void Invoke (OptionContext c, string name, string value, Option option) + { + c.OptionName = name; + c.Option = option; + c.OptionValues.Add (value); + option.Invoke (c); + } + + private const int OptionWidth = 29; + private const int Description_FirstWidth = 80 - OptionWidth; + private const int Description_RemWidth = 80 - OptionWidth - 2; + + static readonly string CommandHelpIndentStart = new string (' ', OptionWidth); + static readonly string CommandHelpIndentRemaining = new string (' ', OptionWidth + 2); + + public void WriteOptionDescriptions (TextWriter o) + { + foreach (Option p in this) { + int written = 0; + + if (p.Hidden) + continue; + + Category c = p as Category; + if (c != null) { + WriteDescription (o, p.Description, "", 80, 80); + continue; + } + CommandOption co = p as CommandOption; + if (co != null) { + WriteCommandDescription (o, co.Command, co.CommandName); + continue; + } + + if (!WriteOptionPrototype (o, p, ref written)) + continue; + + if (written < OptionWidth) + o.Write (new string (' ', OptionWidth - written)); + else { + o.WriteLine (); + o.Write (new string (' ', OptionWidth)); + } + + WriteDescription (o, p.Description, new string (' ', OptionWidth+2), + Description_FirstWidth, Description_RemWidth); + } + + foreach (ArgumentSource s in sources) { + string[] names = s.GetNames (); + if (names == null || names.Length == 0) + continue; + + int written = 0; + + Write (o, ref written, " "); + Write (o, ref written, names [0]); + for (int i = 1; i < names.Length; ++i) { + Write (o, ref written, ", "); + Write (o, ref written, names [i]); + } + + if (written < OptionWidth) + o.Write (new string (' ', OptionWidth - written)); + else { + o.WriteLine (); + o.Write (new string (' ', OptionWidth)); + } + + WriteDescription (o, s.Description, new string (' ', OptionWidth+2), + Description_FirstWidth, Description_RemWidth); + } + } + + internal void WriteCommandDescription (TextWriter o, Command c, string commandName) + { + var name = new string (' ', 8) + (commandName ?? c.Name); + if (name.Length < OptionWidth - 1) { + WriteDescription (o, name + new string (' ', OptionWidth - name.Length) + c.Help, CommandHelpIndentRemaining, 80, Description_RemWidth); + } else { + WriteDescription (o, name, "", 80, 80); + WriteDescription (o, CommandHelpIndentStart + c.Help, CommandHelpIndentRemaining, 80, Description_RemWidth); + } + } + + void WriteDescription (TextWriter o, string value, string prefix, int firstWidth, int remWidth) + { + bool indent = false; + foreach (string line in GetLines (localizer (GetDescription (value)), firstWidth, remWidth)) { + if (indent) + o.Write (prefix); + o.WriteLine (line); + indent = true; + } + } + + bool WriteOptionPrototype (TextWriter o, Option p, ref int written) + { + string[] names = p.Names; + + int i = GetNextOptionIndex (names, 0); + if (i == names.Length) + return false; + + if (names [i].Length == 1) { + Write (o, ref written, " -"); + Write (o, ref written, names [0]); + } + else { + Write (o, ref written, " --"); + Write (o, ref written, names [0]); + } + + for ( i = GetNextOptionIndex (names, i+1); + i < names.Length; i = GetNextOptionIndex (names, i+1)) { + Write (o, ref written, ", "); + Write (o, ref written, names [i].Length == 1 ? "-" : "--"); + Write (o, ref written, names [i]); + } + + if (p.OptionValueType == OptionValueType.Optional || + p.OptionValueType == OptionValueType.Required) { + if (p.OptionValueType == OptionValueType.Optional) { + Write (o, ref written, localizer ("[")); + } + Write (o, ref written, localizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description))); + string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 + ? p.ValueSeparators [0] + : " "; + for (int c = 1; c < p.MaxValueCount; ++c) { + Write (o, ref written, localizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description))); + } + if (p.OptionValueType == OptionValueType.Optional) { + Write (o, ref written, localizer ("]")); + } + } + return true; + } + + static int GetNextOptionIndex (string[] names, int i) + { + while (i < names.Length && names [i] == "<>") { + ++i; + } + return i; + } + + static void Write (TextWriter o, ref int n, string s) + { + n += s.Length; + o.Write (s); + } + + static string GetArgumentName (int index, int maxIndex, string description) + { + var matches = Regex.Matches (description ?? "", @"(?<=(? 1 + if (maxIndex > 1 && parts.Length == 2 && + parts[0] == index.ToString (CultureInfo.InvariantCulture)) { + argName = parts[1]; + } + } + + if (string.IsNullOrEmpty (argName)) { + argName = maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); + } + return argName; + } + + private static string GetDescription (string description) + { + if (description == null) + return string.Empty; + StringBuilder sb = new StringBuilder (description.Length); + int start = -1; + for (int i = 0; i < description.Length; ++i) { + switch (description [i]) { + case '{': + if (i == start) { + sb.Append ('{'); + start = -1; + } + else if (start < 0) + start = i + 1; + break; + case '}': + if (start < 0) { + if ((i+1) == description.Length || description [i+1] != '}') + throw new InvalidOperationException ("Invalid option description: " + description); + ++i; + sb.Append ("}"); + } + else { + sb.Append (description.Substring (start, i - start)); + start = -1; + } + break; + case ':': + if (start < 0) + goto default; + start = i + 1; + break; + default: + if (start < 0) + sb.Append (description [i]); + break; + } + } + return sb.ToString (); + } + + private static IEnumerable GetLines (string description, int firstWidth, int remWidth) + { + return StringCoda.WrappedLines (description, firstWidth, remWidth); + } + } + + public class Command + { + public string Name {get;} + public string Help {get;} + + public OptionSet Options {get; set;} + public Action> Run {get; set;} + + public CommandSet CommandSet {get; internal set;} + + public Command (string name, string help = null) + { + if (string.IsNullOrEmpty (name)) + throw new ArgumentNullException (nameof (name)); + + Name = NormalizeCommandName (name); + Help = help; + } + + static string NormalizeCommandName (string name) + { + var value = new StringBuilder (name.Length); + var space = false; + for (int i = 0; i < name.Length; ++i) { + if (!char.IsWhiteSpace (name, i)) { + space = false; + value.Append (name [i]); + } + else if (!space) { + space = true; + value.Append (' '); + } + } + return value.ToString (); + } + + public virtual int Invoke (IEnumerable arguments) + { + var rest = Options?.Parse (arguments) ?? arguments; + Run?.Invoke (rest); + return 0; + } + } + + class CommandOption : Option + { + public Command Command {get;} + public string CommandName {get;} + + // Prototype starts with '=' because this is an invalid prototype + // (see Option.ParsePrototype(), and thus it'll prevent Category + // instances from being accidentally used as normal options. + public CommandOption (Command command, string commandName = null, bool hidden = false) + : base ("=:Command:= " + (commandName ?? command?.Name), (commandName ?? command?.Name), maxValueCount: 0, hidden: hidden) + { + if (command == null) + throw new ArgumentNullException (nameof (command)); + Command = command; + CommandName = commandName ?? command.Name; + } + + protected override void OnParseComplete (OptionContext c) + { + throw new NotSupportedException ("CommandOption.OnParseComplete should not be invoked."); + } + } + + class HelpOption : Option + { + Option option; + CommandSet commands; + + public HelpOption (CommandSet commands, Option d) + : base (d.Prototype, d.Description, d.MaxValueCount, d.Hidden) + { + this.commands = commands; + this.option = d; + } + + protected override void OnParseComplete (OptionContext c) + { + commands.showHelp = true; + + option?.InvokeOnParseComplete (c); + } + } + + class CommandOptionSet : OptionSet + { + CommandSet commands; + + public CommandOptionSet (CommandSet commands, MessageLocalizerConverter localizer) + : base (localizer) + { + this.commands = commands; + } + + protected override void SetItem (int index, Option item) + { + if (ShouldWrapOption (item)) { + base.SetItem (index, new HelpOption (commands, item)); + return; + } + base.SetItem (index, item); + } + + bool ShouldWrapOption (Option item) + { + if (item == null) + return false; + var help = item as HelpOption; + if (help != null) + return false; + foreach (var n in item.Names) { + if (n == "help") + return true; + } + return false; + } + + protected override void InsertItem (int index, Option item) + { + if (ShouldWrapOption (item)) { + base.InsertItem (index, new HelpOption (commands, item)); + return; + } + base.InsertItem (index, item); + } + } + + public class CommandSet : KeyedCollection + { + readonly string suite; + + OptionSet options; + TextWriter outWriter; + TextWriter errorWriter; + + internal List NestedCommandSets; + + internal HelpCommand help; + + internal bool showHelp; + + internal OptionSet Options => options; + +#if !PCL || NETSTANDARD1_3 + public CommandSet(string suite, MessageLocalizerConverter localizer = null) + : this(suite, Console.Out, Console.Error, localizer) + { + } +#endif + + public CommandSet (string suite, TextWriter output, TextWriter error, MessageLocalizerConverter localizer = null) + { + if (suite == null) + throw new ArgumentNullException (nameof (suite)); + if (output == null) + throw new ArgumentNullException (nameof (output)); + if (error == null) + throw new ArgumentNullException (nameof (error)); + + this.suite = suite; + options = new CommandOptionSet (this, localizer); + outWriter = output; + errorWriter = error; + } + + public string Suite => suite; + public TextWriter Out => outWriter; + public TextWriter Error => errorWriter; + public MessageLocalizerConverter MessageLocalizer => options.MessageLocalizer; + + protected override string GetKeyForItem (Command item) + { + return item?.Name; + } + + public new CommandSet Add (Command value) + { + if (value == null) + throw new ArgumentNullException (nameof (value)); + AddCommand (value); + options.Add (new CommandOption (value)); + return this; + } + + void AddCommand (Command value) + { + if (value.CommandSet != null && value.CommandSet != this) { + throw new ArgumentException ("Command instances can only be added to a single CommandSet.", nameof (value)); + } + value.CommandSet = this; + if (value.Options != null) { + value.Options.MessageLocalizer = options.MessageLocalizer; + } + + base.Add (value); + + help = help ?? value as HelpCommand; + } + + public CommandSet Add (string header) + { + options.Add (header); + return this; + } + + public CommandSet Add (Option option) + { + options.Add (option); + return this; + } + + public CommandSet Add (string prototype, Action action) + { + options.Add (prototype, action); + return this; + } + + public CommandSet Add (string prototype, string description, Action action) + { + options.Add (prototype, description, action); + return this; + } + + public CommandSet Add (string prototype, string description, Action action, bool hidden) + { + options.Add (prototype, description, action, hidden); + return this; + } + + public CommandSet Add (string prototype, OptionAction action) + { + options.Add (prototype, action); + return this; + } + + public CommandSet Add (string prototype, string description, OptionAction action) + { + options.Add (prototype, description, action); + return this; + } + + public CommandSet Add (string prototype, string description, OptionAction action, bool hidden) + { + options.Add (prototype, description, action, hidden); + return this; + } + + public CommandSet Add (string prototype, Action action) + { + options.Add (prototype, null, action); + return this; + } + + public CommandSet Add (string prototype, string description, Action action) + { + options.Add (prototype, description, action); + return this; + } + + public CommandSet Add (string prototype, OptionAction action) + { + options.Add (prototype, action); + return this; + } + + public CommandSet Add (string prototype, string description, OptionAction action) + { + options.Add (prototype, description, action); + return this; + } + + public CommandSet Add (ArgumentSource source) + { + options.Add (source); + return this; + } + + public CommandSet Add (CommandSet nestedCommands) + { + if (nestedCommands == null) + throw new ArgumentNullException (nameof (nestedCommands)); + + if (NestedCommandSets == null) { + NestedCommandSets = new List (); + } + + if (!AlreadyAdded (nestedCommands)) { + NestedCommandSets.Add (nestedCommands); + foreach (var o in nestedCommands.options) { + if (o is CommandOption c) { + options.Add (new CommandOption (c.Command, $"{nestedCommands.Suite} {c.CommandName}")); + } + else { + options.Add (o); + } + } + } + + nestedCommands.options = this.options; + nestedCommands.outWriter = this.outWriter; + nestedCommands.errorWriter = this.errorWriter; + + return this; + } + + bool AlreadyAdded (CommandSet value) + { + if (value == this) + return true; + if (NestedCommandSets == null) + return false; + foreach (var nc in NestedCommandSets) { + if (nc.AlreadyAdded (value)) + return true; + } + return false; + } + + public IEnumerable GetCompletions (string prefix = null) + { + string rest; + ExtractToken (ref prefix, out rest); + + foreach (var command in this) { + if (command.Name.StartsWith (prefix, StringComparison.OrdinalIgnoreCase)) { + yield return command.Name; + } + } + + if (NestedCommandSets == null) + yield break; + + foreach (var subset in NestedCommandSets) { + if (subset.Suite.StartsWith (prefix, StringComparison.OrdinalIgnoreCase)) { + foreach (var c in subset.GetCompletions (rest)) { + yield return $"{subset.Suite} {c}"; + } + } + } + } + + static void ExtractToken (ref string input, out string rest) + { + rest = ""; + input = input ?? ""; + + int top = input.Length; + for (int i = 0; i < top; i++) { + if (char.IsWhiteSpace (input [i])) + continue; + + for (int j = i; j < top; j++) { + if (char.IsWhiteSpace (input [j])) { + rest = input.Substring (j).Trim (); + input = input.Substring (i, j).Trim (); + return; + } + } + rest = ""; + if (i != 0) + input = input.Substring (i).Trim (); + return; + } + } + + public int Run (IEnumerable arguments) + { + if (arguments == null) + throw new ArgumentNullException (nameof (arguments)); + + this.showHelp = false; + if (help == null) { + help = new HelpCommand (); + AddCommand (help); + } + Action setHelp = v => showHelp = v != null; + if (!options.Contains ("help")) { + options.Add ("help", "", setHelp, hidden: true); + } + if (!options.Contains ("?")) { + options.Add ("?", "", setHelp, hidden: true); + } + var extra = options.Parse (arguments); + if (extra.Count == 0) { + if (showHelp) { + return help.Invoke (extra); + } + Out.WriteLine (options.MessageLocalizer ($"Use `{Suite} help` for usage.")); + return 1; + } + var command = GetCommand (extra); + if (command == null) { + help.WriteUnknownCommand (extra [0]); + return 1; + } + if (showHelp) { + if (command.Options?.Contains ("help") ?? true) { + extra.Add ("--help"); + return command.Invoke (extra); + } + command.Options.WriteOptionDescriptions (Out); + return 0; + } + return command.Invoke (extra); + } + + internal Command GetCommand (List extra) + { + return TryGetLocalCommand (extra) ?? TryGetNestedCommand (extra); + } + + Command TryGetLocalCommand (List extra) + { + var name = extra [0]; + if (Contains (name)) { + extra.RemoveAt (0); + return this [name]; + } + for (int i = 1; i < extra.Count; ++i) { + name = name + " " + extra [i]; + if (!Contains (name)) + continue; + extra.RemoveRange (0, i+1); + return this [name]; + } + return null; + } + + Command TryGetNestedCommand (List extra) + { + if (NestedCommandSets == null) + return null; + + var nestedCommands = NestedCommandSets.Find (c => c.Suite == extra [0]); + if (nestedCommands == null) + return null; + + var extraCopy = new List (extra); + extraCopy.RemoveAt (0); + if (extraCopy.Count == 0) + return null; + + var command = nestedCommands.GetCommand (extraCopy); + if (command != null) { + extra.Clear (); + extra.AddRange (extraCopy); + return command; + } + return null; + } + } + + public class HelpCommand : Command + { + public HelpCommand () + : base ("help", help: "Show this message and exit") + { + } + + public override int Invoke (IEnumerable arguments) + { + var extra = new List (arguments ?? new string [0]); + var _ = CommandSet.Options.MessageLocalizer; + if (extra.Count == 0) { + CommandSet.Options.WriteOptionDescriptions (CommandSet.Out); + return 0; + } + var command = CommandSet.GetCommand (extra); + if (command == this || extra.Contains ("--help")) { + CommandSet.Out.WriteLine (_ ($"Usage: {CommandSet.Suite} COMMAND [OPTIONS]")); + CommandSet.Out.WriteLine (_ ($"Use `{CommandSet.Suite} help COMMAND` for help on a specific command.")); + CommandSet.Out.WriteLine (); + CommandSet.Out.WriteLine (_ ($"Available commands:")); + CommandSet.Out.WriteLine (); + var commands = GetCommands (); + commands.Sort ((x, y) => string.Compare (x.Key, y.Key, StringComparison.OrdinalIgnoreCase)); + foreach (var c in commands) { + if (c.Key == "help") { + continue; + } + CommandSet.Options.WriteCommandDescription (CommandSet.Out, c.Value, c.Key); + } + CommandSet.Options.WriteCommandDescription (CommandSet.Out, CommandSet.help, "help"); + return 0; + } + if (command == null) { + WriteUnknownCommand (extra [0]); + return 1; + } + if (command.Options != null) { + command.Options.WriteOptionDescriptions (CommandSet.Out); + return 0; + } + return command.Invoke (new [] { "--help" }); + } + + List> GetCommands () + { + var commands = new List> (); + + foreach (var c in CommandSet) { + commands.Add (new KeyValuePair(c.Name, c)); + } + + if (CommandSet.NestedCommandSets == null) + return commands; + + foreach (var nc in CommandSet.NestedCommandSets) { + AddNestedCommands (commands, "", nc); + } + + return commands; + } + + void AddNestedCommands (List> commands, string outer, CommandSet value) + { + foreach (var v in value) { + commands.Add (new KeyValuePair($"{outer}{value.Suite} {v.Name}", v)); + } + if (value.NestedCommandSets == null) + return; + foreach (var nc in value.NestedCommandSets) { + AddNestedCommands (commands, $"{outer}{value.Suite} ", nc); + } + } + + internal void WriteUnknownCommand (string unknownCommand) + { + CommandSet.Error.WriteLine (CommandSet.Options.MessageLocalizer ($"{CommandSet.Suite}: Unknown command: {unknownCommand}")); + CommandSet.Error.WriteLine (CommandSet.Options.MessageLocalizer ($"{CommandSet.Suite}: Use `{CommandSet.Suite} help` for usage.")); + } + } +} + diff --git a/src/Uno.Wasm.Bootstrap/build/packager.cs b/src/Uno.Wasm.Bootstrap/Packager/packager.cs similarity index 100% rename from src/Uno.Wasm.Bootstrap/build/packager.cs rename to src/Uno.Wasm.Bootstrap/Packager/packager.cs diff --git a/src/Uno.Wasm.Bootstrap/Packager/packager.proj b/src/Uno.Wasm.Bootstrap/Packager/packager.proj new file mode 100644 index 0000000..e959744 --- /dev/null +++ b/src/Uno.Wasm.Bootstrap/Packager/packager.proj @@ -0,0 +1,14 @@ + + + + net462 + Exe + false + + + + + + + + diff --git a/src/Uno.Wasm.Bootstrap/Uno.Wasm.Bootstrap.csproj b/src/Uno.Wasm.Bootstrap/Uno.Wasm.Bootstrap.csproj index ccb92da..b1308d4 100644 --- a/src/Uno.Wasm.Bootstrap/Uno.Wasm.Bootstrap.csproj +++ b/src/Uno.Wasm.Bootstrap/Uno.Wasm.Bootstrap.csproj @@ -42,7 +42,7 @@ true build - + true build @@ -125,4 +125,19 @@ + + + <_packagerIntermediatePath>$(IntermediateOutputPath)packager_build/ + + + <_PackagerSource Include="Packager/*.*" /> + + + + + + + + + diff --git a/src/Uno.Wasm.Bootstrap/UnoInstallSDKTask.cs b/src/Uno.Wasm.Bootstrap/UnoInstallSDKTask.cs index 3d87a9f..4801e91 100644 --- a/src/Uno.Wasm.Bootstrap/UnoInstallSDKTask.cs +++ b/src/Uno.Wasm.Bootstrap/UnoInstallSDKTask.cs @@ -114,53 +114,12 @@ namespace Uno.Wasm.Bootstrap Process.Start("chmod", $"-R +x {SdkPath}"); } } - // - // Disable the packager override as the local updates have been merged into mono master. - // - // Download the corresponding packager - var packagerPath = Path.Combine(SdkPath, "packager_build"); - var packagerFilePath = Path.Combine(packagerPath, "packager.cs"); - - Directory.CreateDirectory(packagerPath); - - if (!File.Exists(packagerFilePath)) + if (!string.IsNullOrEmpty(PackagerOverrideFile)) { - Log.LogMessage($"Overriding packager for {sdkName} with {PackagerOverrideFile}"); - File.Copy(PackagerOverrideFile, packagerFilePath, true); + PackagerBinPath = Path.Combine(SdkPath, "packager2.exe"); + File.Copy(PackagerOverrideFile, PackagerBinPath, true); } - - PackagerBinPath = Path.Combine(SdkPath, "packager2.exe"); - - var projectFile = $@" - - - net462 - Exe - .. - false - - - - - - - {SdkPath}/Mono.Cecil.dll - - - {SdkPath}/Mono.Options.dll - - - - "; - - PackagerProjectFile = Path.Combine(packagerPath, "packager2.csproj"); - File.WriteAllText(PackagerProjectFile, projectFile); - - var thisPath = Path.Combine(Path.GetDirectoryName(new Uri(GetType().Assembly.Location).LocalPath)); - - File.Copy(Path.Combine(thisPath, "Mono.Cecil.dll"), Path.Combine(SdkPath, "Mono.Cecil.dll"), true); - File.Copy(Path.Combine(thisPath, "Mono.Options.dll"), Path.Combine(SdkPath, "Mono.Options.dll"), true); } catch (Exception e) { diff --git a/src/Uno.Wasm.Bootstrap/build/Uno.Wasm.Bootstrap.targets b/src/Uno.Wasm.Bootstrap/build/Uno.Wasm.Bootstrap.targets index cd5102c..d47ae95 100644 --- a/src/Uno.Wasm.Bootstrap/build/Uno.Wasm.Bootstrap.targets +++ b/src/Uno.Wasm.Bootstrap/build/Uno.Wasm.Bootstrap.targets @@ -113,15 +113,13 @@ MonoWasmAOTSDKUri="$(MonoWasmAOTSDKUri)" IsOSUnixLike="$([MSBuild]::IsOsUnixLike())" MonoRuntimeExecutionMode="$(WasmShellMonoRuntimeExecutionMode)" - PackagerOverrideFile="$(MSBuildThisFileDirectory)/packager.cs" + PackagerOverrideFile="$(MSBuildThisFileDirectory)/packager2.exe" MonoTempFolder="$(WasmShellMonoTempFolder)"> - -