/// <summary> /// Executes the action with the given parameters. /// </summary> /// <param name="action">The action to be executed.</param> /// <param name="parameters">The parameters for the action.</param> /// <returns></returns> public static int PerformExecute(this ICliAction action, string[] parameters) { ArgumentsParser ap = new ArgumentsParser(); var td = TypeData.GetTypeData(action); var props = td.GetMembers(); ap.AllOptions.Add("help", 'h', false, "Write help information."); ap.AllOptions.Add("verbose", 'v', false, "Show verbose/debug-level log messages."); ap.AllOptions.Add("color", 'c', false, "Color messages according to their severity."); ap.AllOptions.Add("quiet", 'q', false, "Quiet console logging."); var argToProp = new Dictionary <string, IMemberData>(); var unnamedArgToProp = new List <IMemberData>(); foreach (var prop in props) { if (prop.Readable == false || prop.Writable == false) { continue; } if (prop.HasAttribute <UnnamedCommandLineArgument>()) { unnamedArgToProp.Add(prop); continue; } if (!prop.HasAttribute <CommandLineArgumentAttribute>()) { continue; } var attr = prop.GetAttribute <CommandLineArgumentAttribute>(); var needsArg = prop.TypeDescriptor != TypeData.FromType(typeof(bool)); string description = ""; if (prop.HasAttribute <ObsoleteAttribute>()) { var obsoleteAttribute = prop.GetAttribute <ObsoleteAttribute>(); description = $"[OBSOLETE: {obsoleteAttribute.Message}] {attr.Description}"; } else { description = attr.Description; } var arg = ap.AllOptions.Add(attr.Name, attr.ShortName == null ? '\0' : attr.ShortName.FirstOrDefault(), needsArg, description); arg.IsVisible = attr.Visible; argToProp.Add(arg.LongName, prop); } var args = ap.Parse(parameters); if (args.MissingArguments.Any()) { throw new Exception($"Command line argument '{args.MissingArguments.FirstOrDefault().LongName}' is missing an argument."); } if (args.Contains("help")) { printOptions(action.GetType().GetAttribute <DisplayAttribute>().Name, ap.AllOptions, unnamedArgToProp); return(0); } foreach (var opts in args) { if (argToProp.ContainsKey(opts.Key) == false) { continue; } var prop = argToProp[opts.Key]; if (prop.TypeDescriptor is TypeData propTd) { Type propType = propTd.Load(); if (propType == typeof(bool)) { prop.SetValue(action, true); } else if (propType.IsEnum) { prop.SetValue(action, ParseEnum(opts.Key, opts.Value.Value, propType)); } else if (propType == typeof(string)) { prop.SetValue(action, opts.Value.Value); } else if (propType == typeof(string[])) { prop.SetValue(action, opts.Value.Values.ToArray()); } else if (propType == typeof(int)) { prop.SetValue(action, int.Parse(opts.Value.Value)); } else { throw new Exception(string.Format("Command line option '{0}' is of an unsupported type '{1}'.", opts.Key, propType.Name)); } } else { throw new Exception(string.Format("Command line option '{0}' is of an unsupported type '{1}'.", opts.Key, prop.TypeDescriptor.Name)); } } unnamedArgToProp = unnamedArgToProp.OrderBy(p => p.GetAttribute <UnnamedCommandLineArgument>().Order).ToList(); var requiredArgs = unnamedArgToProp.Where(x => x.GetAttribute <UnnamedCommandLineArgument>().Required).ToHashSet(); int idx = 0; for (int i = 0; i < unnamedArgToProp.Count; i++) { var p = unnamedArgToProp[i]; if (p.TypeDescriptor.IsA(typeof(string))) { if (idx < args.UnnamedArguments.Length) { p.SetValue(action, args.UnnamedArguments[idx++]); requiredArgs.Remove(p); } } else if (p.TypeDescriptor.IsA(typeof(string[]))) { if (idx < args.UnnamedArguments.Length) { p.SetValue(action, args.UnnamedArguments.Skip(idx).ToArray()); requiredArgs.Remove(p); } idx = args.UnnamedArguments.Length; } else if (p.TypeDescriptor is TypeData td2 && td2.Type.IsEnum) { if (idx < args.UnnamedArguments.Length) { var name = p.GetAttribute <UnnamedCommandLineArgument>()?.Name ?? p.Name; p.SetValue(action, ParseEnum($"<{name}>", args.UnnamedArguments[idx++], td2.Type)); requiredArgs.Remove(p); } } } if (args.UnknownsOptions.Any() || requiredArgs.Any()) { if (args.UnknownsOptions.Any()) { Console.WriteLine("Unknown options: " + string.Join(" ", args.UnknownsOptions)); } if (requiredArgs.Any()) { Console.WriteLine("Missing argument: " + string.Join(" ", requiredArgs.Select(p => p.GetAttribute <UnnamedCommandLineArgument>().Name))); } printOptions(action.GetType().GetAttribute <DisplayAttribute>().Name, ap.AllOptions, unnamedArgToProp); return(1); } var actionFullName = td.GetDisplayAttribute().GetFullName(); log.Debug($"Executing CLI action: {actionFullName}"); var sw = Stopwatch.StartNew(); int exitCode = action.Execute(TapThread.Current.AbortToken); log.Debug(sw, "CLI action returned exit code: {0}", exitCode); return(exitCode); }
public static int Execute(params string[] args) { // Trigger plugin manager before anything else. if (ExecutorClient.IsRunningIsolated) { // TODO: This is not needed right now, but might be again when we fix the TODO in tap.exe //PluginManager.DirectoriesToSearch.Clear(); //PluginManager.DirectoriesToSearch.Add(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)); using (var tpmClient = new ExecutorClient()) { tpmClient.MessageServer("delete " + Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)); } } // Set TapMutex to ensure any installers know about running OpenTAP processes. ReflectionHelper.SetTapMutex(); try { // Turn off the default system behavior when CTRL+C is pressed. // When Console.TreatControlCAsInput is false, CTRL+C is treated as an interrupt instead of as input. Console.TreatControlCAsInput = false; } catch { } try { var execThread = TapThread.Current; Console.CancelKeyPress += (s, e) => { e.Cancel = true; execThread.Abort(); }; } catch { } CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; // Find the called action if (!TypeData.GetDerivedTypes <ICliAction>().Any()) { Console.WriteLine("No commands found. Please try reinstalling OpenTAP."); return(1); } try { // setup logging to be relative to the executing assembly. // at this point SessionLogs.Initialize has already been called (PluginManager.Load). // so the log is already being saved at a different location. var logpath = EngineSettings.Current.SessionLogPath.Expand(date: Process.GetCurrentProcess().StartTime); bool isPathRooted = Path.IsPathRooted(logpath); if (isPathRooted == false) { var dir = Path.GetDirectoryName(typeof(SessionLogs).Assembly.Location); if (ExecutorClient.IsRunningIsolated) { // redirect the isolated log path to the non-isolated path. dir = ExecutorClient.ExeDir; } logpath = Path.Combine(dir, logpath); } SessionLogs.Rename(logpath); } catch (Exception e) { log.Error("Path defined in Engine settings contains invalid characters: {0}", EngineSettings.Current.SessionLogPath); log.Debug(e); } ITypeData selectedCommand = null; // Find selected command var actionTree = new CliActionTree(); var selectedcmd = actionTree.GetSubCommand(args); if (selectedcmd?.Type != null && selectedcmd?.SubCommands.Any() != true) { selectedCommand = selectedcmd.Type; } void print_command(CliActionTree cmd, int level, int descriptionStart) { if (cmd.IsBrowsable) { int relativePadding = descriptionStart - (level * LevelPadding); // Calculate amount of characters to pad right before description start to ensure description alignments. Console.Write($"{"".PadRight(level * LevelPadding)}{cmd.Name.PadRight(relativePadding)}"); if (cmd.Type?.IsBrowsable() ?? false) { Console.WriteLine($"{cmd.Type.GetDisplayAttribute().Description}"); } else { Console.WriteLine(); } if (cmd.IsGroup) { foreach (var subCmd in cmd.SubCommands) { print_command(subCmd, level + 1, descriptionStart); } } } } // Print default info if (selectedCommand == null) { Console.WriteLine("OpenTAP Command Line Interface ({0})", Assembly.GetExecutingAssembly().GetSemanticVersion().ToString(4)); Console.WriteLine("Usage: tap <command> [<subcommand(s)>] [<args>]\n"); if (selectedcmd == null) { Console.WriteLine("Valid commands are:"); foreach (var cmd in actionTree.SubCommands) { print_command(cmd, 0, actionTree.GetMaxCommandTreeLength(LevelPadding) + LevelPadding); } } else { Console.Write("Valid subcommands of "); print_command(selectedcmd, 0, actionTree.GetMaxCommandTreeLength(LevelPadding) + LevelPadding); } Console.WriteLine($"\nRun \"{(OperatingSystem.Current == OperatingSystem.Windows ? "tap.exe" : "tap")} " + "<command> [<subcommand>] -h\" to get additional help for a specific command.\n"); if (args.Length == 0 || args.Any(s => s.ToLower() == "--help" || s.ToLower() == "-h")) { return(0); } else { return(-1); } } if (selectedCommand != TypeData.FromType(typeof(RunCliAction)) && UserInput.Interface == null) // RunCliAction has --non-interactive flag and custom platform interaction handling. { CliUserInputInterface.Load(); } ICliAction packageAction = null; try{ packageAction = (ICliAction)selectedCommand.CreateInstance(); }catch (TargetInvocationException e1) when(e1.InnerException is System.ComponentModel.LicenseException e) { Console.Error.WriteLine("Unable to load CLI Action '{0}'", selectedCommand.GetDisplayAttribute().GetFullName()); Console.Error.WriteLine(e.Message); return(-4); } if (packageAction == null) { Console.WriteLine("Error instantiating command {0}", selectedCommand.Name); return(-3); } try { int skip = selectedCommand.GetDisplayAttribute().Group.Length + 1; // If the selected command has a group, it takes two arguments to use the command. E.g. "package create". If not, it only takes 1 argument, E.g. "restapi". return(packageAction.Execute(args.Skip(skip).ToArray())); } catch (ExitCodeException ec) { log.Error(ec.Message); return(ec.ExitCode); } catch (ArgumentException ae) { // ArgumentException usually contains several lines. // Only print the first line as an error message. // Example message: // "Directory is not a git repository. // Parameter name: repositoryDir" var lines = ae.Message.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); log.Error(lines.First()); for (int i = 1; i < lines.Length; i++) { log.Debug(lines[i]); } return(-1); } catch (OperationCanceledException ex) { log.Error(ex.Message); return(1); } catch (Exception ex) { log.Error(ex.Message); log.Debug(ex); return(-1); } finally { Log.Flush(); } }