using DotPython.Hosting.Packaging;
using DotPython.Language.Diagnostics;
using DotPython.Language.Text;
using DotPython.Runtime.Managed;
using DotPython.Runtime.Managed.Execution;
namespace DotPython.Cli;
internal static class DotPythonCommand
{
public static int Run(
IReadOnlyList arguments,
TextReader standardInput,
TextWriter standardOutput,
TextWriter standardError,
CancellationToken cancellationToken = default
)
{
ArgumentNullException.ThrowIfNull(arguments);
ArgumentNullException.ThrowIfNull(standardInput);
ArgumentNullException.ThrowIfNull(standardOutput);
ArgumentNullException.ThrowIfNull(standardError);
if (arguments.Count == 0)
{
standardError.WriteLine(
"dotpython: interactive mode is not implemented; use -c, -, or a script path"
);
return 2;
}
if (arguments[0] is "-h" or "--help")
{
WriteHelp(standardOutput);
return 0;
}
if (arguments[0] is "-V" or "--version")
{
var compatibility = ManagedRuntimeDescriptor.Compatibility;
standardOutput.WriteLine(
$"DotPython {compatibility.Implementation} (Python {compatibility.LanguageVersion})"
);
return 0;
}
if (arguments[0] == "wheel")
{
return RunWheelCommand(arguments, standardOutput, standardError);
}
if (
!TryReadSource(
arguments,
standardInput,
standardError,
out var source,
out var moduleSearchPath
)
)
{
return 2;
}
try
{
var engine = new ManagedPythonEngine(
new ManagedModuleDiscoveryOptions { SearchPaths = [moduleSearchPath] }
);
var result = engine.Execute(
source,
standardOutput,
new ManagedExecutionOptions { StandardInput = standardInput },
cancellationToken
);
if (result.Success)
{
return 0;
}
foreach (var diagnostic in result.Diagnostics)
{
WriteDiagnostic(result.Source, diagnostic, standardError);
}
return 1;
}
catch (OperationCanceledException)
{
standardError.WriteLine("dotpython: execution cancelled");
return 130;
}
catch (Exception exception)
when (exception
is IOException
or InvalidDataException
or UnauthorizedAccessException
or ArgumentException
)
{
standardError.WriteLine($"dotpython: module discovery failed: {exception.Message}");
return 1;
}
}
private static int RunWheelCommand(
IReadOnlyList arguments,
TextWriter standardOutput,
TextWriter standardError
)
{
if (arguments.Count != 3 || arguments[1] != "inspect")
{
standardError.WriteLine("dotpython: usage: dotpython wheel inspect ");
return 2;
}
try
{
var inspection = PythonWheelInspector.Inspect(arguments[2]);
standardOutput.WriteLine(PythonWheelInspectionJson.Serialize(inspection));
return inspection.IsValid ? 0 : 1;
}
catch (IOException exception)
{
standardError.WriteLine(
$"dotpython: cannot inspect '{arguments[2]}': {exception.Message}"
);
return 1;
}
catch (UnauthorizedAccessException exception)
{
standardError.WriteLine(
$"dotpython: cannot inspect '{arguments[2]}': {exception.Message}"
);
return 1;
}
}
private static bool TryReadSource(
IReadOnlyList arguments,
TextReader standardInput,
TextWriter standardError,
out SourceText source,
out string moduleSearchPath
)
{
if (arguments[0] == "-c")
{
if (arguments.Count < 2)
{
standardError.WriteLine("dotpython: argument expected for -c");
source = new SourceText(string.Empty, "");
moduleSearchPath = Directory.GetCurrentDirectory();
return false;
}
source = new SourceText(arguments[1], "");
moduleSearchPath = Directory.GetCurrentDirectory();
return true;
}
if (arguments[0] == "-")
{
source = new SourceText(standardInput.ReadToEnd(), "");
moduleSearchPath = Directory.GetCurrentDirectory();
return true;
}
if (arguments[0].StartsWith('-'))
{
standardError.WriteLine($"dotpython: unsupported option '{arguments[0]}'");
source = new SourceText(string.Empty, "");
moduleSearchPath = Directory.GetCurrentDirectory();
return false;
}
try
{
var fullPath = Path.GetFullPath(arguments[0]);
source = new SourceText(File.ReadAllText(fullPath), fullPath);
moduleSearchPath = Path.GetDirectoryName(fullPath) ?? Directory.GetCurrentDirectory();
return true;
}
catch (IOException exception)
{
standardError.WriteLine(
$"dotpython: cannot read '{arguments[0]}': {exception.Message}"
);
source = new SourceText(string.Empty, arguments[0]);
moduleSearchPath = Directory.GetCurrentDirectory();
return false;
}
catch (UnauthorizedAccessException exception)
{
standardError.WriteLine(
$"dotpython: cannot read '{arguments[0]}': {exception.Message}"
);
source = new SourceText(string.Empty, arguments[0]);
moduleSearchPath = Directory.GetCurrentDirectory();
return false;
}
}
private static void WriteDiagnostic(
SourceText source,
Diagnostic diagnostic,
TextWriter standardError
)
{
var position = source.GetLinePosition(Math.Min(diagnostic.Span.Start, source.Length));
standardError.WriteLine(
$"{source.FilePath ?? ""}:{position.Line + 1}:{position.Character + 1}: "
+ $"{diagnostic.Code}: {diagnostic.Message}"
);
}
private static void WriteHelp(TextWriter output)
{
output.WriteLine("Usage: dotpython -c command [args]");
output.WriteLine(" dotpython - [args]");
output.WriteLine(" dotpython script.py [args]");
output.WriteLine(" dotpython wheel inspect artifact.whl");
output.WriteLine();
output.WriteLine(
"Current managed subset: literals, names, assignment, arithmetic, and calls."
);
}
}