You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
127 lines
4.6 KiB
127 lines
4.6 KiB
using NLog; |
|
using Python.Runtime; |
|
using StabilityMatrix.Core.Models; |
|
|
|
namespace StabilityMatrix.Core.Python; |
|
|
|
/// <summary> |
|
/// Extracts command arguments from Python source file. |
|
/// </summary> |
|
public class ArgParser |
|
{ |
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); |
|
private readonly IPyRunner pyRunner; |
|
private string rootPath; |
|
private string moduleName; |
|
|
|
public ArgParser(IPyRunner pyRunner, string rootPath, string moduleName) |
|
{ |
|
this.pyRunner = pyRunner; |
|
this.rootPath = rootPath; |
|
this.moduleName = moduleName; |
|
} |
|
|
|
/// <summary> |
|
/// Convert a PyObject to a value that can be used as a LaunchOption value. |
|
/// </summary> |
|
public static object PyObjectToOptionValue(PyObject obj) |
|
{ |
|
var type = obj.GetPythonType().Name; |
|
return type switch |
|
{ |
|
"bool" => obj.As<bool>(), |
|
"int" => obj.As<int>(), |
|
"str" => obj.As<string>(), |
|
_ => throw new ArgumentException($"Unknown option type {type}") |
|
}; |
|
} |
|
|
|
/// <summary> |
|
/// Convert a PyObject to a LaunchOptionType enum. |
|
/// </summary> |
|
public static LaunchOptionType? PyObjectToOptionType(PyObject typeObj) |
|
{ |
|
var typeName = typeObj.GetAttr("__name__").As<string>(); |
|
return typeName switch |
|
{ |
|
"bool" => LaunchOptionType.Bool, |
|
"int" => LaunchOptionType.Int, |
|
"str" => LaunchOptionType.String, |
|
_ => null |
|
}; |
|
} |
|
|
|
public async Task<List<LaunchOptionDefinition>> GetArgsAsync() |
|
{ |
|
await pyRunner.Initialize(); |
|
|
|
return await pyRunner.RunInThreadWithLock(() => |
|
{ |
|
using var scope = Py.CreateScope(); |
|
dynamic sys = scope.Import("sys"); |
|
dynamic argparse = scope.Import("argparse"); |
|
// Add root path to sys.path |
|
sys.path.insert(0, rootPath); |
|
// Import module |
|
var argsModule = scope.Import(moduleName); |
|
var argsDict = argsModule.GetAttr("__dict__").As<PyDict>(); |
|
// Find ArgumentParser object in module |
|
dynamic? argParser = null; |
|
var argParserType = argparse.ArgumentParser; |
|
foreach (var obj in argsDict.Values()) |
|
{ |
|
if (obj.IsInstance(argParserType)) |
|
{ |
|
argParser = obj; |
|
break; |
|
} |
|
} |
|
if (argParser == null) |
|
{ |
|
throw new ArgumentException($"Could not find ArgumentParser object in module '{moduleName}'"); |
|
} |
|
// Loop through arguments |
|
var definitions = new List<LaunchOptionDefinition>(); |
|
|
|
foreach (var action in argParser._actions) |
|
{ |
|
var name = (action.dest as PyObject)?.As<string>(); |
|
if (name == null) |
|
{ |
|
throw new Exception("Argument option did not have a `dest` value"); |
|
} |
|
var optionStrings = ((PyObject) action.option_strings).As<string[]>(); |
|
var dest = (action.dest as PyObject)?.As<string>(); |
|
// var nArgs = (action.nargs as PyObject)?.As<int>(); |
|
var isConst = (action.@const as PyObject)?.IsTrue() ?? false; |
|
var isRequired = (action.required as PyObject)?.IsTrue() ?? false; |
|
var type = action.type as PyObject; |
|
// Bool types will have a type of None (null) |
|
var optionType = type == null ? LaunchOptionType.Bool : PyObjectToOptionType(type); |
|
if (optionType == null) |
|
{ |
|
Logger.Warn("Skipping option {Dest} with type {Name}", dest, type); |
|
continue; |
|
} |
|
// Parse default |
|
var @default = action.@default as PyObject; |
|
var defaultValue = @default != null ? PyObjectToOptionValue(@default) : null; |
|
|
|
var help = (action.help as PyObject)?.As<string>(); |
|
|
|
definitions.Add(new LaunchOptionDefinition |
|
{ |
|
Name = help ?? name, |
|
Description = help, |
|
Options = new List<string> { optionStrings[0] }, |
|
// ReSharper disable once ConstantNullCoalescingCondition |
|
Type = optionType ?? LaunchOptionType.Bool, |
|
DefaultValue = defaultValue, |
|
MinSelectedOptions = isRequired ? 1 : 0, |
|
}); |
|
} |
|
|
|
return definitions; |
|
}); |
|
} |
|
}
|
|
|