diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 88ccb6c..3de1292 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -47,9 +47,6 @@ jobs:
- name: "dotnet pack: ${{ env.VER_STR }}"
run: dotnet pack Src/RT.CommandLine.csproj --configuration Release -p:InformationalVersion="${{env.VER_STR}}" -p:VersionPrefix=${{env.VER_NUM}} -p:VersionSuffix=${{env.VER_SUF}} -p:FileVersion=${{env.VER_NUM}} -p:AssemblyVersion=${{env.VER_NUM}} -o Publish
- - name: "dotnet pack Lingo: ${{ env.VER_STR }}"
- run: dotnet pack SrcLingo/RT.CommandLine.Lingo.csproj --configuration Release -p:InformationalVersion="${{env.VER_STR}}" -p:VersionPrefix=${{env.VER_NUM}} -p:VersionSuffix=${{env.VER_SUF}} -p:FileVersion=${{env.VER_NUM}} -p:AssemblyVersion=${{env.VER_NUM}} -o Publish
-
- name: Push to NuGet
run: dotnet nuget push Publish/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json
diff --git a/External/RT.Util b/External/RT.Util
index 0b24fb8..cd73ade 160000
--- a/External/RT.Util
+++ b/External/RT.Util
@@ -1 +1 @@
-Subproject commit 0b24fb8af9318edf2fb1b45d6d97b728133fcde8
+Subproject commit cd73ade4acf2f491d0f7a49a5c2e50392a1ad7f0
diff --git a/RT.CommandLine.sln b/RT.CommandLine.sln
index f41c714..636e524 100644
--- a/RT.CommandLine.sln
+++ b/RT.CommandLine.sln
@@ -4,8 +4,6 @@ VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RT.CommandLine", "Src\RT.CommandLine.csproj", "{DED68431-337E-439C-94E4-ACE2E5363178}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RT.CommandLine.Lingo", "SrcLingo\RT.CommandLine.Lingo.csproj", "{DC6401A1-14F1-44E3-A121-43CC2F192809}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RT.CommandLine.Tests", "Tests\RT.CommandLine.Tests.csproj", "{294D2B02-3FA6-4B9A-82BF-F80396943892}"
EndProject
Global
@@ -18,10 +16,6 @@ Global
{DED68431-337E-439C-94E4-ACE2E5363178}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DED68431-337E-439C-94E4-ACE2E5363178}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DED68431-337E-439C-94E4-ACE2E5363178}.Release|Any CPU.Build.0 = Release|Any CPU
- {DC6401A1-14F1-44E3-A121-43CC2F192809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DC6401A1-14F1-44E3-A121-43CC2F192809}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DC6401A1-14F1-44E3-A121-43CC2F192809}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DC6401A1-14F1-44E3-A121-43CC2F192809}.Release|Any CPU.Build.0 = Release|Any CPU
{294D2B02-3FA6-4B9A-82BF-F80396943892}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{294D2B02-3FA6-4B9A-82BF-F80396943892}.Debug|Any CPU.Build.0 = Debug|Any CPU
{294D2B02-3FA6-4B9A-82BF-F80396943892}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/Src/CommandLine.cs b/Src/CommandLine.cs
index af72185..ad6789f 100644
--- a/Src/CommandLine.cs
+++ b/Src/CommandLine.cs
@@ -1,4 +1,4 @@
-using System.Reflection;
+using System.Reflection;
using RT.Internal;
using RT.PostBuild;
using RT.Util;
diff --git a/Src/RT.CommandLine.csproj b/Src/RT.CommandLine.csproj
index f94364e..286b1fa 100644
--- a/Src/RT.CommandLine.csproj
+++ b/Src/RT.CommandLine.csproj
@@ -16,8 +16,8 @@
-
-
+
+
diff --git a/SrcLingo/CommandLine.cs b/SrcLingo/CommandLine.cs
deleted file mode 100644
index 755f000..0000000
--- a/SrcLingo/CommandLine.cs
+++ /dev/null
@@ -1,2127 +0,0 @@
-using System.Diagnostics;
-using System.Reflection;
-using RT.Lingo;
-using RT.PostBuild;
-using RT.Serialization;
-using RT.Util;
-using RT.Util.Consoles;
-using RT.Util.ExtensionMethods;
-using RT.Util.Text;
-
-namespace RT.CommandLine.Lingo;
-
-///
-/// Implements a command-line parser that can turn the commands and options specified by the user on the command line into
-/// a strongly-typed instance of a specific class. See remarks for more details.
-///
-///
-/// The following conditions must be met by the class wishing to receive the options and parameters:
-///
-/// -
-/// It must be a reference type (a class), must have , and it must have a
-/// parameterless constructor (unless it has subcommands, see below).
-/// -
-///
-/// Every field in the class must have one of the following custom attributes:
-///
-/// -
-/// (allowed for all supported types except bool) — specifies
-/// that the parameter is positional; the user specifies the value(s) in place without an option preceding
-/// it.
-/// -
-/// (allowed for all supported types) — specifies that the parameter invoked
-/// by an option, e.g. -x, which may or may not be followed by a value. (This does not imply that
-/// the parameter is necessarily optional.)
-/// -
-/// (allowed for enum types only) — specifies that the parameter can be
-/// invoked by one of several options, which are specified on the enum values in the enum type.
-/// -
-/// — specifies that shall completely ignore
-/// the field.
-/// -
-///
-/// Each field may optionally have any of the following custom attributes:
-///
-/// -
-/// (allowed for all supported types except bool) — specifies
-/// that the parameter must be specified by the user. For a string[] field, it means that at least
-/// one value must be specified.
-/// -
-/// — specifies that the option or command does not appear in the help
-/// screen generated by CommandLineParser.
-/// -
-///
-/// Each field in the class must be of one of the following types:
-///
-/// -
-/// string, any integer type, float, double, or any nullable version of these. The
-/// field can be positional () or not ().
-/// -
-/// string[]. The field can be positional () or not (), but if it is positional, it must be the last positional parameter.
-/// -
-/// bool. The field must have an and cannot be positional or
-/// mandatory.
-/// -
-///
-/// Any enum type. There are three ways that enum types can be used. To explain these, the following
-/// enum type declaraction is used as an example:
-///
-/// enum OutputFormat { PlainText, Xml }
-///
-/// -
-///
-/// — The user can specify a single parameter (e.g.
-/// plain or xml) to select an enum value. Every value in the enum type must
-/// have a to specify the name by which that enum value is
-/// selected:
-///
-/// enum OutputFormat
-/// {
-/// [CommandName("plain")]
-/// PlainText,
-/// [CommandName("xml")]
-/// Xml
-/// }
-/// -
-/// — The user can select an enum value by specifying an option
-/// followed by a parameter that identifies the enum value (e.g. -f plain or -f
-/// xml). As above, every value in the enum type must have a to specify the name by which that enum value is selected.
-/// -
-///
-/// — The user can select an enum value by specifying just
-/// an option (e.g. -p or -x). Every value in the enum type must have an to specify the option by which that enum value is selected:
-///
-/// enum OutputFormat
-/// {
-/// [Option("-p", "--plain")]
-/// PlainText,
-/// [Option("-x", "--xml")]
-/// Xml
-/// }
-///
-/// A parameter on the attribute determines whether the user is allowed to specify only one
-/// enum value or multiple (which will be combined using bitwise or).
-/// -
-/// If the field is optional, the enum value that corresponds to the field’s initial (default)
-/// value may omit the or .
-/// -
-///
-/// Every field must have documentation or be explicitly marked with
-/// (except for fields that use or ). For
-/// every field whose type is an enum type, the values in the enum type must also have documentation or , except for the enum value that corresponds to the field’s default value if
-/// the field is not mandatory.
-///
-/// Documentation is provided in one of the following ways:
-///
-/// -
-/// Monolingual, translation-agnostic (unlocalizable) applications use the to specify documentation directly.
-/// -
-///
-/// Translatable applications must declare methods with the following signature:
-///
-/// static string FieldNameDoc(Translation)
-///
-/// The first parameter must be of the same type as the object passed in for the applicationTr
-/// parameter of . The name of the method is the name of the field or enum value
-/// followed by Doc. The return value is the translated string.
-/// -
-/// and can be used together. However, a
-/// positional field can only be made mandatory if all the positional fields preceding it are also mandatory.
-///
-/// Subcommands can be implemented by using derived classes. For example, in order to allow the user to invoke
-/// commands of the following form:
-///
-/// MyTool.exe create new_item
-/// MyTool.exe rename old_name new_name
-///
-/// you would declare the following classes:
-///
-/// [CommandLine]
-/// abstract class CmdBase { }
-///
-/// [CommandName("create")]
-/// sealed class CmdCreate : CmdBase
-/// {
-/// [IsPositional, IsMandatory]
-/// public string ItemName;
-/// }
-///
-/// [CommandName("rename")]
-/// sealed class CmdRename : CmdBase
-/// {
-/// [IsPositional, IsMandatory]
-/// public string OldName;
-/// [IsPositional, IsMandatory]
-/// public string NewName;
-/// }
-///
-/// In this example, we have omitted the documentation attributes, but in practice they would be required. The
-/// following points are of note here:
-///
-/// -
-///
-/// The class CmdBase is abstract to indicate that the subcommand is mandatory. The class could be made
-/// non-abstract to indicate that the subcommand is optional.
-/// -
-///
-/// The class CmdBase does not need to have a parameterless constructor because only CmdCreate
-/// and CmdRename would actually be instantiated by CommandLineParser. However, if it were
-/// non-abstract, it would need a parameterless constructor.
-/// -
-///
-/// Parameters that pertain to all subcommands can be added in CmdBase and the user would specify those
-/// before the command name.
-/// -
-///
-/// You can have any arbitrary multi-level class hierarchy. Only classes marked with become subcommands.
-public static class CommandLineParser
-{
- ///
- /// Parses the specified command-line arguments into an instance of the specified type. See the remarks section of the
- /// documentation for for features and limitations.
- ///
- /// The class containing the fields and attributes which define the command-line syntax.
- ///
- /// The command-line arguments to be parsed.
- ///
- /// Specifies the application’s translation object which contains the localised strings that document the command-line
- /// options and commands. This object is passed in to the FieldNameDoc methods described in the documentation
- /// for . This should be null for monoligual applications.
- ///
- /// Specifies a callback which is invoked on every documentation string retrieved from the s to generate the help text. This callback can modify the text arbitrarily.
- ///
- /// An instance of the class containing the options and parameters specified by the user
- /// on the command line.
- public static TArgs Parse(string[] args, TranslationBase applicationTr = null, Func helpProcessor = null)
- {
- return (TArgs) parseCommandLine(getCommandInfo(typeof(TArgs)), args, 0, applicationTr, helpProcessor);
- }
-
- ///
- /// Parses the specified command-line arguments into an instance of the specified type. In case of failure, prints
- /// usage information to the console and returns default(TArgs). See the remarks section of the documentation
- /// for for features and limitations.
- ///
- /// The class containing the fields and attributes which define the command-line syntax.
- ///
- /// The command-line arguments to be parsed.
- ///
- /// Specifies the application’s translation object which contains the localized strings that document the command-line
- /// options and commands. This object is passed in to the FieldNameDoc() methods described in the documentation for
- /// . This should be null for monoligual applications.
- ///
- /// Specifies a translation object that contains the localized strings for CommandLineParser’s own text.
- ///
- /// Specifies a callback which is invoked on every documentation string retrieved from the s to generate the help text. This callback can modify the text arbitrarily.
- ///
- /// An instance of the class containing the options and parameters specified by the user
- /// on the command line.
- public static TArgs ParseOrWriteUsageToConsole(string[] args, TranslationBase applicationTr = null, Translation cmdLineTr = null, Func helpProcessor = null)
- {
- try
- {
- return Parse(args, applicationTr, helpProcessor);
- }
- catch (CommandLineParseException e)
- {
- e.WriteUsageInfoToConsole(applicationTr, cmdLineTr, helpProcessor);
- return default(TArgs);
- }
- }
-
- private static CommandInfo getCommandInfo(Type type)
- {
- return new CommandInfo { Elements = getCommandLineElements(type).ToArray(), Type = type };
- }
-
- private static IEnumerable getCommandLineElements(Type type)
- {
- var haveSeenOptionalPositional = false;
- foreach (var fieldForeach in getEligibleFields(type))
- {
- var field = fieldForeach; // This is necessary for the lambda expressions to work
-
- if (field.IsDefined())
- continue;
-
- var positional = field.IsDefined();
- var mandatory = field.IsDefined();
-
- if (positional && mandatory && haveSeenOptionalPositional)
- throw new InternalErrorException("Cannot have positional mandatory parameter after a positional optional one.");
-
- if (positional && !mandatory)
- haveSeenOptionalPositional = true;
-
- // ### ENUM fields
- if (field.FieldType.IsEnum)
- {
- // ### ENUM fields, positional
- if (positional)
- yield return new CmdLineEnumFieldPositional { IsMandatory = mandatory, Field = field };
-
- // ### ENUM fields, non-positional
- else
- {
- // Take care of both option+name scheme (e.g. “-x foo -x bar”) and option scheme (e.g. “-x -y”)
- var behavior = field.GetCustomAttributes().Select(eoa => eoa.Behavior).FirstOrDefault(EnumBehavior.SingleValue);
- var underlyingType = field.FieldType.GetEnumUnderlyingType();
- var option = field.GetCustomAttributes().FirstOrDefault();
- if (option == null)
- {
- var enumFields = field.FieldType.GetFields(BindingFlags.Static | BindingFlags.Public)
- .SelectMany(f => f.GetOrderedOptionAttributeNames().Select(name => Ut.KeyValuePair(name, f.GetRawConstantValue())))
- .ToArray();
- yield return new CmdLineEnumOptions
- {
- Behavior = behavior,
- Field = field,
- IsMandatory = mandatory,
- Options = enumFields.Select(inf => inf.Key).ToArray(),
- OptionToValue = enumFields.ToDictionary()
- };
- }
- else
- {
- yield return new CmdLineEnumOptionWithNames
- {
- Behavior = behavior,
- Options = option.Names,
- Field = field,
- IsMandatory = mandatory,
- NameToValue = field.FieldType.GetFields(BindingFlags.Static | BindingFlags.Public)
- .SelectMany(f => f.GetCustomAttributes().SelectMany(cna => cna.Names).Select(name => Ut.KeyValuePair(name, f.GetRawConstantValue())))
- .ToDictionary()
- };
- }
- }
- }
-
- // ### BOOL fields
- else if (field.FieldType == typeof(bool))
- yield return new CmdLineBoolOption { IsMandatory = mandatory, Field = field, Options = field.GetOrderedOptionAttributeNames() };
-
- // ### STRING and INTEGER fields (including nullable)
- else if (field.FieldType == typeof(string) || ExactConvert.IsTrueIntegerType(field.FieldType) || ExactConvert.IsTrueIntegerNullableType(field.FieldType) ||
- field.FieldType == typeof(float) || field.FieldType == typeof(float?) || field.FieldType == typeof(double) || field.FieldType == typeof(double?))
- {
- if (positional)
- yield return new CmdLineOtherPositional { Field = field, IsMandatory = mandatory };
- else
- yield return new CmdLineOtherOption { Field = field, IsMandatory = mandatory, Options = field.GetOrderedOptionAttributeNames() };
- }
-
- // ### STRING[] fields
- else if (field.FieldType == typeof(string[]))
- {
- if (positional)
- yield return new CmdLineStringArrayPositional { Field = field, IsMandatory = mandatory };
- else
- yield return new CmdLineStringArrayOption { Field = field, IsMandatory = mandatory, Options = field.GetOrderedOptionAttributeNames() };
- }
- else
- // This only happens if the post-build check didn't run
- throw new InternalErrorException("{0}.{1} is not of a supported type.".Fmt(type.FullName, field.Name));
- }
-
- // ### Command names
-
- // See if the class has subclasses that represent subcommands
- var derivedTypes = getDirectSubcommands(type);
- if (derivedTypes.Length > 0)
- {
- yield return new CmdLineSubcommand
- {
- IsMandatory = type.IsAbstract,
- Type = type,
- Subcommands = derivedTypes.Select(t => new SubcommandInfo
- {
- Elements = getCommandLineElements(t).ToArray(),
- Names = t.GetCustomAttributes().First().Names,
- Type = t
- }).ToArray()
- };
- }
- }
-
- private static FieldInfo[] getEligibleFields(Type type)
- {
- var bindingFlags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
-
- // Get all fields from the type
- var fields = type.GetFields(bindingFlags).ToList();
-
- // Keep adding fields from the base type until we find one with CommandNameAttribute or CommandLineAttribute
- var testType = type.BaseType;
- while (testType != typeof(object) && !testType.IsDefined() && !testType.IsDefined())
- {
- fields.AddRange(testType.GetFields(bindingFlags));
- testType = testType.BaseType;
- }
-
- return fields.ToArray();
- }
-
- private static Type[] getDirectSubcommands(Type type)
- {
- var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(asm => asm.GetTypes()).Where(t => !t.IsGenericTypeDefinition && t.IsSubclassOf(type) && t.IsDefined()).ToList();
- types.RemoveAll(t => types.Any(t.IsSubclassOf));
- return types.ToArray();
- }
-
- private static object parseCommandLine(CommandInfo cmd, string[] args, int i, TranslationBase applicationTr, Func helpProcessor)
- {
- if (i < args.Length)
- if (args[i] == "-?" || args[i] == "/?" || args[i] == "--?" || args[i] == "/h" || args[i] == "--help" || args[i] == "-help" || args[i] == "help")
- throw new CommandLineHelpRequestedException(cmd);
-
- var elements = cmd.Elements;
- var missingMandatories = new HashSet(elements.Where(e => e.IsMandatory));
- var positionals = elements.Where(e => e.IsPositional).ToQueue();
- var actionsToPerform = new List>();
-
- bool suppressOptions = false;
- object ret = null;
-
- while (i < args.Length)
- {
- if (args[i] == "--" && !suppressOptions)
- {
- suppressOptions = true;
- i++;
- }
- else if (!suppressOptions && args[i].StartsWith('-'))
- {
- CmdLineElement el = null;
- foreach (var element in elements.Where(e => !e.IsPositional))
- {
- if (element.ProcessParameter(args, ref i, actionsToPerform, suppressOptions, helpProcessor, cmd))
- {
- el = element;
- break;
- }
- }
- if (el == null)
- throw new UnrecognizedCommandOrOptionException(args[i], cmd);
- missingMandatories.Remove(el);
- }
- else // positional
- {
- if (positionals.Count == 0)
- throw new UnexpectedArgumentException(args.Subarray(i), cmd);
- var positional = positionals.Dequeue();
- // This should only return true or throw an exception
- Ut.Assert(positional.ProcessParameter(args, ref i, actionsToPerform, suppressOptions, helpProcessor, cmd));
- if (positional is CmdLineSubcommand)
- {
- // Special case: recursive call
- ret = parseCommandLine(((CmdLineSubcommand) positional).Subcommand, args, i, applicationTr, helpProcessor);
- i = args.Length;
- }
- else if (positional is CmdLineStringArrayPositional)
- {
- // Special case: this positional remains in the queue forever
- positionals.Enqueue(positional);
- }
- missingMandatories.Remove(positional);
- }
- }
-
- if (positionals.Count > 0)
- positionals.Dequeue().ProcessEndOfParameters(actionsToPerform, cmd);
-
- foreach (var m in missingMandatories)
- m.ProcessEndOfParameters(actionsToPerform, cmd);
-
- if (ret == null) // there was no subcommand
- ret = Activator.CreateInstance(cmd.Type, true);
-
- foreach (var action in actionsToPerform)
- action(ret);
-
- Type[] typeParam;
- ConsoleColoredString error = null;
- if (cmd.Type.TryGetGenericParameters(typeof(ICommandLineValidatable<>), out typeParam))
- {
- var tp = typeof(ICommandLineValidatable<>).MakeGenericType(typeParam[0]);
- if (typeParam[0] != applicationTr.GetType())
- throw new CommandLineValidationException(@"The type {0} implements {1}, but ApplicationTr is of type {2}. If ApplicationTr is right, the interface implemented should be {3}.".Fmt(
- cmd.Type.FullName,
- tp.FullName,
- applicationTr.GetType().FullName,
- typeof(ICommandLineValidatable<>).MakeGenericType(applicationTr.GetType()).FullName
- ), cmd);
-
- var meth = tp.GetMethod("Validate");
- if (meth == null || !meth.GetParameters().Select(p => p.ParameterType).SequenceEqual(new Type[] { typeParam[0] }))
- throw new CommandLineValidationException(@"Couldn’t find the Validate method in the {0} type.".Fmt(tp.FullName), cmd);
-
- error = (ConsoleColoredString) meth.Invoke(ret, new object[] { applicationTr });
- }
- else if (typeof(ICommandLineValidatable).IsAssignableFrom(cmd.Type))
- error = ((ICommandLineValidatable) ret).Validate();
-
- if (error != null)
- throw new CommandLineValidationException(error, cmd);
-
- return ret;
- }
-
- internal static ConsoleColoredString GenerateHelp(CommandInfo cmd, int? wrapWidth = null, TranslationBase applicationTr = null, Translation tr = null, Func helpProcessor = null)
- {
- helpProcessor = helpProcessor ?? (s => s);
-
- if (tr == null)
- tr = new Translation();
-
- int leftMargin = 3;
- var wrapToWidth = wrapWidth ?? ConsoleUtil.WrapToWidth();
-
- var helpString = new List();
- var commandNameAttr = cmd.Type.GetCustomAttributes().FirstOrDefault();
- string commandName = commandNameAttr == null ? Process.GetCurrentProcess().ProcessName : "... " + commandNameAttr.Names.OrderByDescending(c => c.Length).First();
-
- //
- // ## CONSTRUCT THE “USAGE” LINE
- //
- var usage = new List();
- usage.Add(new ConsoleColoredString(tr.Usage + " ", CmdLineColor.UsageLinePrefix));
- usage.Add(commandName);
-
- // Options must be listed before positionals because if a positional is a subcommand, all the options must be before it.
- // Optional positionals must come after mandatory positionals because that is the order they must be specified in.
- // If any mandatory positional is a subcommand, then you can’t have any optional positionals anyway.
- var elements = cmd.Elements.Order().ToArray();
- foreach (var elem in elements)
- usage.Add(" " + elem.UsageString);
-
- // Word-wrap the usage line
- foreach (var line in new ConsoleColoredString(usage.ToArray()).WordWrap(wrapToWidth, tr.Usage.Translation.Length + 1))
- {
- helpString.Add(line);
- helpString.Add(ConsoleColoredString.NewLine);
- }
- helpString.Add(ConsoleColoredString.NewLine);
-
- //
- // ## CONSTRUCT THE TABLES
- //
- var anyCommandsWithSuboptions = false;
-
- // Word-wrap the documentation for the command (if any)
- var doc = cmd.Type.GetDocumentation(cmd.Type, applicationTr, helpProcessor);
- foreach (var line in doc.WordWrap(wrapToWidth))
- {
- helpString.Add(line);
- helpString.Add(ConsoleColoredString.NewLine);
- }
-
- // Table of required parameters
- if (elements.Any(e => e.IsMandatory))
- {
- var requiredParamsTable = new TextTable { MaxWidth = wrapToWidth - leftMargin, ColumnSpacing = 3, RowSpacing = 1, LeftMargin = leftMargin };
- int requiredRow = 0;
- foreach (var f in elements.Where(e => e.IsMandatory))
- anyCommandsWithSuboptions |= f.AddHelpRow(requiredParamsTable, ref requiredRow, applicationTr, helpProcessor);
-
- helpString.Add(ConsoleColoredString.NewLine);
- helpString.Add(new ConsoleColoredString(tr.ParametersHeader, CmdLineColor.HelpHeading));
- helpString.Add(ConsoleColoredString.NewLine);
- helpString.Add(ConsoleColoredString.NewLine);
- requiredParamsTable.RemoveEmptyColumns();
- helpString.Add(requiredParamsTable.ToColoredString());
- }
-
- // Table of optional parameters
- if (elements.Any(e => !e.IsMandatory))
- {
- var optionalParamsTable = new TextTable { MaxWidth = wrapToWidth - leftMargin, ColumnSpacing = 3, RowSpacing = 1, LeftMargin = leftMargin };
- int optionalRow = 0;
- foreach (var f in elements.Where(e => !e.IsMandatory))
- anyCommandsWithSuboptions |= f.AddHelpRow(optionalParamsTable, ref optionalRow, applicationTr, helpProcessor);
-
- helpString.Add(ConsoleColoredString.NewLine);
- helpString.Add(new ConsoleColoredString(tr.OptionsHeader, CmdLineColor.HelpHeading));
- helpString.Add(ConsoleColoredString.NewLine);
- helpString.Add(ConsoleColoredString.NewLine);
- optionalParamsTable.RemoveEmptyColumns();
- helpString.Add(optionalParamsTable.ToColoredString());
- }
-
- // “This command accepts further arguments on the command line.”
- if (anyCommandsWithSuboptions)
- {
- helpString.Add(ConsoleColoredString.NewLine);
- foreach (var line in (new ConsoleColoredString("* ", CmdLineColor.SubcommandsPresentAsterisk) + ConsoleColoredString.FromEggsNode(EggsML.Parse(tr.AdditionalOptions.Translation))).WordWrap(wrapToWidth, 2))
- {
- helpString.Add(line);
- helpString.Add(ConsoleColoredString.NewLine);
- }
- }
-
- return new ConsoleColoredString(helpString.ToArray());
- }
-
- #region Post-build step check
-
- ///
- /// Performs safety checks to ensure that the structure of your command-line syntax defining class is valid according
- /// to the criteria laid out in the documentation of . Run this method as a post-build
- /// step to ensure reliability of execution. For an example of use, see .
- ///
- /// The class containing the fields and attributes which define the command-line syntax.
- ///
- /// Object to report post-build errors to.
- ///
- /// The type of the translation object, derived from , which would be passed in for the
- /// “applicationTr” parameter of at normal run-time.
- public static void PostBuildStep(IPostBuildReporter rep, Type applicationTrType)
- {
- var type = typeof(TArgs);
- if (!type.IsDefined())
- rep.Error(@"To use {0} as a command-line type, it must have the [CommandLine] attribute.".Fmt(type.FullName), (type.IsEnum ? "enum " : type.IsInterface ? "interface " : typeof(Delegate).IsAssignableFrom(type) ? "delegate " : type.IsValueType ? "struct " : "class ") + type.Name);
- postBuildStep(rep, typeof(TArgs), applicationTrType, false);
- }
-
- private static void postBuildStep(IPostBuildReporter rep, Type cmdType, Type applicationTrType, bool classDocRecommended)
- {
- if (!cmdType.IsClass)
- rep.Error(@"{0} is not a class.".Fmt(cmdType.FullName), (cmdType.IsEnum ? "enum " : cmdType.IsInterface ? "interface " : typeof(Delegate).IsAssignableFrom(cmdType) ? "delegate " : "struct ") + cmdType.Name);
-
- object instance;
- var type = cmdType;
- try
- {
- if (type.IsAbstract)
- type = type.Assembly.GetTypes().FirstOrDefault(t => t.IsClass && !t.IsAbstract && cmdType.IsAssignableFrom(t) && t.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, null, Type.EmptyTypes, null) != null);
- if (type == null)
- {
- rep.Error(@"The class {0} does not have a derived non-abstract class type with a default constructor.".Fmt(cmdType.FullName), "class " + cmdType.Name);
- return;
- }
- instance = Activator.CreateInstance(type, true);
- }
- catch (Exception e)
- {
- rep.Error(@"{0} could not be instantiated ({1}). Does it have a default constructor?".Fmt(type.FullName, e.Message), "class " + type.Name);
- return;
- }
-
- if (applicationTrType != null)
- {
- Type[] typeParam;
- if (cmdType.TryGetGenericParameters(typeof(ICommandLineValidatable<>), out typeParam) && typeParam[0] != applicationTrType)
- rep.Error(@"The type {0} implements {1}, but the ApplicationTr type is {2}. If ApplicationTr is right, the interface implemented should be {3}.".Fmt(
- cmdType.FullName,
- typeof(ICommandLineValidatable<>).MakeGenericType(typeParam[0]).FullName,
- applicationTrType.FullName,
- typeof(ICommandLineValidatable<>).MakeGenericType(applicationTrType).FullName
- ), "class " + cmdType.Name);
- }
-
- var optionTaken = new Dictionary();
- var sensibleDocMethods = new List();
- FieldInfo lastField = null;
- bool haveSeenOptionalPositional = false;
-
- checkDocumentation(rep, cmdType, cmdType, applicationTrType, sensibleDocMethods, classDocRecommended);
-
- foreach (var field in getEligibleFields(cmdType))
- {
- if (field.IsDefined())
- continue;
-
- // Every field must have one of the following
- var positional = field.IsDefined();
- var options = field.GetOrderedOptionAttributeNames();
- var enumOpt = field.GetCustomAttributes().FirstOrDefault();
-
- if (positional && lastField != null)
- rep.Error(@"The type of {0}.{1} necessitates that no positional fields can follow it in the same class.".Fmt(lastField.DeclaringType.FullName, lastField.Name), "class " + cmdType.Name, field.Name);
-
- if (!positional && options == null && enumOpt == null)
- {
- rep.Error(@"{0}.{1}: Every field must have one of the following attributes: [IsPositional], [Option], [EnumOptions] (fields of an enum type only), or [Ignore].".Fmt(field.DeclaringType.FullName, field.Name), "class " + cmdType.Name, field.Name);
- continue;
- }
-
- // EnumOptionsAttribute can only be used on enum fields
- if (enumOpt != null && !field.FieldType.IsEnum)
- rep.Error(@"{0}.{1}: Cannot use [EnumOptions] attribute on a field whose type is not an enum type.".Fmt(field.DeclaringType.FullName, field.Name), "class " + cmdType.Name, field.Name);
- // Can’t combine IsPositional and Option
- else if (positional && options != null)
- rep.Error(@"{0}.{1}: Cannot use [IsPositional] and [Option] attributes on the same field.".Fmt(field.DeclaringType.FullName, field.Name), "class " + cmdType.Name, field.Name);
- // Can’t combine IsPositional and EnumOptions
- else if (positional && enumOpt != null)
- rep.Error(@"{0}.{1}: Cannot use [IsPositional] and [EnumOptions] attributes on the same field. For a positional enum value, use only [IsPositional].".Fmt(field.DeclaringType.FullName, field.Name), "class " + cmdType.Name, field.Name);
- // Can’t have [Option] without an option name
- else if (options != null && options.Length == 0)
- rep.Error(@"{0}.{1}: An [Option] attribute must specify at least one option name.".Fmt(field.DeclaringType.FullName, field.Name), "class " + cmdType.Name, field.Name);
-
- // Option names must start with a dash
- if (options != null && options.Any(o => !o.StartsWith('-')))
- rep.Error(@"{0}.{1}: All names in an [Option] attribute must start with at least one dash ('-'). Offending option name: ""{2}""".Fmt(field.DeclaringType.FullName, field.Name, options.First(o => !o.StartsWith('-'))), "class " + cmdType.Name, field.Name);
-
- var mandatory = field.IsDefined();
-
- if (mandatory && field.IsDefined())
- rep.Error(@"{0}.{1}: Fields cannot simultaneously be mandatory and also undocumented.".Fmt(field.DeclaringType.FullName, field.Name), "class " + cmdType.Name, field.Name);
-
- if (positional && mandatory && haveSeenOptionalPositional)
- rep.Error(@"{0}.{1}: Positional fields can only be marked mandatory if all preceding positional fields are also marked mandatory.".Fmt(field.DeclaringType.FullName, field.Name), "class " + cmdType.Name, field.Name);
- else if (positional && !mandatory)
- haveSeenOptionalPositional = true;
-
- // ### ENUM fields
- if (field.FieldType.IsEnum)
- {
- // Can’t have a mandatory or a positional multi-value enum
- if (mandatory && enumOpt != null && enumOpt.Behavior == EnumBehavior.MultipleValues)
- rep.Error(@"{0}.{1}: A mandatory enum field cannot use multi-value behavior.".Fmt(field.DeclaringType.FullName, field.Name), "class " + cmdType.Name, field.Name);
- if (positional && enumOpt != null && enumOpt.Behavior == EnumBehavior.MultipleValues)
- rep.Error(@"{0}.{1}: A positional enum field cannot use multi-value behavior.".Fmt(field.DeclaringType.FullName, field.Name), "class " + cmdType.Name, field.Name);
-
- var commandsTaken = new Dictionary();
- var defaultValue = field.GetValue(instance);
-
- foreach (var enumField in field.FieldType.GetFields(BindingFlags.Static | BindingFlags.Public))
- {
- if (enumField.IsDefined())
- continue;
- // If the field is not mandatory, it is allowed to have a default value
- if (!mandatory && enumField.GetValue(null).Equals(defaultValue))
- continue;
-
- // check that the enum values all have documentation
- checkDocumentation(rep, enumField, cmdType, applicationTrType, sensibleDocMethods, true);
-
- if (options != null || positional)
- {
- // check that the enum values all have at least one CommandName, and they do not clash
- var cmdNames = enumField.GetCustomAttributes().FirstOrDefault();
- if (cmdNames == null || cmdNames.Names.Length == 0)
- rep.Error(@"{0}.{1} (used by {2}.{3}): Enum value must have a [CommandName] attribute (unless it is the field's default value and the field is optional).".Fmt(field.FieldType.FullName, enumField.Name, cmdType.FullName, field.Name), "enum " + field.FieldType.Name, enumField.Name);
- else
- checkCommandNamesUnique(rep, cmdNames.Names, commandsTaken, cmdType, field, enumField);
- }
- else
- {
- // check that the non-default enum values’ Options are present and do not clash
- var optionNames = enumField.GetOrderedOptionAttributeNames();
- if (optionNames == null || !optionNames.Any())
- rep.Error(@"{0}.{1} (used by {2}.{3}): Enum value must have an [Option] attribute with at least one option name (unless it is the field's default value and the field is optional).".Fmt(field.FieldType.FullName, enumField.Name, cmdType.FullName, field.Name), "enum " + field.FieldType.Name, enumField.Name);
- else
- checkOptionsUnique(rep, optionNames, optionTaken, cmdType, field, enumField);
- }
- }
-
- // If the enum field has an Option attribute, it needs documentation too
- if (options != null)
- checkDocumentation(rep, field, cmdType, applicationTrType, sensibleDocMethods, true);
- }
- // ### BOOL fields
- else if (field.FieldType == typeof(bool))
- {
- if (positional || mandatory)
- rep.Error(@"{0}.{1}: Fields of type bool cannot be positional or mandatory.".Fmt(cmdType.FullName, field.Name), "class " + cmdType.Name, field.Name);
- else
- // Here we have checked that the field is not positional, not an enum, and not [Ignore]’d, so it must have an [Option] attribute
- checkOptionsUnique(rep, options, optionTaken, cmdType, field);
- checkDocumentation(rep, field, cmdType, applicationTrType, sensibleDocMethods, true);
- }
- // ### STRING, STRING[], INTEGER and FLOATING fields (including nullable)
- else if (field.FieldType == typeof(string) || field.FieldType == typeof(string[]) ||
- (ExactConvert.IsTrueIntegerType(field.FieldType) && !field.FieldType.IsEnum) ||
- (ExactConvert.IsTrueIntegerNullableType(field.FieldType) && !field.FieldType.GetGenericArguments()[0].IsEnum) ||
- field.FieldType == typeof(float) || field.FieldType == typeof(float?) || field.FieldType == typeof(double) || field.FieldType == typeof(double?))
- {
- // options is null if and only if this field is positional
- if (options != null)
- checkOptionsUnique(rep, options, optionTaken, cmdType, field);
- checkDocumentation(rep, field, cmdType, applicationTrType, sensibleDocMethods, true);
-
- // A positional string[] can only be the last field
- if (positional && field.FieldType == typeof(string[]))
- lastField = field;
- }
- else
- rep.Error(@"{0}.{1} is not of a supported type. Currently accepted types are: enum types, bool, string, string[], numeric types (byte, sbyte, short, ushort, int, uint, long, ulong, float and double) and nullable numeric types.".Fmt(cmdType.FullName, field.Name), "class " + cmdType.Name, field.Name);
- }
-
- // Check for derived classes
- var subcommandsTaken = new Dictionary();
- var anyDerived = false;
- foreach (var derivedType in getDirectSubcommands(cmdType))
- {
- anyDerived = true;
- checkCommandNamesUnique(rep, derivedType.GetCustomAttributes().First().Names, subcommandsTaken, derivedType);
- postBuildStep(rep, derivedType, applicationTrType, true);
- }
-
- if (anyDerived && lastField != null)
- rep.Error(@"The type of {0}.{1} precludes the use of subcommands.".Fmt(lastField.DeclaringType.FullName, lastField.Name), "class " + cmdType.Name, lastField.Name);
-
- if (applicationTrType != null)
- // Warn if the class has unused documentation methods
- foreach (var meth in cmdType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Where(m => m.Name.EndsWith("Doc") && m.ReturnType == typeof(string) && m.GetParameters().Select(p => p.ParameterType).SequenceEqual(new Type[] { applicationTrType })))
- if (!sensibleDocMethods.Contains(meth))
- rep.Error(@"{0}.{1} looks like a documentation method, but has no corresponding field, or the corresponding field does not require documentation because it is a positional enum or has an [EnumOptions] attribute.".Fmt(cmdType.FullName, meth.Name), "class " + cmdType.Name, meth.Name);
- }
-
- private static void checkOptionsUnique(IPostBuildReporter rep, IEnumerable options, Dictionary optionTaken, Type type, FieldInfo field, FieldInfo enumField)
- {
- foreach (var option in options)
- {
- if (optionTaken.ContainsKey(option))
- {
- rep.Error(@"{0}.{1}: Option ""{2}"" is used more than once.".Fmt(field.FieldType.FullName, enumField.Name, option), "enum " + field.FieldType.Name, enumField.Name);
- rep.Error(@" -- It is used by {0}.{1}...".Fmt(type.FullName, field.Name), "class " + type.Name, field.Name);
- rep.Error(@" -- ... and by {0}.{1}.".Fmt(optionTaken[option].DeclaringType.FullName, optionTaken[option].Name), "class " + optionTaken[option].DeclaringType.Name, optionTaken[option].Name);
- }
- optionTaken[option] = field;
- }
- }
-
- private static void checkOptionsUnique(IPostBuildReporter rep, IEnumerable options, Dictionary optionTaken, Type type, FieldInfo field)
- {
- foreach (var option in options)
- {
- if (optionTaken.ContainsKey(option))
- {
- rep.Error(@"Option ""{2}"" is used by {0}.{1}...".Fmt(type.FullName, field.Name, option), "class " + type.Name, field.Name);
- rep.Error(@" -- ... and by {0}.{1}.".Fmt(optionTaken[option].DeclaringType.FullName, optionTaken[option].Name), "class " + optionTaken[option].DeclaringType.Name, optionTaken[option].Name);
- }
- optionTaken[option] = field;
- }
- }
-
- private static void checkCommandNamesUnique(IPostBuildReporter rep, string[] commandNames, Dictionary commandsTaken, Type subclass)
- {
- foreach (var cmd in commandNames)
- {
- if (commandsTaken.ContainsKey(cmd))
- {
- rep.Error(@"CommandName ""{1}"" is used by {0}...".Fmt(subclass.FullName, cmd), "class " + subclass.Name);
- rep.Error(@" -- ... and by {0}.".Fmt(commandsTaken[cmd].FullName), "class " + commandsTaken[cmd].Name);
- }
- commandsTaken[cmd] = subclass;
- }
- }
-
- private static void checkCommandNamesUnique(IPostBuildReporter rep, string[] commandNames, Dictionary commandsTaken, Type type, FieldInfo field, FieldInfo enumField)
- {
- foreach (var cmd in commandNames)
- {
- if (commandsTaken.ContainsKey(cmd))
- {
- rep.Error(@"{0}.{1}: Option ""{2}"" is used more than once.".Fmt(field.FieldType.FullName, enumField.Name, cmd), "enum " + field.FieldType.Name, enumField.Name);
- rep.Error(@" -- It is used by {0}.{1}...".Fmt(type.FullName, field.Name), "class " + type.Name, field.Name);
- rep.Error(@" -- ... and by {0}.{1}.".Fmt(commandsTaken[cmd].DeclaringType.FullName, commandsTaken[cmd].Name), "class " + commandsTaken[cmd].DeclaringType.Name, commandsTaken[cmd].Name);
- }
- commandsTaken[cmd] = enumField;
- }
- }
-
- private static Dictionary _applicationTrCacheField = null;
- private static Dictionary _applicationTrCache
- {
- get
- {
- if (_applicationTrCacheField == null)
- _applicationTrCacheField = new Dictionary();
- return _applicationTrCacheField;
- }
- }
-
- private static void checkDocumentation(IPostBuildReporter rep, MemberInfo member, Type inType, Type applicationTrType, List sensibleDocMethods, bool classDocRecommended)
- {
- if (member.IsDefined())
- return;
-
- if (!(member is Type) && inType.IsSubclassOf(member.DeclaringType))
- inType = member.DeclaringType;
-
- var attr = member.GetCustomAttributes().FirstOrDefault();
- ConsoleColoredString toCheck = null;
- if (attr != null)
- {
- try
- {
- toCheck = attr.Text; // this property can throw the first time it's accessed
- }
- catch (Exception e)
- {
- if (member is Type)
- rep.Error(@"{0}: Type documentation could not be parsed as {1}: {2}".Fmt(((Type) member).FullName, attr.OriginalFormat, e.Message), "class " + member.Name);
- else
- rep.Error(@"{0}.{1}: Field documentation could not be parsed as {2}: {3}".Fmt(member.DeclaringType.FullName, member.Name, attr.OriginalFormat, e.Message), "class " + member.DeclaringType.Name, member.Name);
- return;
- }
- }
- else if (applicationTrType != null)
- {
- var meth = inType.GetMethod(member.Name + "Doc", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { applicationTrType }, null);
- if (meth != null && meth.ReturnType == typeof(string))
- {
- sensibleDocMethods.Add(meth);
- if (!_applicationTrCache.ContainsKey(applicationTrType))
- _applicationTrCache[applicationTrType] = Activator.CreateInstance(applicationTrType);
- var appTr = _applicationTrCache[applicationTrType];
- toCheck = (string) meth.Invoke(null, new object[] { appTr });
- if (toCheck == null)
- {
- rep.Error(@"{0}." + member.Name + @"Doc() returned null.".Fmt(inType.FullName), "class " + inType.Name, member.Name + "Doc");
- return;
- }
- }
- }
-
- if (classDocRecommended && toCheck == null)
- {
- if (member is Type)
- {
- rep.Warning((@"{0} does not have any documentation. " +
- (applicationTrType == null ? "U" : @"To provide localised documentation, declare a method ""static string {1}Doc({2})"" on {3}. Otherwise, u") +
- @"se the [DocumentationLiteral] attribute to specify unlocalisable documentation. " +
- @"Use [Undocumented] to completely hide an option or command from the help screen.").Fmt(((Type) member).FullName, member.Name, applicationTrType != null ? applicationTrType.FullName : null, inType.FullName),
- ((Type) member).Namespace,
- "CommandName",
- "class " + member.Name);
- }
- else
- {
- rep.Warning((@"{0}.{1} does not have any documentation. " +
- (applicationTrType == null ? "U" : @"To provide localised documentation, declare a method ""static string {1}Doc({2})"" on {3}. Otherwise, u") +
- @"se the [DocumentationLiteral] attribute to specify unlocalisable documentation. " +
- @"Use [Undocumented] to completely hide an option or command from the help screen.").Fmt(member.DeclaringType.FullName, member.Name, applicationTrType != null ? applicationTrType.FullName : null, inType.FullName),
- member.DeclaringType.Namespace,
- (member.DeclaringType.IsEnum ? "enum " : member.DeclaringType.IsValueType ? "struct " : "class ") + member.DeclaringType.Name,
- member.Name);
- }
- return;
- }
- }
-
- #endregion
-
- ///
- /// Converts the specified parse tree into a console colored string according to
- /// CommandLineParser-specific rules. This method is used to convert
- /// documentation into colored text. See Remarks.
- ///
- /// A number of named tags have a special meaning. Any tag named after a value of results
- /// in that color. Both spellings of gray/grey are supported. The {h}...{} named tag stands for the highlight color
- /// (white). {nowrap}...{} can be placed around text that must not be broken into multiple lines by the word wrapper.
- /// The tags {field}, {option}, {command} and {enum} are used to refer to the corresponding command line syntax
- /// element, and is highlighted the same way the documentation generator would highlight references to these entities.
- public static ConsoleColoredString Colorize(RhoElement text)
- {
- var strings = new List();
- if (text.Name == null)
- colorizeChildren(text, strings, ConsoleColor.Gray, false);
- else
- colorizeWalk(text, strings, ConsoleColor.Gray, false);
- return new ConsoleColoredString(strings);
- }
-
- ///
- /// Converts the specified parse tree into a console colored string using the rules described in
- /// . This method is used to convert documentation into colored text, as well as any documentation using the
- /// legacy .
- public static ConsoleColoredString Colorize(EggsNode text)
- {
- return text.ToConsoleColoredStringWordWrap(int.MaxValue).JoinColoredString(Environment.NewLine);
- }
-
- private static void colorizeChildren(RhoElement text, List strings, ConsoleColor curColor, bool curNowrap)
- {
- foreach (var child in text.Children)
- {
- if (child is RhoText)
- strings.Add(nowrap((child as RhoText).Text, curNowrap).Color(curColor));
- else
- colorizeWalk(child as RhoElement, strings, curColor, curNowrap);
- }
- }
-
- private static void colorizeWalk(RhoElement text, List strings, ConsoleColor curColor, bool curNowrap)
- {
- var name = text.Name.ToLower();
- if (name == "field")
- {
- validateNoAttributes(text);
- validateOnlyTextChild(text);
- strings.Add("<".Color(CmdLineColor.FieldBrackets) + nowrap((text.Children[0] as RhoText).Text).Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets));
- }
- else if (name == "option")
- {
- validateNoAttributes(text);
- validateOnlyTextChild(text);
- strings.Add(nowrap((text.Children[0] as RhoText).Text).Color(CmdLineColor.Option));
- }
- else if (name == "command")
- {
- validateNoAttributes(text);
- validateOnlyTextChild(text);
- strings.Add(nowrap((text.Children[0] as RhoText).Text).Color(CmdLineColor.Command));
- }
- else if (name == "enum")
- {
- validateNoAttributes(text);
- validateOnlyTextChild(text);
- strings.Add(nowrap((text.Children[0] as RhoText).Text).Color(CmdLineColor.EnumValue));
- }
- else if (name == "nowrap")
- {
- validateNoAttributes(text);
- colorizeChildren(text, strings, curColor, true);
- }
- else if (name == "n") // newline
- {
- validateNoAttributes(text);
- validateNoChildren(text);
- strings.Add("\n");
- }
- else if (name == "h") // highlight
- {
- validateNoAttributes(text);
- colorizeChildren(text, strings, CmdLineColor.Highlight, curNowrap);
- }
- else
- {
- if (!EnumStrong.TryParse(name, out curColor, true))
- {
- if (name == "grey")
- curColor = ConsoleColor.Gray;
- else if (name == "darkgrey")
- curColor = ConsoleColor.DarkGray;
- else
- throw new ArgumentException("Unsupported element: {0}.".Fmt(text.Name), "text");
- }
- validateNoAttributes(text);
- colorizeChildren(text, strings, curColor, curNowrap);
- }
- }
-
- private static string nowrap(string text, bool doNowrap = true)
- {
- if (doNowrap)
- return text.Replace(' ', '\xA0'); // non-breaking space
- else
- return text;
- }
-
- private static void validateNoAttributes(RhoElement text)
- {
- if (text.Value != null || text.Attributes.Any())
- throw new ArgumentException("Element {0} must not have any attributes.".Fmt(text.Name), "text");
- }
-
- private static void validateNoChildren(RhoElement text)
- {
- if (text.Children.Any())
- throw new ArgumentException("Element {0} must not have any child nodes.".Fmt(text.Name), "text");
- }
-
- private static void validateOnlyTextChild(RhoElement text)
- {
- if (text.Children.Count != 1 || !(text.Children[0] is RhoText))
- throw new ArgumentException("Element {0} must only contain text, and no other elements.".Fmt(text.Name), "text");
- }
-}
-
-internal class CommandInfo
-{
- public Type Type;
- public CmdLineElement[] Elements;
-}
-
-internal sealed class SubcommandInfo : CommandInfo
-{
- public string[] Names;
-}
-
-internal abstract class CmdLineElement : IComparable
-{
- public bool IsMandatory;
- public virtual bool IsPositional { get { return false; } }
- public abstract bool ProcessParameter(string[] args, ref int i, List> actionsToPerform, bool suppressOptions, Func helpProcessor, CommandInfo cmd);
- public abstract void ProcessEndOfParameters(List> actionsToPerform, CommandInfo cmd);
- public abstract ConsoleColoredString UsageString { get; }
- public abstract bool AddHelpRow(TextTable table, ref int row, TranslationBase applicationTr, Func helpProcessor);
-
- public int CompareTo(CmdLineElement other)
- {
- // Options must be listed before positionals because if a positional is a subcommand, all the options must be before it.
- if (IsPositional && !other.IsPositional)
- return 1;
- if (!IsPositional && other.IsPositional)
- return -1;
-
- // Optional positionals must come after mandatory positionals because that is the order they must be specified in.
- // If any mandatory positional is a subcommand, then you can’t have any optional positionals anyway.
- if (IsMandatory && !other.IsMandatory)
- return -1;
- if (!IsMandatory && other.IsMandatory)
- return 1;
-
- return 0;
- }
-
- protected ConsoleColoredString FormatField(string name)
- {
- return "<".Color(CmdLineColor.FieldBrackets) + name.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets);
- }
-
- protected static bool tryConvertString(string value, List> listToAddActionTo, FieldInfo field)
- {
- object result;
-
- if (field.FieldType == typeof(string))
- result = value;
- else
- {
- Type type = field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(Nullable<>)
- ? field.FieldType.GetGenericArguments()[0]
- : field.FieldType;
- if (!ExactConvert.Try(type, value, out result))
- return false;
- }
- listToAddActionTo.Add(obj => { field.SetValue(obj, result); });
- return true;
- }
-}
-
-internal abstract class CmdLineFieldPositional : CmdLineElement
-{
- public override bool IsPositional { get { return true; } }
-
- public FieldInfo Field;
- public override void ProcessEndOfParameters(List> actionsToPerform, CommandInfo cmd)
- {
- if (IsMandatory)
- throw new MissingParameterException(Field, null, false, cmd);
- }
- public override ConsoleColoredString UsageString
- {
- get
- {
- return (IsMandatory ? "{0}" : "[{0}]").Color(CmdLineColor.OptionalityDelimiters).Fmt(
- "<".Color(CmdLineColor.FieldBrackets) + Field.Name.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets));
- }
- }
- public override bool AddHelpRow(TextTable table, ref int row, TranslationBase applicationTr, Func helpProcessor)
- {
- table.SetCell(0, row, FormatField(Field.Name), noWrap: true, colSpan: 2);
- table.SetCell(2, row, Field.GetDocumentation(Field.DeclaringType, applicationTr, helpProcessor), colSpan: 4);
- row++;
- return false;
- }
-}
-
-internal sealed class CmdLineEnumFieldPositional : CmdLineFieldPositional
-{
- public override bool ProcessParameter(string[] args, ref int i, List> actionsToPerform, bool suppressOptions, Func helpProcessor, CommandInfo cmd)
- {
- var name = args[i];
- if (name.StartsWith('-') && !suppressOptions)
- return false;
- foreach (var enumField in Field.FieldType.GetFields(BindingFlags.Static | BindingFlags.Public))
- {
- if (enumField.GetCustomAttributes().First().Names.Any(c => c.Equals(name, StringComparison.OrdinalIgnoreCase)))
- {
- actionsToPerform.Add(obj => { Field.SetValue(obj, enumField.GetValue(null)); });
- i++;
- return true;
- }
- }
- throw new UnrecognizedCommandOrOptionException(args[i], cmd);
- }
-
- public override bool AddHelpRow(TextTable table, ref int row, TranslationBase applicationTr, Func helpProcessor)
- {
- var topRow = row;
- var doc = Field.GetDocumentation(Field.DeclaringType, applicationTr, helpProcessor);
- if (doc.Length > 0 || Field.FieldType.GetFields(BindingFlags.Static | BindingFlags.Public).All(el => el.IsDefined() || !el.GetCustomAttributes().Any()))
- {
- table.SetCell(2, row, doc, colSpan: 4);
- row++;
- }
- foreach (var el in Field.FieldType.GetFields(BindingFlags.Static | BindingFlags.Public))
- {
- if (el.IsDefined())
- continue;
- var attr = el.GetCustomAttributes().FirstOrDefault();
- if (attr == null) // skip the default value
- continue;
- table.SetCell(2, row, attr.Names.Where(n => n.Length <= 2).Select(s => s.Color(CmdLineColor.EnumValue)).JoinColoredString(", "), noWrap: true);
- table.SetCell(3, row, attr.Names.Where(n => n.Length > 2).Select(s => s.Color(CmdLineColor.EnumValue)).JoinColoredString(Environment.NewLine), noWrap: true);
- table.SetCell(4, row, el.GetDocumentation(Field.DeclaringType, applicationTr, helpProcessor), colSpan: 2);
- row++;
- }
- table.SetCell(0, topRow, FormatField(Field.Name), noWrap: true, colSpan: 2, rowSpan: row - topRow);
- return false;
- }
-}
-
-internal abstract class CmdLineOption : CmdLineElement
-{
- public string[] Options;
-}
-
-internal abstract class CmdLineFieldOption : CmdLineOption
-{
- public FieldInfo Field;
-
- public override void ProcessEndOfParameters(List> actionsToPerform, CommandInfo cmd)
- {
- if (IsMandatory)
- throw new MissingParameterException(Field, null, true, cmd);
- }
-
- public override bool AddHelpRow(TextTable table, ref int row, TranslationBase applicationTr, Func helpProcessor)
- {
- table.SetCell(0, row, Field.GetOrderedOptionAttributeNames().Where(o => !o.StartsWith("--")).OrderBy(cmd => cmd.Length).Select(cmd => cmd.Color(CmdLineColor.Option)).JoinColoredString(", "), noWrap: true);
- table.SetCell(1, row, Field.GetOrderedOptionAttributeNames().Where(o => o.StartsWith("--")).OrderBy(cmd => cmd.Length).Select(cmd => cmd.Color(CmdLineColor.Option)).JoinColoredString(Environment.NewLine), noWrap: true);
- table.SetCell(2, row, Field.GetDocumentation(Field.DeclaringType, applicationTr, helpProcessor), colSpan: 4);
- row++;
- return false;
- }
-}
-
-internal abstract class CmdLineEnumOption : CmdLineFieldOption
-{
- public EnumBehavior Behavior;
- public string AlreadyProcessedOptionOrCommand = null;
- public object AlreadyProcessedValue = null;
-
- protected abstract object getValue(string[] args, ref int i, out string optionOrCommand, CommandInfo cmd);
-
- public override bool ProcessParameter(string[] args, ref int i, List> actionsToPerform, bool suppressOptions, Func helpProcessor, CommandInfo cmd)
- {
- if (suppressOptions || !Options.Contains(args[i]))
- return false;
- string optionOrCommand;
- var value = getValue(args, ref i, out optionOrCommand, cmd);
- if (value == null)
- return false;
- if (Behavior == EnumBehavior.SingleValue)
- {
- if (AlreadyProcessedOptionOrCommand == null)
- {
- AlreadyProcessedOptionOrCommand = optionOrCommand;
- AlreadyProcessedValue = value;
- actionsToPerform.Add(obj => { Field.SetValue(obj, value); });
- }
- else if (AlreadyProcessedValue.Equals(value))
- {
- // Don’t throw an error if the same value is simply specified multiple times. Just ignore the second occurrence
- }
- else
- {
- // Since only a single value is allowed, throw an error if another value is specified later
- throw new IncompatibleCommandOrOptionException(AlreadyProcessedOptionOrCommand, optionOrCommand, cmd);
- }
- }
- else
- {
- if (AlreadyProcessedValue == null)
- AlreadyProcessedValue = value;
- else
- // Bitwise OR
- value = AlreadyProcessedValue = (dynamic) AlreadyProcessedValue | (dynamic) value;
-
- actionsToPerform.Add(obj => { Field.SetValue(obj, value); });
- }
- return true;
- }
-}
-
-internal sealed class CmdLineEnumOptionWithNames : CmdLineEnumOption
-{
- public Dictionary NameToValue;
-
- protected override object getValue(string[] args, ref int i, out string optionOrCommand, CommandInfo cmd)
- {
- i++;
- if (i >= args.Length)
- throw new IncompleteOptionException(args[i - 1], cmd);
- optionOrCommand = args[i];
- object value;
- if (!NameToValue.TryGetValue(optionOrCommand, out value))
- throw new UnrecognizedCommandOrOptionException(optionOrCommand, cmd);
- i++;
- return value;
- }
-
- public override ConsoleColoredString UsageString
- {
- get
- {
- if (Behavior == EnumBehavior.MultipleValues)
- {
- // -t name [-t name [...]] — multi-value enums with CommandNames
- return (IsMandatory ? "{0} {1} [{0} {1} [...]]" : "[{0} {1} [{0} {1} [...]]]").Color(CmdLineColor.OptionalityDelimiters).Fmt(
- Options[0].Color(CmdLineColor.Option),
- "<".Color(CmdLineColor.FieldBrackets) + Field.Name.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets));
- }
- else
- {
- // -t name
- return (IsMandatory ? "{0} {1}" : "[{0} {1}]").Color(CmdLineColor.OptionalityDelimiters).Fmt(
- Options[0].Color(CmdLineColor.Option),
- "<".Color(CmdLineColor.FieldBrackets) + Field.Name.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets));
- }
- }
- }
-
- public override bool AddHelpRow(TextTable table, ref int row, TranslationBase applicationTr, Func helpProcessor)
- {
- var topRow = row;
- row++;
- foreach (var el in Field.FieldType.GetFields(BindingFlags.Static | BindingFlags.Public).Where(e => !e.IsDefined()))
- {
- var attr = el.GetCustomAttributes().FirstOrDefault();
- if (attr == null) // skip the default value
- continue;
- table.SetCell(3, row, attr.Names.Where(n => n.Length <= 2).Select(s => s.Color(CmdLineColor.EnumValue)).JoinColoredString(", "), noWrap: true);
- table.SetCell(4, row, attr.Names.Where(n => n.Length > 2).Select(s => s.Color(CmdLineColor.EnumValue)).JoinColoredString(Environment.NewLine), noWrap: true);
- table.SetCell(5, row, el.GetDocumentation(Field.DeclaringType, applicationTr, helpProcessor));
- row++;
- }
- if (row == topRow + 1)
- throw new InvalidOperationException("Enum type {2}.{3} has no values (apart from default value for field {0}.{1}).".Fmt(Field.DeclaringType.FullName, Field.Name, Field.FieldType.DeclaringType.FullName, Field.FieldType));
- table.SetCell(0, topRow, Field.GetOrderedOptionAttributeNames().Where(o => !o.StartsWith("--")).OrderBy(cmd => cmd.Length).Select(cmd => cmd.Color(CmdLineColor.Option)).JoinColoredString(", "), noWrap: true, rowSpan: row - topRow);
- table.SetCell(1, topRow, Field.GetOrderedOptionAttributeNames().Where(o => o.StartsWith("--")).OrderBy(cmd => cmd.Length).Select(cmd => cmd.Color(CmdLineColor.Option)).JoinColoredString(Environment.NewLine), noWrap: true, rowSpan: row - topRow);
- table.SetCell(2, topRow, Field.GetDocumentation(Field.DeclaringType, applicationTr, helpProcessor), colSpan: 4);
- table.SetCell(2, topRow + 1, FormatField(Field.Name), noWrap: true, rowSpan: row - topRow - 1);
- return false;
- }
-}
-
-internal sealed class CmdLineEnumOptions : CmdLineEnumOption
-{
- public Dictionary OptionToValue;
-
- protected override object getValue(string[] args, ref int i, out string optionOrCommand, CommandInfo cmd)
- {
- optionOrCommand = args[i];
- i++;
- return OptionToValue[optionOrCommand];
- }
-
- public override ConsoleColoredString UsageString
- {
- get
- {
- var options = Field.FieldType.GetFields(BindingFlags.Public | BindingFlags.Static)
- .Where(fld => fld.IsDefined() && !fld.IsDefined())
- .Select(fi => fi.GetOrderedOptionAttributeNames().First().Color(CmdLineColor.Option))
- .ToArray();
-
- if (Behavior == EnumBehavior.MultipleValues)
- // [-t] [-u] [-v] — multi-value enums with Option names
- return options.Select(opt => "[{0}]".Color(CmdLineColor.OptionalityDelimiters).Fmt(opt)).JoinColoredString(" ");
-
- // {-t|-u} — single-value enums with Options
- return (IsMandatory ? (options.Length > 1 ? "{{{0}{1}" : "{0}") : "[{0}]").Color(CmdLineColor.OptionalityDelimiters).Fmt(options.JoinColoredString("|".Color(CmdLineColor.OptionalityDelimiters)), "}");
- }
- }
-
- public override bool AddHelpRow(TextTable table, ref int row, TranslationBase applicationTr, Func helpProcessor)
- {
- foreach (var el in Field.FieldType.GetFields(BindingFlags.Static | BindingFlags.Public).Where(e => e.IsDefined() && !e.IsDefined()))
- {
- table.SetCell(0, row, el.GetOrderedOptionAttributeNames().Where(o => !o.StartsWith("--")).OrderBy(cmd => cmd.Length).Select(cmd => cmd.Color(CmdLineColor.Option)).JoinColoredString(", "), noWrap: true);
- table.SetCell(1, row, el.GetOrderedOptionAttributeNames().Where(o => o.StartsWith("--")).OrderBy(cmd => cmd.Length).Select(cmd => cmd.Color(CmdLineColor.Option)).JoinColoredString(Environment.NewLine), noWrap: true);
- table.SetCell(2, row, el.GetDocumentation(Field.DeclaringType, applicationTr, helpProcessor), colSpan: 4);
- row++;
- }
- return false;
- }
-}
-
-internal sealed class CmdLineBoolOption : CmdLineFieldOption
-{
- public override bool ProcessParameter(string[] args, ref int i, List> actionsToPerform, bool suppressOptions, Func helpProcessor, CommandInfo cmd)
- {
- if (suppressOptions || !Options.Contains(args[i]))
- return false;
- actionsToPerform.Add(obj => { Field.SetValue(obj, true); });
- i++;
- return true;
- }
-
- public override ConsoleColoredString UsageString
- {
- get
- {
- // [-t]
- return "[{0}]".Color(CmdLineColor.OptionalityDelimiters).Fmt(Field.GetOrderedOptionAttributeNames().First().Color(CmdLineColor.Option));
- }
- }
-}
-
-// Covers string, integers, float/double, and their nullables
-internal sealed class CmdLineOtherOption : CmdLineFieldOption
-{
- public override bool ProcessParameter(string[] args, ref int i, List> actionsToPerform, bool suppressOptions, Func helpProcessor, CommandInfo cmd)
- {
- var optionName = args[i];
- if (suppressOptions || !Options.Contains(optionName))
- return false;
-
- i++;
- if (i >= args.Length)
- throw new IncompleteOptionException(optionName, cmd);
-
- if (!tryConvertString(args[i], actionsToPerform, Field))
- throw new InvalidNumericParameterException(Field.Name, cmd);
-
- i++;
- return true;
- }
-
- public override ConsoleColoredString UsageString
- {
- get
- {
- // -t name
- return (IsMandatory ? "{0} {1}" : "[{0} {1}]").Color(CmdLineColor.OptionalityDelimiters).Fmt(
- Field.GetOrderedOptionAttributeNames().First().Color(CmdLineColor.Option),
- "<".Color(CmdLineColor.FieldBrackets) + Field.Name.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets));
- }
- }
-}
-
-// Covers string, integers, float/double, and their nullables
-internal sealed class CmdLineOtherPositional : CmdLineFieldPositional
-{
- public override bool ProcessParameter(string[] args, ref int i, List> actionsToPerform, bool suppressOptions, Func helpProcessor, CommandInfo cmd)
- {
- if (!tryConvertString(args[i], actionsToPerform, Field))
- throw new InvalidNumericParameterException(Field.Name, cmd);
- i++;
- return true;
- }
-}
-
-internal sealed class CmdLineStringArrayOption : CmdLineFieldOption
-{
- private bool Already = false;
- public override bool ProcessParameter(string[] args, ref int i, List> actionsToPerform, bool suppressOptions, Func helpProcessor, CommandInfo cmd)
- {
- var optionName = args[i];
- if (suppressOptions || !Options.Contains(optionName))
- return false;
-
- i++;
- if (i >= args.Length)
- throw new IncompleteOptionException(optionName, cmd);
-
- var value = args[i];
- if (Already)
- actionsToPerform.Add(obj => { Field.SetValue(obj, ((string[]) Field.GetValue(obj)).Concat(value).ToArray()); });
- else
- {
- actionsToPerform.Add(obj => { Field.SetValue(obj, new string[] { value }); });
- Already = true;
- }
-
- i++;
- return true;
- }
-
- public override ConsoleColoredString UsageString
- {
- get
- {
- // -t name [-t name [...]]
- return (IsMandatory ? "{0} {1} [{0} {1} [...]]" : "[{0} {1} [{0} {1} [...]]]").Color(CmdLineColor.OptionalityDelimiters).Fmt(
- Field.GetOrderedOptionAttributeNames().First().Color(CmdLineColor.Option),
- "<".Color(CmdLineColor.FieldBrackets) + Field.Name.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets));
- }
- }
-}
-
-internal sealed class CmdLineStringArrayPositional : CmdLineFieldPositional
-{
- private List Already = null;
-
- public override bool ProcessParameter(string[] args, ref int i, List> actionsToPerform, bool suppressOptions, Func helpProcessor, CommandInfo cmd)
- {
- if (Already == null)
- Already = new List();
- Already.Add(args[i]);
- i++;
- return true;
- }
-
- public override void ProcessEndOfParameters(List> actionsToPerform, CommandInfo cmd)
- {
- if (IsMandatory && Already == null)
- throw new MissingParameterException(Field, null, false, cmd);
- actionsToPerform.Add(obj => { Field.SetValue(obj, Already == null ? new string[] { } : Already.ToArray()); });
- }
-}
-
-internal sealed class CmdLineSubcommand : CmdLineElement
-{
- public override bool IsPositional { get { return true; } }
-
- public Type Type;
- public SubcommandInfo[] Subcommands;
- public SubcommandInfo Subcommand;
-
- public override bool ProcessParameter(string[] args, ref int i, List> actionsToPerform, bool suppressOptions, Func helpProcessor, CommandInfo cmd)
- {
- var name = args[i];
- Subcommand = Subcommands.FirstOrDefault(s => s.Names.Contains(name));
- if (Subcommand == null)
- throw new UnrecognizedCommandOrOptionException(name, cmd);
- i++;
- return true;
- }
-
- public override void ProcessEndOfParameters(List> actionsToPerform, CommandInfo cmd)
- {
- if (IsMandatory)
- throw new MissingSubcommandException(cmd);
- }
-
- public override ConsoleColoredString UsageString
- {
- get
- {
- return (IsMandatory ? "{0}" : "[{0}]").Color(CmdLineColor.OptionalityDelimiters).Fmt(
- "<".Color(CmdLineColor.FieldBrackets) + "...".Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets));
- }
- }
-
- public override bool AddHelpRow(TextTable table, ref int row, TranslationBase applicationTr, Func helpProcessor)
- {
- var anyCommandsWithSuboptions = false;
- int origRow = row;
- foreach (var subcmd in Subcommands)
- {
- var cell1 = ConsoleColoredString.Empty;
- var cell2 = ConsoleColoredString.Empty;
- anyCommandsWithSuboptions |= subcmd.Elements.Length > 0;
- var asterisk = subcmd.Elements.Length > 0 ? "*".Color(CmdLineColor.SubcommandsPresentAsterisk) : ConsoleColoredString.Empty;
- table.SetCell(2, row, subcmd.Names.Where(n => n.Length <= 2).Select(n => n.Color(CmdLineColor.Command) + asterisk).JoinColoredString(", "), noWrap: true);
- table.SetCell(3, row, subcmd.Names.Where(n => n.Length > 2).Select(n => n.Color(CmdLineColor.Command) + asterisk).JoinColoredString(Environment.NewLine), noWrap: true);
- table.SetCell(4, row, subcmd.Type.GetDocumentation(subcmd.Type, applicationTr, helpProcessor), colSpan: 2);
- row++;
- }
- table.SetCell(0, origRow, FormatField("..."), colSpan: 2, rowSpan: row - origRow, noWrap: true);
- return anyCommandsWithSuboptions;
- }
-}
-
-internal static class CmdLineColor
-{
- public const ConsoleColor Option = ConsoleColor.Yellow;
- public const ConsoleColor FieldBrackets = ConsoleColor.DarkCyan;
- public const ConsoleColor Field = ConsoleColor.Cyan;
- public const ConsoleColor Command = ConsoleColor.Green;
- public const ConsoleColor EnumValue = ConsoleColor.Green;
- public const ConsoleColor UsageLinePrefix = ConsoleColor.Green;
- public const ConsoleColor OptionalityDelimiters = ConsoleColor.DarkGray; // e.g. [foo|bar] has [, ] and | in this color
- public const ConsoleColor SubcommandsPresentAsterisk = ConsoleColor.DarkYellow;
- public const ConsoleColor UnexpectedArgument = ConsoleColor.Magenta;
- public const ConsoleColor Error = ConsoleColor.Red;
- public const ConsoleColor HelpHeading = ConsoleColor.White;
- public const ConsoleColor Highlight = ConsoleColor.White;
-}
-
-///
-/// Contains methods to validate a set of parameters passed by the user on the command-line and parsed by . Use this class only in monolingual (unlocalisable) applications. Use otherwise.
-public interface ICommandLineValidatable
-{
- ///
- /// When overridden in a derived class, returns an error message if the contents of the class are invalid, otherwise
- /// returns null.
- ConsoleColoredString Validate();
-}
-
-///
-/// Contains methods to validate a set of parameters passed by the user on the command-line and parsed by .
-///
-/// A translation-string class containing the error messages that can occur during validation.
-public interface ICommandLineValidatable where TTranslation : TranslationBase
-{
- ///
- /// When implemented in a class, returns an error message if the contents of the class are invalid, otherwise returns
- /// null.
- ///
- /// Contains translations for the messages that may occur during validation.
- ConsoleColoredString Validate(TTranslation tr);
-}
-
-/// Groups the translatable strings in the class into categories.
-public enum TranslationGroup
-{
- /// Error messages produced by the command-line parser.
- [LingoGroup("Command-line errors", "Contains messages informing the user of invalid command-line syntax.")]
- CommandLineError,
- /// Messages used by the command-line parser to produce help pages.
- [LingoGroup("Command-line help", "Contains messages used to construct help pages for command-line options and parameters.")]
- CommandLineHelp
-}
-
-/// Contains translatable strings pertaining to the command-line parser, including error messages and usage help.
-public sealed class Translation : TranslationBase
-{
-#pragma warning disable 1591 // Missing XML comment for publicly visible type or member
- public Translation() : base(Language.EnglishUS) { }
-
- [LingoInGroup(TranslationGroup.CommandLineError)]
- public TrString
- IncompatibleCommandOrOption = @"The command or option, {0}, cannot be used in conjunction with {1}. Please specify only one of the two.",
- IncompleteOption = @"The {0} option must be followed by an additional parameter.",
- InvalidNumber = @"The {0} option expects a number. The specified parameter does not constitute a valid number.",
- MissingOption = @"The option {0} is mandatory and must be specified.",
- MissingOptionBefore = @"The option {0} is mandatory and must be specified before the {1} parameter.",
- MissingParameter = @"The parameter {0} is mandatory and must be specified.",
- MissingParameterBefore = @"The parameter {0} is mandatory and must be specified before the {1} parameter.",
- MissingSubcommand = @"The command line options must be followed by a command name.",
- UnexpectedParameter = @"Unexpected parameter: {0}",
- UnrecognizedCommandOrOption = @"The specified command or option, {0}, is not recognized.",
- UserRequestedHelp = @"The user has requested help using one of the help options.";
-
- [LingoInGroup(TranslationGroup.CommandLineHelp)]
- public TrString
- AdditionalOptions = @"This command accepts further arguments on the command line. Type the command followed by *-?* or *help* to list them.",
- Error = @"Error:",
- OptionsHeader = @"Optional parameters:",
- ParametersHeader = @"Required parameters:",
- Usage = @"Usage:";
-
-#pragma warning restore 1591 // Missing XML comment for publicly visible type or member
-}
-
-/// Use this on a class to specify that it represent a command-line syntax.
-[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
-public sealed class CommandLineAttribute : Attribute
-{
- /// Constructor.
- public CommandLineAttribute() { }
-}
-
-///
-/// Use this on a derived class or on an enum value to specify the command the user must use to invoke that class or enum
-/// value.
-[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe]
-public sealed class CommandNameAttribute : Attribute
-{
- ///
- /// Constructor.
- ///
- /// The command(s) the user can specify to invoke this class or enum value.
- public CommandNameAttribute(params string[] names) { Names = names; }
- /// The command the user can specify to invoke this class.
- public string[] Names { get; private set; }
-}
-
-/// Use this to specify that a command-line parameter is mandatory.
-[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe]
-public sealed class IsMandatoryAttribute : Attribute
-{
- /// Constructor.
- public IsMandatoryAttribute() { }
-}
-
-///
-/// Use this to specify that a command-line parameter is positional, i.e. is not invoked by an option that starts with
-/// "-".
-[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe]
-public sealed class IsPositionalAttribute : Attribute
-{
- /// Constructor.
- public IsPositionalAttribute() { }
-}
-
-///
-/// Use this to specify that a field in a class can be specified on the command line using an option, for example
-/// -a or --option-name. The option name(s) MUST begin with a dash (-).
-[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe]
-public sealed class OptionAttribute : Attribute
-{
- ///
- /// Constructor.
- ///
- /// The name of the option. Specify several names as synonyms if required.
- public OptionAttribute(params string[] names) { Names = names; }
- /// All of the names of the option.
- public string[] Names { get; private set; }
-}
-
-///
-/// Use this attribute to link a command-line option or command with the help text that describes (documents) it. Suitable
-/// for single-language applications only. See Remarks.
-///
-/// This attribute specifies the documentation in plain text. All characters are printed exactly as specified. You may
-/// wish to use to specify documentation with special markup for
-/// command-line-related concepts, as well as for an alternative markup
-/// language without command-line specific concepts.
-[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe]
-public class DocumentationAttribute : Attribute
-{
- ///
- /// Gets the console-colored documentation string. Note that this property may throw if the text couldn't be parsed
- /// where applicable.
- public virtual ConsoleColoredString Text { get { return OriginalText; } }
- /// Gets a string describing the documentation format to the programmer (not seen by the users).
- public virtual string OriginalFormat { get { return "Plain text"; } }
- /// Gets the original documentation string exactly as specified in the attribute.
- public string OriginalText { get; private set; }
-
- /// Constructor.
- public DocumentationAttribute(string documentation)
- {
- OriginalText = documentation;
- }
-}
-
-///
-/// Use this attribute to link a command-line option or command with the help text that describes (documents) it. Suitable
-/// for single-language applications only. The documentation is to be specified in , which is
-/// interpreted as described in . See also .
-[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe]
-public class DocumentationRhoMLAttribute : DocumentationAttribute
-{
- /// Gets a string describing the documentation format to the programmer (not seen by the users).
- public override string OriginalFormat { get { return "RhoML"; } }
- ///
- /// Gets the console-colored documentation string. Note that this property may throw if the text couldn't be parsed
- /// where applicable.
- public override ConsoleColoredString Text
- {
- get { return _parsed ?? (_parsed = CommandLineParser.Colorize(RhoML.Parse(OriginalText))); }
- }
- private ConsoleColoredString _parsed;
- /// Constructor.
- public DocumentationRhoMLAttribute(string documentation) : base(documentation) { }
-}
-
-///
-/// Use this attribute to link a command-line option or command with the help text that describes (documents) it. Suitable
-/// for single-language applications only. The documentation is to be specified in , which is
-/// interpreted as described in . See also and .
-[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe]
-public class DocumentationEggsMLAttribute : DocumentationAttribute
-{
- /// Gets a string describing the documentation format to the programmer (not seen by the users).
- public override string OriginalFormat { get { return "EggsML"; } }
- ///
- /// Gets the console-colored documentation string. Note that this property may throw if the text couldn't be parsed
- /// where applicable.
- public override ConsoleColoredString Text
- {
- get { return _parsed ?? (_parsed = CommandLineParser.Colorize(EggsML.Parse(OriginalText))); }
- }
- private ConsoleColoredString _parsed;
- /// Constructor.
- public DocumentationEggsMLAttribute(string documentation) : base(documentation) { }
-}
-
-///
-/// This is a legacy attribute. Do not use in new programs. This attribute is equivalent to .
-[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false), RummageKeepUsersReflectionSafe]
-public class DocumentationLiteralAttribute : DocumentationEggsMLAttribute
-{
- /// Constructor.
- public DocumentationLiteralAttribute(string documentation) : base(documentation) { }
-}
-
-///
-/// Specifies that a specific command-line option should not be printed in help pages, i.e. the option should explicitly
-/// be undocumented.
-[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
-public sealed class UndocumentedAttribute : Attribute
-{
- /// Constructor.
- public UndocumentedAttribute() { }
-}
-
-/// Describes the behavior of an enum-typed field with the .
-public enum EnumBehavior
-{
- /// Specifies that an enum is considered to represent a single value.
- SingleValue,
- /// Specifies that an enum is considered to represent a bitfield containing multiple values.
- MultipleValues
-}
-
-///
-/// Specifies that a field of an enum type should be interpreted as multiple possible options, each specified by an on the enum values in the enum type.
-[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
-public sealed class EnumOptionsAttribute : Attribute
-{
- /// Constructor.
- public EnumOptionsAttribute(EnumBehavior behavior) { Behavior = behavior; }
-
- ///
- /// Specifies whether the enum is considered to represent a single value or a bitfield containing multiple values.
- public EnumBehavior Behavior { get; private set; }
-}
-
-/// Specifies that the command-line parser should ignore a field.
-[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
-public sealed class IgnoreAttribute : Attribute
-{
- /// Constructor.
- public IgnoreAttribute() { }
-}
-
-/// Represents any error encountered while parsing a command line. This class is abstract.
-[Serializable]
-public abstract class CommandLineParseException : TranslatableException
-{
- /// Specifies the command-line type for which a help screen is to be output to the user on the console.
- internal CommandInfo CommandInfo { get; private set; }
-
- /// Contains the error message that describes the cause of this exception.
- public Func GetColoredMessage { get; private set; }
-
- ///
- /// Generates a printable description of the error represented by this exception, typically used to tell the user what
- /// they did wrong.
- ///
- /// The translation class containing the translated text, or null for English.
- ///
- /// The character width at which the output should be word-wrapped. The default (null) uses .
- public ConsoleColoredString GenerateErrorText(Translation tr, int? wrapWidth = null)
- {
- if (tr == null)
- tr = new Translation();
-
- var strings = new List();
- var message = tr.Error.Translation.Color(CmdLineColor.Error) + " " + GetColoredMessage(tr);
- foreach (var line in message.WordWrap(wrapWidth ?? ConsoleUtil.WrapToWidth(), tr.Error.Translation.Length + 1))
- {
- strings.Add(line);
- strings.Add(Environment.NewLine);
- }
- return new ConsoleColoredString(strings);
- }
-
- /// Constructor.
- internal CommandLineParseException(Func getMessage, CommandInfo commandInfo) : this(getMessage, commandInfo, null) { }
- /// Constructor.
- internal CommandLineParseException(Func getMessage, CommandInfo commandInfo, Exception inner)
- : base(tr => getMessage(tr).ToString(), inner)
- {
- CommandInfo = commandInfo;
- GetColoredMessage = getMessage;
- }
-
- ///
- /// Prints usage information, followed by an error message describing to the user what it was that the parser didn't
- /// understand.
- ///
- /// An object containing translations for the documentation strings. Set this to null only if your application
- /// is definitely monolingual (unlocalisable).
- ///
- /// Contains translations for the messages used by the command-line parser. Set this to null only if your
- /// application is definitely monolingual (unlocalisable).
- ///
- /// Specifies a callback which is invoked on every documentation string retrieved from the s to generate the help text. This callback can modify the text arbitrarily.
- public virtual void WriteUsageInfoToConsole(TranslationBase applicationTr = null, Translation tr = null, Func helpProcessor = null)
- {
- ConsoleUtil.Write(GetUsageInfo(applicationTr, tr, helpProcessor));
- }
-
- ///
- /// Generates and returns usage information, followed by an error message describing to the user what it was that the
- /// parser didn't understand.
- ///
- /// An object containing translations for the documentation strings. Set this to null only if your application
- /// is definitely monolingual (unlocalisable).
- ///
- /// Contains translations for the messages used by the command-line parser. Set this to null only if your
- /// application is definitely monolingual (unlocalisable).
- ///
- /// Specifies a callback which is invoked on every documentation string retrieved from the s to generate the help text. This callback can modify the text arbitrarily.
- public ConsoleColoredString GetUsageInfo(TranslationBase applicationTr = null, Translation tr = null, Func helpProcessor = null)
- {
- if (tr == null)
- tr = new Translation();
- var str = CommandLineParser.GenerateHelp(CommandInfo, ConsoleUtil.WrapToWidth(), applicationTr, tr, helpProcessor);
- if (WriteErrorText)
- str += Environment.NewLine + GenerateErrorText(tr, ConsoleUtil.WrapToWidth());
- return str;
- }
-
- ///
- /// Determines whether should call and output it
- /// to the console. Default is true.
- ///
- /// Only set this to false if the user explicitly asked to see the help screen. Otherwise its appearance
- /// without explanation is confusing.
- protected internal virtual bool WriteErrorText { get { return true; } }
-}
-
-/// Indicates that the user supplied one of the standard options we recognize as a help request.
-[Serializable]
-public sealed class CommandLineHelpRequestedException : CommandLineParseException
-{
- /// Constructor.
- internal CommandLineHelpRequestedException(CommandInfo commandInfo)
- : base(tr => tr.UserRequestedHelp.Color(ConsoleColor.Gray), commandInfo)
- {
- }
-
- /// Overrides the base to indicate that no error message should be output along with the help screen.
- protected internal override bool WriteErrorText { get { return false; } }
-}
-
-/// Specifies that the arguments specified by the user on the command-line do not pass the custom validation checks.
-[Serializable]
-public sealed class CommandLineValidationException : CommandLineParseException
-{
- /// Constructor.
- internal CommandLineValidationException(ConsoleColoredString message, CommandInfo commandInfo) : base(tr => message, commandInfo) { }
-}
-
-///
-/// Specifies that the command-line parser encountered a command or option that was not recognised (there was no or attribute with a matching option or command name).
-[Serializable]
-public sealed class UnrecognizedCommandOrOptionException : CommandLineParseException
-{
- /// The unrecognized command name or option name.
- public string CommandOrOptionName { get; private set; }
- /// Constructor.
- internal UnrecognizedCommandOrOptionException(string commandOrOptionName, CommandInfo commandInfo) : this(commandOrOptionName, commandInfo, null) { }
- /// Constructor.
- internal UnrecognizedCommandOrOptionException(string commandOrOptionName, CommandInfo commandInfo, Exception inner)
- : base(tr => tr.UnrecognizedCommandOrOption.ToConsoleColoredString().Fmt(commandOrOptionName.Color(ConsoleColor.White)), commandInfo, inner)
- {
- CommandOrOptionName = commandOrOptionName;
- }
-}
-
-///
-/// Specifies that the command-line parser encountered a command or option that is not allowed in conjunction with a
-/// previously-encountered command or option.
-[Serializable]
-public sealed class IncompatibleCommandOrOptionException : CommandLineParseException
-{
- ///
- /// The earlier option or command, which by itself is valid, but conflicts with the .
- public string EarlierCommandOrOption { get; private set; }
- /// The later option or command, which conflicts with the .
- public string LaterCommandOrOption { get; private set; }
- /// Constructor.
- internal IncompatibleCommandOrOptionException(string earlier, string later, CommandInfo commandInfo) : this(earlier, later, commandInfo, null) { }
- /// Constructor.
- internal IncompatibleCommandOrOptionException(string earlier, string later, CommandInfo commandInfo, Exception inner)
- : base(tr => tr.IncompatibleCommandOrOption.ToConsoleColoredString().Fmt(later.Color(ConsoleColor.White), earlier.Color(ConsoleColor.White)), commandInfo, inner)
- {
- EarlierCommandOrOption = earlier;
- LaterCommandOrOption = later;
- }
-}
-
-///
-/// Specifies that the command-line parser encountered the end of the command line when it expected an argument to an
-/// option.
-[Serializable]
-public sealed class IncompleteOptionException : CommandLineParseException
-{
- /// The name of the option that was missing an argument.
- public string OptionName { get; private set; }
- /// Constructor.
- internal IncompleteOptionException(string optionName, CommandInfo commandInfo) : this(optionName, commandInfo, null) { }
- /// Constructor.
- internal IncompleteOptionException(string optionName, CommandInfo commandInfo, Exception inner)
- : base(tr => tr.IncompleteOption.ToConsoleColoredString().Fmt(optionName.Color(ConsoleColor.White)), commandInfo, inner)
- {
- OptionName = optionName;
- }
-}
-
-///
-/// Specifies that the command-line parser encountered additional command-line arguments when it expected the end of the
-/// command line.
-[Serializable]
-public sealed class UnexpectedArgumentException : CommandLineParseException
-{
- /// Contains the first unexpected argument and all of the subsequent arguments.
- public string[] UnexpectedParameters { get; private set; }
- /// Constructor.
- internal UnexpectedArgumentException(string[] unexpectedArgs, CommandInfo commandInfo) : this(unexpectedArgs, commandInfo, null) { }
- /// Constructor.
- internal UnexpectedArgumentException(string[] unexpectedArgs, CommandInfo commandInfo, Exception inner)
- : base(tr => tr.UnexpectedParameter.ToConsoleColoredString().Fmt(unexpectedArgs.Select(prm => prm.Length > 50 ? prm.Substring(0, 47) + "..." : prm).FirstOrDefault().Color(CmdLineColor.UnexpectedArgument)), commandInfo, inner)
- {
- UnexpectedParameters = unexpectedArgs;
- }
-}
-
-///
-/// Specifies that a parameter that expected a numerical value was passed a string by the user that doesn’t parse as a
-/// number.
-[Serializable]
-public sealed class InvalidNumericParameterException : CommandLineParseException
-{
- /// Contains the name of the field pertaining to the parameter that was passed an invalid value.
- public string FieldName { get; private set; }
- /// Constructor.
- internal InvalidNumericParameterException(string fieldName, CommandInfo commandInfo) : this(fieldName, commandInfo, null) { }
- /// Constructor.
- internal InvalidNumericParameterException(string fieldName, CommandInfo commandInfo, Exception inner)
- : base(tr => tr.InvalidNumber.ToConsoleColoredString().Fmt("<".Color(CmdLineColor.FieldBrackets) + fieldName.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets)), commandInfo, inner)
- {
- FieldName = fieldName;
- }
-}
-
-///
-/// Specifies that the command-line parser encountered the end of the command line when it expected additional mandatory
-/// options.
-[Serializable]
-public sealed class MissingParameterException : CommandLineParseException
-{
- /// Contains the field pertaining to the parameter that was missing.
- public FieldInfo Field { get; private set; }
- /// Contains an optional reference to a field which the missing parameter must precede.
- public FieldInfo BeforeField { get; private set; }
- ///
- /// Specifies whether the missing parameter was a missing option (true) or a missing positional parameter (false).
- public bool IsOption { get; private set; }
- /// Constructor.
- internal MissingParameterException(FieldInfo paramField, FieldInfo beforeField, bool isOption, CommandInfo commandInfo) : this(paramField, beforeField, isOption, commandInfo, null) { }
- /// Constructor.
- internal MissingParameterException(FieldInfo paramField, FieldInfo beforeField, bool isOption, CommandInfo commandInfo, Exception inner)
- : base(tr => getMessage(tr, paramField, beforeField, isOption), commandInfo, inner)
- { Field = paramField; BeforeField = beforeField; IsOption = isOption; }
-
- private static ConsoleColoredString getMessage(Translation tr, FieldInfo field, FieldInfo beforeField, bool isOption)
- {
- if (beforeField == null)
- return (isOption ? tr.MissingOption : tr.MissingParameter).ToConsoleColoredString().Fmt(field.FormatParameterUsage(true));
-
- return (isOption ? tr.MissingOptionBefore : tr.MissingParameterBefore).ToConsoleColoredString().Fmt(
- field.FormatParameterUsage(true),
- "<".Color(CmdLineColor.FieldBrackets) + beforeField.Name.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets));
- }
-}
-
-///
-/// Specifies that the command-line parser encountered the end of the command line when it expected a mandatory
-/// subcommand.
-[Serializable]
-public sealed class MissingSubcommandException : CommandLineParseException
-{
- /// Constructor.
- internal MissingSubcommandException(CommandInfo commandInfo) : this(commandInfo, null) { }
- /// Constructor.
- internal MissingSubcommandException(CommandInfo commandInfo, Exception inner)
- : base(tr => tr.MissingSubcommand.ToConsoleColoredString(), commandInfo, inner)
- { }
-}
-
-static class CmdLineExtensions
-{
- public static string[] GetOrderedOptionAttributeNames(this MemberInfo member)
- {
- var attr = member.GetCustomAttributes().FirstOrDefault();
- return attr == null ? null : attr.Names.OrderBy(compareOptionNames).ToArray();
- }
-
- private static int compareOptionNames(string opt1, string opt2)
- {
- bool long1 = opt1.StartsWith("--");
- bool long2 = opt2.StartsWith("--");
- if (long1 == long2)
- return StringComparer.OrdinalIgnoreCase.Compare(opt1, opt2);
- else if (long1)
- return 1; // --blah comes after -blah
- else
- return -1;
- }
-
- public static ConsoleColoredString FormatParameterUsage(this FieldInfo field, bool isMandatory)
- {
- // Positionals
- if (field.IsDefined())
- return (isMandatory ? "{0}" : "[{0}]").Color(CmdLineColor.OptionalityDelimiters).Fmt(
- "<".Color(CmdLineColor.FieldBrackets) + field.Name.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets));
-
- // -t name [-t name [...]] — arrays, multi-value enums with CommandNames
- if (field.FieldType.IsArray ||
- (field.FieldType.IsEnum &&
- field.IsDefined() &&
- field.IsDefined() &&
- field.GetCustomAttributes().First().Behavior == EnumBehavior.MultipleValues))
- {
- return (isMandatory ? "{0} {1} [{0} {1} [...]]" : "[{0} {1} [{0} {1} [...]]]").Color(CmdLineColor.OptionalityDelimiters).Fmt(
- field.GetOrderedOptionAttributeNames().First().Color(CmdLineColor.Option),
- "<".Color(CmdLineColor.FieldBrackets) + field.Name.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets));
- }
-
- // Enums with Option names
- if (field.FieldType.IsEnum && !field.IsDefined())
- {
- var options = field.FieldType.GetFields(BindingFlags.Public | BindingFlags.Static)
- .Where(fld => fld.IsDefined() && !fld.IsDefined())
- .Select(fi => fi.GetOrderedOptionAttributeNames().First().Color(CmdLineColor.Option))
- .ToArray();
-
- if (field.IsDefined() && field.GetCustomAttributes().First().Behavior == EnumBehavior.MultipleValues)
- // [-t] [-u] [-v] — multi-value enums with Option names
- return options.Select(opt => "[{0}]".Color(CmdLineColor.OptionalityDelimiters).Fmt(opt)).JoinColoredString(" ");
-
- // {-t|-u} — single-value enums with Options
- return (isMandatory ? (options.Length > 1 ? "{{{0}{1}" : "{0}") : "[{0}]").Color(CmdLineColor.OptionalityDelimiters).Fmt(options.JoinColoredString("|".Color(CmdLineColor.OptionalityDelimiters)), "}");
- }
-
- // -t — bools
- if (field.FieldType == typeof(bool))
- return "[{0}]".Color(CmdLineColor.OptionalityDelimiters).Fmt(field.GetOrderedOptionAttributeNames().First().Color(CmdLineColor.Option));
-
- // -t name
- return (isMandatory ? "{0} {1}" : "[{0} {1}]").Color(CmdLineColor.OptionalityDelimiters).Fmt(
- field.GetOrderedOptionAttributeNames().First().Color(CmdLineColor.Option),
- "<".Color(CmdLineColor.FieldBrackets) + field.Name.Color(CmdLineColor.Field) + ">".Color(CmdLineColor.FieldBrackets));
- }
-
- public static ConsoleColoredString GetDocumentation(this MemberInfo member, Type inType, TranslationBase applicationTr, Func helpProcessor)
- {
- if (member.IsDefined())
- return helpProcessor(member.GetCustomAttributes().Select(d => d.Text ?? "").First());
- if (applicationTr == null)
- return "";
-
- if (!(member is Type) && inType.IsSubclassOf(member.DeclaringType))
- inType = member.DeclaringType;
- var meth = inType.GetMethod(member.Name + "Doc", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { applicationTr.GetType() }, null);
- if (meth == null || meth.ReturnType != typeof(string))
- return "";
- var str = (string) meth.Invoke(null, new object[] { applicationTr });
- return str == null ? "" : helpProcessor(CommandLineParser.Colorize(EggsML.Parse(str)));
- }
-}
diff --git a/SrcLingo/RT.CommandLine.Lingo.csproj b/SrcLingo/RT.CommandLine.Lingo.csproj
deleted file mode 100644
index c4b81b0..0000000
--- a/SrcLingo/RT.CommandLine.Lingo.csproj
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
- netstandard2.0;net8.0
- enable
- latest
- $(OutputPath)\$(AssemblyName).xml
-
- Timwi;rstarkov
- A command line parser that populates a class or a set of classes, with support for advanced help text formatting and translations using RT.Lingo.
- rt.commandline;command line;parser
- MIT
- snupkg
- true
-
-
-
-
-
-
-
-
-
-
diff --git a/Tests/CommandLineLingoTests.cs b/Tests/CommandLineLingoTests.cs
deleted file mode 100644
index b18b7f1..0000000
--- a/Tests/CommandLineLingoTests.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using Xunit;
-
-namespace RT.CommandLine.Lingo.Tests;
-
-public sealed class CmdLineLingoTests
-{
-#pragma warning disable 0649 // Field is never assigned to, and will always have its default value null
- private class commandLine
- {
- [Option("--stuff")]
- public string Stuff;
- [IsPositional]
- public string[] Args;
- }
-#pragma warning restore 0649 // Field is never assigned to, and will always have its default value null
-
- [Fact]
- public static void Test()
- {
- var c = CommandLineParser.Parse("--stuff blah abc def".Split(' '));
- Assert.Equal("blah", c.Stuff);
- Assert.True(c.Args.SequenceEqual(new[] { "abc", "def" }));
-
- c = CommandLineParser.Parse("def --stuff thingy abc".Split(' '));
- Assert.Equal("thingy", c.Stuff);
- Assert.True(c.Args.SequenceEqual(new[] { "def", "abc" }));
-
- c = CommandLineParser.Parse("--stuff stuff -- abc --stuff blah -- def".Split(' '));
- Assert.Equal("stuff", c.Stuff);
- Assert.True(c.Args.SequenceEqual(new[] { "abc", "--stuff", "blah", "--", "def" }));
- }
-}
diff --git a/Tests/CommandLineTests.cs b/Tests/CommandLineTests.cs
index 7894c5c..1ea4104 100644
--- a/Tests/CommandLineTests.cs
+++ b/Tests/CommandLineTests.cs
@@ -1,4 +1,6 @@
-using RT.Util.Consoles;
+using RT.Json;
+using RT.Serialization;
+using RT.Util.Consoles;
using Xunit;
namespace RT.CommandLine.Tests;
@@ -92,50 +94,6 @@ public static void TestInvalidOption()
}
}
-
- class Test1Cmd : ICommandLineValidatable
- {
- [IsPositional, IsMandatory]
- public string Base;
-
- [IsPositional, IsMandatory]
- public Test1SubcommandBase Subcommand;
-
- public static int ValidateCalled = 0;
-
- public ConsoleColoredString Validate()
- {
- ValidateCalled++;
- return null;
- }
- }
-
- [CommandGroup]
- abstract class Test1SubcommandBase : ICommandLineValidatable
- {
- public static int ValidateCalled = 0;
- public abstract ConsoleColoredString Validate();
- }
-
- [CommandName("sub1")]
- sealed class Test1Subcommand1 : Test1SubcommandBase
- {
- [IsPositional, IsMandatory]
- public string ItemName;
-
- public override ConsoleColoredString Validate()
- {
- ValidateCalled++;
- return null;
- }
- }
-
- [CommandName("sub2")]
- sealed class Test1Subcommand2 : Test1SubcommandBase
- {
- public override ConsoleColoredString Validate() { return null; }
- }
-
[Fact]
public static void TestSubcommandValidation()
{
@@ -157,4 +115,16 @@ public static void TestSubcommandValidation()
Assert.Equal(1, Test1Cmd.ValidateCalled);
Assert.Equal(0, Test1SubcommandBase.ValidateCalled);
}
+
+ [Fact]
+ public static void TestMore()
+ {
+ Assert.True(
+ JsonValue.Parse(@"{""Boolean"":true,""Subcommand"":{""SharedString"":null,""Name"":""this"","":type"":""Test2SubcommandAdd""}}")
+ == ClassifyJson.Serialize(CommandLineParser.Parse(["-b", "add", "this"])));
+
+ Assert.True(
+ JsonValue.Parse(@"{""Boolean"":false,""Subcommand"":{""SharedString"":null,""Id"":""this"","":type"":""Test2SubcommandDelete""}}")
+ == ClassifyJson.Serialize(CommandLineParser.Parse(["del", "this"])));
+ }
}
diff --git a/Tests/RT.CommandLine.Tests.csproj b/Tests/RT.CommandLine.Tests.csproj
index a9d613d..06f867f 100644
--- a/Tests/RT.CommandLine.Tests.csproj
+++ b/Tests/RT.CommandLine.Tests.csproj
@@ -9,8 +9,10 @@
-
-
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
@@ -22,7 +24,6 @@
-
diff --git a/Tests/Test1.cs b/Tests/Test1.cs
new file mode 100644
index 0000000..f772c80
--- /dev/null
+++ b/Tests/Test1.cs
@@ -0,0 +1,46 @@
+using RT.Util.Consoles;
+
+namespace RT.CommandLine.Tests;
+
+class Test1Cmd : ICommandLineValidatable
+{
+ [IsPositional, IsMandatory]
+ public string Base;
+
+ [IsPositional, IsMandatory]
+ public Test1SubcommandBase Subcommand;
+
+ public static int ValidateCalled = 0;
+
+ public ConsoleColoredString Validate()
+ {
+ ValidateCalled++;
+ return null;
+ }
+}
+
+[CommandGroup]
+abstract class Test1SubcommandBase : ICommandLineValidatable
+{
+ public static int ValidateCalled = 0;
+ public abstract ConsoleColoredString Validate();
+}
+
+[CommandName("sub1")]
+sealed class Test1Subcommand1 : Test1SubcommandBase
+{
+ [IsPositional, IsMandatory]
+ public string ItemName;
+
+ public override ConsoleColoredString Validate()
+ {
+ ValidateCalled++;
+ return null;
+ }
+}
+
+[CommandName("sub2")]
+sealed class Test1Subcommand2 : Test1SubcommandBase
+{
+ public override ConsoleColoredString Validate() { return null; }
+}
diff --git a/Tests/Test2.cs b/Tests/Test2.cs
new file mode 100644
index 0000000..aaf9619
--- /dev/null
+++ b/Tests/Test2.cs
@@ -0,0 +1,30 @@
+namespace RT.CommandLine.Tests;
+
+class Test2Cmd
+{
+ [Option("-b")]
+ public bool Boolean;
+
+ public Test2Subcommand Subcommand;
+}
+
+[CommandGroup]
+abstract class Test2Subcommand
+{
+ [Option("-k")]
+ public string SharedString;
+}
+
+[CommandName("add")]
+class Test2SubcommandAdd : Test2Subcommand
+{
+ [IsPositional, IsMandatory]
+ public string Name;
+}
+
+[CommandName("del")]
+class Test2SubcommandDelete : Test2Subcommand
+{
+ [IsPositional, IsMandatory]
+ public string Id;
+}