Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstract command line parser #443

Merged
merged 4 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cesium.CodeGen/ICompilerReporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Cesium.CodeGen;

public interface ICompilerReporter
{
void ReportError(string message);
void ReportInformation(string message);
}
28 changes: 28 additions & 0 deletions Cesium.Compiler.Tests/Cesium.Compiler.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Cesium.Compiler\Cesium.Compiler.csproj" />
</ItemGroup>

</Project>
132 changes: 132 additions & 0 deletions Cesium.Compiler.Tests/CommandLineParsingTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using Cesium.CodeGen;

namespace Cesium.Compiler.Tests
{
public class CommandLineParsingTest
{
[Fact]
public async Task CompileMainArguments()
{
var args = new[] { "C:\\doom-clr\\doom.c", "-o", "C:\\\\Cesium\\\\Cesium.IntegrationTests/bin/doom.exe" };
var reporter = new MockCompilerReporter();

var errorCode = await CommandLineParser.ParseCommandLineArgs(args, reporter, args =>
{
Assert.Equal(new[] { "C:\\doom-clr\\doom.c" }, args.InputFilePaths);
Assert.Equal("C:\\\\Cesium\\\\Cesium.IntegrationTests/bin/doom.exe", args.OutputFilePath);
Assert.Empty(reporter.Errors);
NoInformationalMessages(reporter);
return Task.FromResult(0);
});
NoInformationalMessages(reporter);
Assert.Empty(reporter.Errors);
Assert.Equal(0, errorCode);
}

[Fact]
public async Task MissingOutputPath()
{
var args = new[] { "C:\\doom-clr\\doom.c" };
var reporter = new MockCompilerReporter();

var errorCode = await CommandLineParser.ParseCommandLineArgs(args, reporter, args =>
{
return Task.FromResult(0);
});
NoInformationalMessages(reporter);
Assert.Equal(new[] { "Required option 'o, out' is missing." }, reporter.Errors);
Assert.Equal(3, errorCode);
}

[Fact]
public async Task MissingSourceFiles()
{
var args = new[] { "-o", "C:\\doom-clr\\doom.exe" };
var reporter = new MockCompilerReporter();

var errorCode = await CommandLineParser.ParseCommandLineArgs(args, reporter, args =>
{
return Task.FromResult(0);
});
NoInformationalMessages(reporter);
Assert.Equal(new[] { "Input file paths should be defined." }, reporter.Errors);
Assert.Equal(2, errorCode);
}

[Fact]
public async Task CompileMultipleCompilationFilesMainArguments()
{
var args = new[] { "C:\\folder\\file1.c", "C:\\folder\\file2.c", "-o", "C:\\\\Cesium\\\\Cesium.IntegrationTests/bin/doom.exe" };
var reporter = new MockCompilerReporter();

var errorCode = await CommandLineParser.ParseCommandLineArgs(args, reporter, args =>
{
Assert.Equal(new[] { "C:\\folder\\file1.c", "C:\\folder\\file2.c" }, args.InputFilePaths);
Assert.Equal("C:\\\\Cesium\\\\Cesium.IntegrationTests/bin/doom.exe", args.OutputFilePath);
return Task.FromResult(0);
});
NoInformationalMessages(reporter);
Assert.Empty(reporter.Errors);
Assert.Equal(0, errorCode);
}

[Fact]
public async Task PreprocessFile()
{
var args = new[] { "C:\\Cesium\\Cesium.Samples\\getopt.c", "-E" };
var reporter = new MockCompilerReporter();

var errorCode = await CommandLineParser.ParseCommandLineArgs(args, reporter, args =>
{
Assert.Equal(new[] { "C:\\Cesium\\Cesium.Samples\\getopt.c" }, args.InputFilePaths);
Assert.True(args.ProducePreprocessedFile);
return Task.FromResult(0);
});
Assert.Empty(reporter.InformationMessages);
Assert.Empty(reporter.Errors);
Assert.Equal(0, errorCode);
}

[Fact]
public async Task MultipleIncludeFiles()
{
var args = new[] { "C:\\Cesium\\Cesium.Samples\\getopt.c", "-o", "C:\\\\Cesium\\\\Cesium.IntegrationTests/bin/doom.exe", "-I", "C:\\\\Cesium\\\\Cesium.Samples\\\\", "-I", "C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.22621.0\\um\\" };
var reporter = new MockCompilerReporter();

var errorCode = await CommandLineParser.ParseCommandLineArgs(args, reporter, args =>
{
Assert.Equal(new[] { "C:\\Cesium\\Cesium.Samples\\getopt.c" }, args.InputFilePaths);
Assert.Equal("C:\\\\Cesium\\\\Cesium.IntegrationTests/bin/doom.exe", args.OutputFilePath);
Assert.Equal(new[] { "C:\\\\Cesium\\\\Cesium.Samples\\\\", "C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.22621.0\\um\\" }, args.IncludeDirectories);
return Task.FromResult(0);
});
NoInformationalMessages(reporter);
Assert.Empty(reporter.Errors);
Assert.Equal(0, errorCode);
}

[Fact]
public async Task MultipleDefinesFiles()
{
var args = new[] { "C:\\Cesium\\Cesium.Samples\\getopt.c", "-o", "C:\\\\Cesium\\\\Cesium.IntegrationTests/bin/doom.exe", "-D", "TEST_1", "-D", "TEST_2" };
var reporter = new MockCompilerReporter();

var errorCode = await CommandLineParser.ParseCommandLineArgs(args, reporter, args =>
{
Assert.Equal(new[] { "C:\\Cesium\\Cesium.Samples\\getopt.c" }, args.InputFilePaths);
Assert.Equal("C:\\\\Cesium\\\\Cesium.IntegrationTests/bin/doom.exe", args.OutputFilePath);
Assert.Equal(new[] { "TEST_1", "TEST_2" }, args.DefineConstant);
return Task.FromResult(0);
});
NoInformationalMessages(reporter);
Assert.Empty(reporter.Errors);
Assert.Equal(0, errorCode);
}

private static void NoInformationalMessages(MockCompilerReporter reporter)
{
Assert.NotNull(reporter.InformationMessages.Single(_ => _.StartsWith("Cesium v")));
Assert.Empty(reporter.InformationMessages.Where(_ => !_.StartsWith("Cesium v")));
}
}
}
18 changes: 18 additions & 0 deletions Cesium.Compiler.Tests/MockCompilerReporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Cesium.CodeGen;

