/// <summary> /// parse and evaluate a command line<br/> /// 1. parse command line<br/> /// error or:<br/> /// 2. execute command<br/> /// A. internal command (modules) or alias<br/> /// B. underlying shell command (found in scan paths)<br/> /// file: <br/> /// C. file (batch)<br/> /// D. non executable file<br/> /// not a file:<br/> /// E. unknown command<br/> /// </summary> /// <param name="context">command evaluation context</param> /// <param name="expr">expression to be evaluated</param> /// <param name="outputX">begin x location of the command line expression in the console if applyable</param> /// <param name="postAnalysisPreExecOutput">text to be outputed before any analysis output</param> /// <returns>data returned by the analysis and the evaluation of an expression (analysis error or commmand returns or command error)</returns> public ExpressionEvaluationResult Eval( CommandEvaluationContext context, string expr, int outputX = 0, string postAnalysisPreExecOutput = null) // TODO: an eval options object would be nice { try { var pipelineParseResults = ParseCommandLine(context, SyntaxAnalyzer, expr, ExternalParserExtension); bool allValid = true; var evalParses = new List <ExpressionEvaluationResult>(); // check pipeline syntax analysis foreach (var pipelineParseResult in pipelineParseResults) { allValid &= pipelineParseResult.ParseResult.ParseResultType == ParseResultType.Valid; var evalParse = AnalysisPipelineParseResult( context, pipelineParseResult, expr, outputX, pipelineParseResult.ParseResult ); evalParses.Add(evalParse); } // eventually output the post analysis pre exec content if (!string.IsNullOrEmpty(postAnalysisPreExecOutput)) { context.Out.Echo(postAnalysisPreExecOutput); } if (!allValid) { // 💥syntax error in pipeline - break exec var err = evalParses.FirstOrDefault(); context.ShellEnv.UpdateVarLastCommandReturn(expr, null, err == null ? ReturnCode.OK : GetReturnCode(err), err?.SyntaxError); return(err); } // run pipeline var evalRes = PipelineProcessor.RunPipeline(context, pipelineParseResults.FirstOrDefault()); // update shell env context.ShellEnv.UpdateVarLastCommandReturn(expr, evalRes.Result, GetReturnCode(evalRes), evalRes.EvalErrorText, evalRes.EvalError); return(evalRes); } catch (Exception pipelineException) { // code pipeline parse or execution error // update shell env context.ShellEnv.UpdateVarLastCommandReturn(expr, ReturnCode.Error, ReturnCode.Unknown, pipelineException.Message, pipelineException); context.Errorln(pipelineException.Message); return(new ExpressionEvaluationResult(expr, null, ParseResultType.NotIdentified, null, (int)ReturnCode.Error, pipelineException, pipelineException.Message)); } }
ExpressionEvaluationResult AnalysisPipelineParseResult( CommandEvaluationContext context, PipelineParseResult pipelineParseResult, string expr, int outputX, ParseResult parseResult ) { ExpressionEvaluationResult r = null; var errorText = ""; string[] t; int idx; string serr; switch (parseResult.ParseResultType) { case ParseResultType.Empty: r = new ExpressionEvaluationResult(expr, null, parseResult.ParseResultType, null, (int)ReturnCode.OK, null); break; case ParseResultType.NotValid: /* command syntax not valid */ var perComErrs = new Dictionary <string, List <CommandSyntaxParsingResult> >(); foreach (var prs in parseResult.SyntaxParsingResults) { if (prs.CommandSyntax != null) { if (perComErrs.TryGetValue(prs.CommandSyntax?.CommandSpecification?.Name, out var lst)) { lst.Add(prs); } else { perComErrs.Add(prs.CommandSyntax.CommandSpecification.Name, new List <CommandSyntaxParsingResult> { prs }); } } } var errs = new List <string>(); var minErrPosition = int.MaxValue; var errPositions = new List <int>(); foreach (var kvp in perComErrs) { var comSyntax = kvp.Value.First().CommandSyntax; foreach (var prs in kvp.Value) { foreach (var perr in prs.ParseErrors) { minErrPosition = Math.Min(minErrPosition, perr.Position); errPositions.Add(perr.Position); if (!errs.Contains(perr.Description)) { errs.Add(perr.Description); } } errorText += Br + Red + string.Join(Br + Red, errs); } errorText += $"{Br}{Red}for syntax: {comSyntax}{Br}"; } errPositions.Sort(); errPositions = errPositions.Distinct().ToList(); t = new string[expr.Length + 2]; for (int i = 0; i < t.Length; i++) { t[i] = " "; } foreach (var errPos in errPositions) { t[GetIndex(context, errPos, expr)] = Settings.ErrorPositionMarker + ""; } serr = string.Join("", t); Error(" ".PadLeft(outputX + 1) + serr, false, false); Error(errorText); r = new ExpressionEvaluationResult(expr, errorText, parseResult.ParseResultType, null, (int)ReturnCode.NotIdentified, null); break; case ParseResultType.Ambiguous: errorText += $"{Red}ambiguous syntaxes:{Br}"; foreach (var prs in parseResult.SyntaxParsingResults) { errorText += $"{Red}{prs.CommandSyntax}{Br}"; } Error(errorText); r = new ExpressionEvaluationResult(expr, errorText, parseResult.ParseResultType, null, (int)ReturnCode.NotIdentified, null); break; case ParseResultType.NotIdentified: t = new string[expr.Length + 2]; for (int j = 0; j < t.Length; j++) { t[j] = " "; } var err = parseResult.SyntaxParsingResults.First().ParseErrors.First(); idx = err.Position; t[idx] = Settings.ErrorPositionMarker + ""; errorText += Red + err.Description; serr = string.Join("", t); context.Errorln(" ".PadLeft(outputX) + serr); context.Errorln(errorText); r = new ExpressionEvaluationResult(expr, errorText, parseResult.ParseResultType, null, (int)ReturnCode.NotIdentified, null); break; case ParseResultType.SyntaxError: t = new string[expr.Length + 2]; for (int j = 0; j < t.Length; j++) { t[j] = " "; } var err2 = parseResult.SyntaxParsingResults.First().ParseErrors.First(); idx = err2.Index; t[idx] = Settings.ErrorPositionMarker + ""; errorText += Red + err2.Description; serr = string.Join("", t); context.Errorln(" ".PadLeft(outputX) + serr); context.Errorln(errorText); r = new ExpressionEvaluationResult(expr, errorText, parseResult.ParseResultType, null, (int)ReturnCode.NotIdentified, null); break; } return(r); }
/// <summary> /// exec a file with os shell exec or orbsh shell exec /// </summary> /// <param name="context">command evaluation context</param> /// <param name="comPath">command filePath</param> /// <param name="args">command line arguments string</param> /// <param name="output">shell exec result if any</param> /// <param name="waitForExit">true if wait for exec process exits</param> /// <param name="isStreamsEchoEnabled">if true, exec process output stream is echoized to context out (dump command output)</param> /// <param name="isOutputCaptureEnabled">if true capture the exec process output and give the result in parameter 'output'</param> /// <param name="mergeErrorStreamIntoOutput">if true merge exec process err stream content to the process output content (if process out capture is enabled)</param> /// <returns>exec process return code</returns> public int ShellExec( CommandEvaluationContext context, string comPath, string args, string workingDirectory, out string output, bool waitForExit = true, bool isStreamsEchoEnabled = true, bool isOutputCaptureEnabled = true, bool mergeErrorStreamIntoOutput = true, bool redirectStandardInput = false ) { Thread inputTask = null; TextReader stdin = null; ProcessWrapper pw = null; try { output = null; workingDirectory ??= Environment.CurrentDirectory; var processStartInfo = new ProcessStartInfo() { UseShellExecute = false, //StandardOutputEncoding = Encoding.UTF8, // keep system default //StandardErrorEncoding = Encoding.UTF8, // keep system default RedirectStandardError = true, RedirectStandardInput = redirectStandardInput, // allows access to process StandardInput RedirectStandardOutput = true, CreateNoWindow = true, FileName = comPath, Arguments = args, WindowStyle = ProcessWindowStyle.Normal, WorkingDirectory = workingDirectory }; var sb = new StringBuilder(); // batch shell exec ? if (Path.GetExtension(comPath) == context.ShellEnv.GetValue <string>(ShellEnvironmentVar.settings_clp_shellExecBatchExt)) { var batchMethod = typeof(CommandLineProcessorCommands).GetMethod(nameof(CommandLineProcessorCommands.Batch)); var r = Eval(context, batchMethod, "\"" + FileSystemPath.UnescapePathSeparators(comPath) + " " + args + "\"", 0); output = sb.ToString(); return(r.EvalResultCode); } // process exec pw = ProcessWrapper.ThreadRun( processStartInfo, null, (outStr) => { if (isStreamsEchoEnabled) { context.Out.Echoln(outStr); } if (isOutputCaptureEnabled) { sb.AppendLine(outStr); } }, (errStr) => { if (isStreamsEchoEnabled) { context.Errorln(errStr); } if (isOutputCaptureEnabled && mergeErrorStreamIntoOutput) { sb.AppendLine(errStr); } } ); if (context.ShellEnv.IsOptionSetted(ShellEnvironmentVar.settings_clp_enableShellExecTraceProcessStart)) { context.Out.Echoln($"{context.ShellEnv.Colors.TaskInformation}process '{Path.GetFileName(comPath)}' [{pw.Process.Id}] started(rdc)"); } int retCode = 0; int c = -1; if (waitForExit) { if (redirectStandardInput) { inputTask = new Thread( () => { try { var cp = comPath; stdin = System.Console.In; #if dbg Debug.WriteLine($"input task started [ cp = {cp} ]"); #endif while (!(bool)pw?.Process?.HasExited) { if (System.Console.KeyAvailable) { var key = System.Console.ReadKey(true); c = key.KeyChar; if (c > -1) { context.Out.ConsolePrint("" + (char)c); #if dbg Debug.Write((char)c); #endif pw?.Process.StandardInput.Write((char)c); pw?.Process.StandardInput.Flush(); } } Thread.Sleep(1); } #if dbg Debug.WriteLine("input task exited"); #endif } catch #if dbg (Exception ex) #endif { #if dbg Debug.WriteLine($"input task exited ({ex.Message})"); #endif } }); inputTask.Name = "forward input task"; inputTask.Start(); } var cancellationTask = Task.Run(() => { try { while (!(bool)pw?.Process?.HasExited) { if (IsCancellationRequested) { pw?.Process?.Kill(); } Thread.Sleep(1); } inputTask?.Interrupt(); #if dbg Debug.WriteLine($"cancellation task exited"); #endif } catch #if dbg (Exception ex) #endif { #if dbg Debug.WriteLine($"cancellation task exited ({ex.Message})"); #endif } }); pw.Process.WaitForExit(); retCode = pw.Process.ExitCode; pw.StdOutCallBackThread.Join(); pw.StdErrCallBackThread.Join(); output = sb.ToString(); } if (context.ShellEnv.IsOptionSetted(ShellEnvironmentVar.settings_clp_enableShellExecTraceProcessEnd)) { context.Out.Echoln($"{context.ShellEnv.Colors.TaskInformation}process '{Path.GetFileName(comPath)}' exited with code: {retCode}(rdc)"); } return(retCode); } catch (Exception shellExecException) { inputTask?.Interrupt(); throw new Exception($"ShellExec error: {shellExecException}", shellExecException); } finally { } }