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

Source Generated Enums for Keyboard, Mouse, and Gamepad control binds #41

Merged
merged 3 commits into from
Apr 14, 2024
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
129 changes: 129 additions & 0 deletions LethalCompanyInputUtils.SourceGen/ControlEnumsGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using System.Collections.Generic;
using System.Linq;
using LethalCompanyInputUtils.SourceGen.Schema;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;

namespace LethalCompanyInputUtils.SourceGen;

[Generator]
public class ControlEnumsGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{

}

public void Execute(GeneratorExecutionContext context)
{
var files = context.AdditionalFiles;

foreach (var contents in Enumerable.OfType<AdditionalText>(files).Select(file => file.GetText()?.ToString()))
{
if (contents is null)
return;

var controlLayouts = JsonConvert.DeserializeObject<ControlLayouts>(contents);
if (controlLayouts is null)
continue;

var layouts = new Dictionary<string, ControlLayout>();

foreach (var layout in controlLayouts.Layouts)
{
if (!string.IsNullOrEmpty(layout.Extend) && layouts.TryGetValue(layout.Extend, out var parentLayout))
{
var extendedControls = layout.Controls;
foreach (var extendedControl in extendedControls)
extendedControl.SourceLayout = layout.Name;

var controls = new List<Control>(parentLayout.Controls);
controls.AddRange(extendedControls);

parentLayout.Controls = controls.ToArray();

layouts[layout.Extend] = parentLayout;
}
else
{
layouts[layout.Name] = layout;
}
}

foreach (var kvp in layouts)
{
var layout = kvp.Value;

var builder = new SourceBuilder();

var enumName = $"{layout.Name}Control";

builder.AppendLine("// <auto-generated/>")
.AppendLine("")
.AppendLine("using System;")
.AppendLine("")
.AppendLine("namespace LethalCompanyInputUtils.BindingPathEnums;")
.AppendLine("")
.AppendLine($"public enum {enumName}")
.AppendLine("{")
.IncrementDepth();

builder.AppendLine("/// <summary>")
.AppendLine("/// Unbound or No bind, Same as <see cref=\"Unbound\"/>")
.AppendLine("/// </summary>")
.AppendLine("None,");

builder.AppendLine("/// <summary>")
.AppendLine("/// Unbound or No bind, Same as <see cref=\"None\"/>")
.AppendLine("/// </summary>")
.AppendLine("Unbound = None,");

foreach (var control in layout.Controls)
{
var variant = control.Name.ToPascalCase();

builder.AppendLine("/// <summary>")
.AppendLine($"/// Bind Path: &lt;{layout.Name}&gt;/{control.Name}")
.AppendLine("/// </summary>")
.AppendLine($"{variant},");
}

builder.DecrementDepth()
.AppendLine("}")
.AppendLine("");

var enumVarName = enumName.ToCamelCase();

builder.AppendLine($"public static class {enumName}Extensions")
.AppendLine("{")
.IncrementDepth()
.AppendLine($"public static string ToPath(this {enumName} {enumVarName})")
.AppendLine("{")
.IncrementDepth()
.AppendLine($"return {enumVarName} switch {{")
.IncrementDepth();

builder.AppendLine($"{enumName}.None => \"\",");

foreach (var control in layout.Controls)
{
var variant = control.Name.ToPascalCase();

var layoutName = control.SourceLayout ?? layout.Name;

builder.AppendLine($"{enumName}.{variant} => \"<{layoutName}>/{control.Name}\",");
}

builder.AppendLine($"_ => throw new ArgumentOutOfRangeException(nameof({enumVarName}), {enumVarName}, null)")
.DecrementDepth()
.AppendLine("};")
.DecrementDepth()
.AppendLine("}")
.DecrementDepth()
.AppendLine("}");

context.AddSource($"{enumName}.g.cs", builder.ToString());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>

<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsRoslynComponent>true</IsRoslynComponent>

<RootNamespace>LethalCompanyInputUtils.SourceGen</RootNamespace>
<PackageId>LethalCompanyInputUtils.SourceGen</PackageId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.0"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" PrivateAssets="all" GeneratePathProperty="true" />
</ItemGroup>

<PropertyGroup>
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
</PropertyGroup>

<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\*.dll" IncludeRuntimeDependency="false" />
</ItemGroup>
</Target>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"DebugRoslynSourceGenerator": {
"commandName": "DebugRoslynComponent",
"targetProject": "../LethalCompanyInputUtils.SourceGen.Sample/LethalCompanyInputUtils.SourceGen.Sample.csproj"
}
}
}
13 changes: 13 additions & 0 deletions LethalCompanyInputUtils.SourceGen/Schema/Control.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace LethalCompanyInputUtils.SourceGen.Schema;

[Serializable]
public class Control
{
public string Name { get; set; } = "";

public string DisplayName { get; set; } = "";

public string? SourceLayout { get; set; }
}
14 changes: 14 additions & 0 deletions LethalCompanyInputUtils.SourceGen/Schema/ControlLayout.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using Newtonsoft.Json;

namespace LethalCompanyInputUtils.SourceGen.Schema;

[Serializable]
public class ControlLayout
{
public string Name { get; set; } = "";

public string Extend { get; set; } = "";

public Control[] Controls { get; set; } = [];
}
9 changes: 9 additions & 0 deletions LethalCompanyInputUtils.SourceGen/Schema/ControlLayouts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace LethalCompanyInputUtils.SourceGen.Schema;

[Serializable]
public class ControlLayouts
{
public ControlLayout[] Layouts { get; set; } = [];
}
57 changes: 57 additions & 0 deletions LethalCompanyInputUtils.SourceGen/SourceBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Text;

namespace LethalCompanyInputUtils.SourceGen;

internal class SourceBuilder
{
private readonly StringBuilder _builder = new();
private int _depth;
private bool _usedTabsThisLine;

public SourceBuilder IncrementDepth()
{
_depth++;

return this;
}

public SourceBuilder DecrementDepth()
{
_depth--;

if (_depth < 0 )
_depth = 0;

return this;
}

public SourceBuilder Append(string text)
{
if (!_usedTabsThisLine)
{
for (int i = 0; i < _depth; i++)
_builder.Append('\t');

_usedTabsThisLine = false;
}

_builder.Append(text);

return this;
}

public SourceBuilder AppendLine(string line)
{
Append(line);

_builder.Append('\n');
_usedTabsThisLine = false;

return this;
}

public override string ToString()
{
return _builder.ToString();
}
}
60 changes: 60 additions & 0 deletions LethalCompanyInputUtils.SourceGen/StringUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
namespace LethalCompanyInputUtils.SourceGen;

internal static class StringUtils
{
public static string ToCamelCase(this string text)
{
var textLowerCase = text.ToLower();

var textCamelCase = "";
for (int i = 0; i < text.Length; i++)
{
if (i == 0)
{
textCamelCase += textLowerCase[i];
continue;
}

textCamelCase += text[i];
}

return textCamelCase;
}

public static string ToPascalCase(this string text)
{
if (int.TryParse(text, out var num))
return $"Num{num}";

var textUpperCase = text.ToUpper();
var capitalizeNext = false;

var textPascalCase = "";
for (int i = 0; i < text.Length; i++)
{
if (i == 0)
{
textPascalCase += textUpperCase[i];
continue;
}

if (text[i] == ' ' || text[i] == '/')
{
capitalizeNext = true;
continue;
}

if (capitalizeNext)
{
capitalizeNext = false;
textPascalCase += textUpperCase[i];
}
else
{
textPascalCase += text[i];
}
}

return textPascalCase;
}
}
10 changes: 8 additions & 2 deletions LethalCompanyInputUtils.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,23 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
CHANGELOG.md = CHANGELOG.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LethalCompanyInputUtils.SourceGen", "LethalCompanyInputUtils.SourceGen\LethalCompanyInputUtils.SourceGen.csproj", "{D0544F61-1C64-44A0-A2BB-6DC53C41F2A2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{566DD797-F558-4E46-92A7-31080C736C97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{566DD797-F558-4E46-92A7-31080C736C97}.Release|Any CPU.ActiveCfg = Release|Any CPU
{566DD797-F558-4E46-92A7-31080C736C97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{566DD797-F558-4E46-92A7-31080C736C97}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94E68A23-78B8-4D27-BCA6-B14C238F9D67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94E68A23-78B8-4D27-BCA6-B14C238F9D67}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94E68A23-78B8-4D27-BCA6-B14C238F9D67}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94E68A23-78B8-4D27-BCA6-B14C238F9D67}.Release|Any CPU.Build.0 = Release|Any CPU
{D0544F61-1C64-44A0-A2BB-6DC53C41F2A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0544F61-1C64-44A0-A2BB-6DC53C41F2A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0544F61-1C64-44A0-A2BB-6DC53C41F2A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0544F61-1C64-44A0-A2BB-6DC53C41F2A2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
Loading
Loading