namespace Cesium.Compiler.Tests;

internal class MockCompilerReporter : ICompilerReporter
{
public List<string> Errors { get; set; } = new();
public List<string> InformationMessages { get; set; } = new();
public void ReportError(string message)
{
this.Errors.Add(message);
}

public void ReportInformation(string message)
{
this.InformationMessages.Add(message);
}
}
1 change: 1 addition & 0 deletions Cesium.Compiler.Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
2 changes: 1 addition & 1 deletion Cesium.Compiler/Cesium.Compiler.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Yoakke.SynKit.C.Syntax" Version="2023.5.30-5.54.38-nightly" />
</ItemGroup>

Expand Down
60 changes: 60 additions & 0 deletions Cesium.Compiler/CommandLineParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
namespace Cesium.Compiler;

using Cesium.CodeGen;
using CommandLine;
using CommandLine.Text;
using System.Reflection;

public class CommandLineParser
{
public static async Task<int> ParseCommandLineArgs(string[] args, ICompilerReporter reporter, Func<Arguments, Task<int>> worker)
{
var parserResult = new Parser(x =>
{
x.HelpWriter = null;
x.AllowMultiInstance = true;
}).ParseArguments<Arguments>(args);

return await parserResult.MapResult(async args =>
{
if (!args.NoLogo && !args.ProducePreprocessedFile)
{
reporter.ReportInformation($"Cesium v{Assembly.GetExecutingAssembly().GetName().Version}");
}

if (args.InputFilePaths.Count == 0)
{
reporter.ReportError("Input file paths should be defined.");
return 2;
}

if (!args.ProducePreprocessedFile && string.IsNullOrWhiteSpace(args.OutputFilePath))
{
reporter.ReportError("Required option 'o, out' is missing.");
return 3;
}

return await worker(args);
},
_ =>
{
string helpText = PrepareHelpText(parserResult);
Console.WriteLine(helpText);
return Task.FromResult(-1);
});
}

static string PrepareHelpText<T>(ParserResult<T> result)
{
if (result is NotParsed<T> notParsed && notParsed.Errors.IsVersion())
return HelpText.AutoBuild(result);

var helpText = HelpText.AutoBuild(result, h =>
{
h.AddEnumValuesToHelpText = true;
return HelpText.DefaultParsingErrorsHandler(result, h);
}, e => e);

return helpText;
}
}
47 changes: 9 additions & 38 deletions Cesium.Compiler/Main.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,10 @@
using System.Reflection;
using Cesium.CodeGen;
using Cesium.Compiler;
using Cesium.Core;
using CommandLine;
using CommandLine.Text;
using Mono.Cecil;

