Skip to content

Commit dd678ba

Browse files
authored
Add a cli tool to generate proto or cs files from a .msg file. (#7)
1 parent 087f012 commit dd678ba

File tree

6 files changed

+251
-0
lines changed

6 files changed

+251
-0
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,23 @@ This is a DSL which simplifies the writing of ProtoBuf contracts for [Zebus](htt
1313
- [`Zebus.MessageDsl.Build`](https://www.nuget.org/packages/Zebus.MessageDsl.Build) provides a MSBuild code generator which will translate `.msg` files in your project
1414
- [`Zebus.MessageDsl.Generator`](https://www.nuget.org/packages/Zebus.MessageDsl.Generator) provides a Roslyn source generator which will translate `.msg` files in your project
1515

16+
## .NET Tool
17+
18+
We offer a .NET CLI tool to generate code from `.msg` files. This tool can be installed either globally or locally within your project.
19+
20+
To install the tool globally:
21+
22+
```bash
23+
dotnet tool install -g Zebus.MessageDsl.Tool
24+
```
25+
26+
After installation, the tool will be accessible as messagedsl (on Linux, you may need to refresh your environment variables). It supports the following options:
27+
28+
--namespace: Sets the default namespace for the generated files (optional).
29+
--format: Specifies the output format (csharp or proto). Defaults to proto.
30+
31+
The tool accepts a path to a .msg file as an argument. If no argument is provided, it will read the .msg file from the standard input.
32+
1633
## Documentation
1734

1835
- [DSL Syntax](docs/Syntax.md)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
8+
<IsPackable>false</IsPackable>
9+
<IsTestProject>true</IsTestProject>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
14+
<PackageReference Include="NUnit" Version="3.13.3"/>
15+
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
16+
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
17+
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<ProjectReference Include="..\Abc.Zebus.MessageDsl.Tool\Abc.Zebus.MessageDsl.Tool.csproj" />
22+
</ItemGroup>
23+
24+
</Project>
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using NUnit.Framework;
2+
3+
namespace Abc.Zebus.MessageDsl.Tool.Tests;
4+
5+
[TestFixture]
6+
public class ToolTests
7+
{
8+
private TextWriter _originalOut = null!;
9+
private TextReader _originalIn = null!;
10+
private StringWriter _output = null!;
11+
12+
[SetUp]
13+
public void Setup()
14+
{
15+
_originalOut = Console.Out;
16+
_originalIn = Console.In;
17+
_output = new StringWriter();
18+
Console.SetOut(_output);
19+
}
20+
21+
[TearDown]
22+
public void TearDown()
23+
{
24+
Console.SetOut(_originalOut);
25+
Console.SetIn(_originalIn);
26+
}
27+
28+
[Test]
29+
public void should_generate_csharp_with_namespace()
30+
{
31+
Console.SetIn(new StringReader("TestMessage()"));
32+
33+
var exitCode = Program.Main(["--namespace", "TestNS", "--format", "CSharp"]);
34+
Assert.That(exitCode, Is.EqualTo(0));
35+
var outputString = _output.ToString();
36+
Assert.That(outputString, Does.Contain("namespace TestNS"));
37+
Assert.That(outputString, Does.Contain("class TestMessage"));
38+
}
39+
40+
[Test]
41+
public void should_generate_proto_output_with_empty_namespace_when_not_provided()
42+
{
43+
var tempFile = Path.GetTempFileName();
44+
try
45+
{
46+
File.WriteAllText(tempFile, "TestMessage()");
47+
48+
var exitCode = Program.Main([tempFile, "--format", "Proto"]);
49+
Assert.That(exitCode, Is.EqualTo(0));
50+
var outputString = _output.ToString();
51+
Assert.That(outputString.Contains("message TestMessage"));
52+
}
53+
finally
54+
{
55+
File.Delete(tempFile);
56+
}
57+
}
58+
59+
[Test]
60+
public void should_error_on_file_not_found()
61+
{
62+
var exitCode = Program.Main(["invalid_file.msg"]);
63+
Assert.That(exitCode, Is.EqualTo(1));
64+
}
65+
66+
[Test]
67+
public void should_error_on_directory()
68+
{
69+
var aDirectory = Environment.CurrentDirectory;
70+
var exitCode = Program.Main([aDirectory]);
71+
Assert.That(exitCode, Is.EqualTo(1));
72+
}
73+
74+
[Test]
75+
public void should_error_on_invalid_msg()
76+
{
77+
Console.SetIn(new StringReader("InvalidSyntax"));
78+
var exitCode = Program.Main([]);
79+
Assert.That(exitCode, Is.EqualTo(1));
80+
}
81+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<PackAsTool>true</PackAsTool>
9+
<ToolCommandName>messagedsl</ToolCommandName>
10+
<RollForward>major</RollForward>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1"/>
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<ProjectReference Include="..\Abc.Zebus.MessageDsl\Abc.Zebus.MessageDsl.csproj"/>
19+
</ItemGroup>
20+
21+
</Project>
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System.CommandLine;
2+
using Abc.Zebus.MessageDsl.Ast;
3+
using Abc.Zebus.MessageDsl.Generator;
4+
5+
namespace Abc.Zebus.MessageDsl.Tool;
6+
7+
public class Program
8+
{
9+
public static int Main(string[] args)
10+
{
11+
var mainCommand = new RootCommand();
12+
var path = new Argument<string?>(".msg file", "The .msg file to process.")
13+
{
14+
Arity = ArgumentArity.ZeroOrOne
15+
};
16+
17+
var defaultNamespace = new Option<string?>(name: "--namespace", description: "The default namespace to use for the generated files", getDefaultValue: () => null);
18+
var outputType = new Option<Format>(name: "--format", description: "The output format to generate (csharp or proto)", getDefaultValue: () => Format.Proto);
19+
20+
mainCommand.AddArgument(path);
21+
mainCommand.AddOption(defaultNamespace);
22+
mainCommand.AddOption(outputType);
23+
24+
mainCommand.SetHandler(
25+
context =>
26+
{
27+
var parse = context.ParseResult;
28+
var parsedPath = parse.GetValueForArgument(path);
29+
var parsedNamespace = parse.GetValueForOption(defaultNamespace);
30+
var parsedFormat = parse.GetValueForOption(outputType);
31+
string? txt = null;
32+
try
33+
{
34+
if (parsedPath != null)
35+
txt = File.ReadAllText(parsedPath);
36+
}
37+
catch (FileNotFoundException)
38+
{
39+
Console.Error.WriteLine($"File {parsedPath} does not exist.");
40+
context.ExitCode = 1;
41+
return;
42+
}
43+
catch (Exception ex)
44+
{
45+
Console.Error.WriteLine($"Error reading file {parsedPath}: {ex.Message}");
46+
context.ExitCode = 1;
47+
return;
48+
}
49+
50+
txt ??= Console.In.ReadToEnd();
51+
var parsed = ParsedContracts.Parse(txt, parsedNamespace);
52+
foreach (var error in parsed.Errors)
53+
{
54+
Console.Error.WriteLine(error);
55+
}
56+
57+
if (parsed.Errors.Count != 0)
58+
{
59+
context.ExitCode = 1;
60+
return;
61+
}
62+
63+
foreach (var message in parsed.Messages)
64+
{
65+
message.Options.Proto = true;
66+
}
67+
68+
switch (parsedFormat)
69+
{
70+
case Format.CSharp:
71+
{
72+
var cs = CSharpGenerator.Generate(parsed);
73+
Console.Write(cs);
74+
return;
75+
}
76+
case Format.Proto:
77+
{
78+
var proto = ProtoGenerator.Generate(parsed);
79+
Console.Write(proto);
80+
return;
81+
}
82+
default:
83+
throw new InvalidOperationException();
84+
}
85+
}
86+
);
87+
88+
return mainCommand.Invoke(args);
89+
}
90+
}
91+
92+
internal enum Format
93+
{
94+
Proto,
95+
CSharp
96+
}

src/Abc.Zebus.MessageDsl.sln

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{A8144077-4
3232
NuGetReadme.md = NuGetReadme.md
3333
EndProjectSection
3434
EndProject
35+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abc.Zebus.MessageDsl.Tool", "Abc.Zebus.MessageDsl.Tool\Abc.Zebus.MessageDsl.Tool.csproj", "{7CB3D85B-543E-4066-9689-29847BDBDFE0}"
36+
EndProject
37+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abc.Zebus.MessageDsl.Tool.Tests", "Abc.Zebus.MessageDsl.Tool.Tests\Abc.Zebus.MessageDsl.Tool.Tests.csproj", "{8B4FFEB6-6A8D-4DDA-A6B3-36CA326DF2CC}"
38+
EndProject
3539
Global
3640
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3741
Debug|Any CPU = Debug|Any CPU
@@ -66,6 +70,14 @@ Global
6670
{97EB9627-0DA3-4A81-90EE-F30772A36E40}.Debug|Any CPU.Build.0 = Debug|Any CPU
6771
{97EB9627-0DA3-4A81-90EE-F30772A36E40}.Release|Any CPU.ActiveCfg = Release|Any CPU
6872
{97EB9627-0DA3-4A81-90EE-F30772A36E40}.Release|Any CPU.Build.0 = Release|Any CPU
73+
{7CB3D85B-543E-4066-9689-29847BDBDFE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
74+
{7CB3D85B-543E-4066-9689-29847BDBDFE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
75+
{7CB3D85B-543E-4066-9689-29847BDBDFE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
76+
{7CB3D85B-543E-4066-9689-29847BDBDFE0}.Release|Any CPU.Build.0 = Release|Any CPU
77+
{8B4FFEB6-6A8D-4DDA-A6B3-36CA326DF2CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
78+
{8B4FFEB6-6A8D-4DDA-A6B3-36CA326DF2CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
79+
{8B4FFEB6-6A8D-4DDA-A6B3-36CA326DF2CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
80+
{8B4FFEB6-6A8D-4DDA-A6B3-36CA326DF2CC}.Release|Any CPU.Build.0 = Release|Any CPU
6981
EndGlobalSection
7082
GlobalSection(SolutionProperties) = preSolution
7183
HideSolutionNode = FALSE

0 commit comments

Comments
 (0)