using FileFlows.Plugin;
using Jint;
using Jint.Runtime;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
namespace BasicNodes;
///
/// A Javascript code executor
///
internal class JavascriptExecutor
{
///
/// Delegate used by the executor so log messages can be passed from the javascript code into the flow runner
///
/// the parameters for the logger
delegate void LogDelegate(params object[] values);
///
/// Executes javascript
///
/// the execution arguments
/// the output to be called next
public static int Execute(JavascriptExecutionArgs execArgs)
{
if (string.IsNullOrEmpty(execArgs?.Code))
return -1; // no code, flow cannot continue doesnt know what to do
var args = execArgs.Args;
try
{
long fileSize = 0;
var fileInfo = new FileInfo(args.WorkingFile);
if (fileInfo.Exists)
fileSize = fileInfo.Length;
// replace Variables. with dictionary notation
string tcode = execArgs.Code;
foreach (string k in args.Variables.Keys.OrderByDescending(x => x.Length))
{
// replace Variables.Key or Variables?.Key?.Subkey etc to just the variable
// so Variables.file?.Orig.Name, will be replaced to Variables["file.Orig.Name"]
// since its just a dictionary key value
string keyRegex = @"Variables(\?)?\." + k.Replace(".", @"(\?)?\.");
object? value = args.Variables[k];
if (value is JsonElement jElement)
{
if (jElement.ValueKind == JsonValueKind.String)
value = jElement.GetString();
if (jElement.ValueKind == JsonValueKind.Number)
value = jElement.GetInt64();
}
tcode = Regex.Replace(tcode, keyRegex, "Variables['" + k + "']");
}
var sb = new StringBuilder();
var log = new
{
ILog = new LogDelegate(args.Logger.ILog),
DLog = new LogDelegate(args.Logger.DLog),
WLog = new LogDelegate(args.Logger.WLog),
ELog = new LogDelegate(args.Logger.ELog),
};
var engine = new Engine(options =>
{
options.LimitMemory(4_000_000);
options.MaxStatements(500);
})
.SetValue("Logger", args.Logger)
.SetValue("Variables", args.Variables)
.SetValue("Flow", args)
.SetValue(nameof(FileInfo), new Func((string file) => new FileInfo(file)))
.SetValue(nameof(DirectoryInfo), new Func((string path) => new DirectoryInfo(path))); ;
foreach (var arg in execArgs.AdditionalArguments)
engine.SetValue(arg.Key, arg.Value);
var result = int.Parse(engine.Evaluate(tcode).ToObject().ToString());
return result;
}
catch(JavaScriptException ex)
{
// print out the code block for debugging
int lineNumber = 0;
var lines = execArgs.Code.Split('\n');
string pad = "D" + (lines.ToString().Length);
args.Logger.DLog("Code: " + Environment.NewLine +
string.Join("\n", lines.Select(x => (++lineNumber).ToString("D3") + ": " + x)));
args.Logger?.ELog($"Failed executing script [{ex.LineNumber}, {ex.Column}]: {ex.Message}");
return -1;
}
catch (Exception ex)
{
args.Logger?.ELog("Failed executing function: " + ex.Message + Environment.NewLine + ex.StackTrace);
return -1;
}
}
}
///
/// The arguments to pass to the javascript executor
///
public class JavascriptExecutionArgs
{
///
/// Gets or sets the code to execute
///
public string Code { get; set; }
///
/// Gets or sets teh NodeParameters
///
public NodeParameters Args { get; set; }
///
/// Gets a collection of additional arguments to be passed to the javascript executor
///
public Dictionary AdditionalArguments { get; private set; } = new Dictionary();
}