var parserResult = new Parser(x => x.HelpWriter = null).ParseArguments<Arguments>(args);

return await parserResult.MapResult(async args =>
await CommandLineParser.ParseCommandLineArgs(args, new CompilerReporter(), async args =>
{
if (!args.NoLogo && !args.ProducePreprocessedFile)
{
Console.WriteLine($"Cesium v{Assembly.GetExecutingAssembly().GetName().Version}");
}

if (args.InputFilePaths.Count == 0)
{
Console.Error.WriteLine("Input file paths should be defined.");
return 2;
}

if (!args.ProducePreprocessedFile && string.IsNullOrWhiteSpace(args.OutputFilePath))
{
Console.Error.WriteLine("Required option 'o, out' is missing.");
return 3;
}

var targetArchitectureSet = args.TargetArchitectureSet;
var targetRuntime = args.Framework switch
{
Expand Down Expand Up @@ -59,24 +37,17 @@
args.IncludeDirectories.ToList(),
args.ProducePreprocessedFile);
return await Compilation.Compile(args.InputFilePaths, args.OutputFilePath, compilationOptions);
},
_ =>
{
string helpText = PrepareHelpText(parserResult);
Console.WriteLine(helpText);
return Task.FromResult(-1);
});

static string PrepareHelpText<T>(ParserResult<T> result)
class CompilerReporter : ICompilerReporter
{
if (result is NotParsed<T> notParsed && notParsed.Errors.IsVersion())
return HelpText.AutoBuild(result);

var helpText = HelpText.AutoBuild(result, h =>
public void ReportError(string message)
{
h.AddEnumValuesToHelpText = true;
return HelpText.DefaultParsingErrorsHandler(result, h);
}, e => e);
Console.Error.WriteLine(message);
}

return helpText;
public void ReportInformation(string message)
{
Console.Out.WriteLine(message);
}
}
10 changes: 8 additions & 2 deletions Cesium.sln
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{F3EB
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{986C6A13-2973-4405-BA3C-452512152835}"
ProjectSection(SolutionItems) = preProject
docs\architecture-sets.md = docs\architecture-sets.md
docs\builtins.md = docs\builtins.md
docs\exceptions.md = docs\exceptions.md
docs\language-extensions.md = docs\language-extensions.md
docs\tests.md = docs\tests.md
docs\type-system.md = docs\type-system.md
docs\architecture-sets.md = docs\architecture-sets.md
docs\builtins.md = docs\builtins.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cesium.Preprocessor", "Cesium.Preprocessor\Cesium.Preprocessor.csproj", "{0CDF730D-2A2A-437F-B27F-2BB04770C709}"
Expand All @@ -77,6 +77,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cesium.Core", "Cesium.Core\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cesium.Runtime.Tests", "Cesium.Runtime.Tests\Cesium.Runtime.Tests.csproj", "{526D8E61-6143-490F-BB9D-E7CD78512E55}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cesium.Compiler.Tests", "Cesium.Compiler.Tests\Cesium.Compiler.Tests.csproj", "{A5E33E79-E710-4F2F-838F-20CD0A3142F3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -131,6 +133,10 @@ Global
{526D8E61-6143-490F-BB9D-E7CD78512E55}.Debug|Any CPU.Build.0 = Debug|Any CPU
{526D8E61-6143-490F-BB9D-E7CD78512E55}.Release|Any CPU.ActiveCfg = Release|Any CPU
{526D8E61-6143-490F-BB9D-E7CD78512E55}.Release|Any CPU.Build.0 = Release|Any CPU
{A5E33E79-E710-4F2F-838F-20CD0A3142F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5E33E79-E710-4F2F-838F-20CD0A3142F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5E33E79-E710-4F2F-838F-20CD0A3142F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5E33E79-E710-4F2F-838F-20CD0A3142F3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Loading