Skip to content

Commit

Permalink
feat: #6 New source of truth (#7)
Browse files Browse the repository at this point in the history
A new source of truth uses the JSON embedded directly in the PDF specification from https://usb.org. Dependency on https://github.com/IntergatedCircuits/hid-usage-tables has been removed.

All new code generator is written using Roslyn Source generators instead of T4 templating.

Updated to latest HID Usage Table specifications, and latest NuGets.

Note, there are quite a large number of enumeration value changes, etc. due to the update in specifications and tweaks to naming.
  • Loading branch information
thargy authored Aug 17, 2022
1 parent c256897 commit f565ecc
Show file tree
Hide file tree
Showing 123 changed files with 27,436 additions and 26,981 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -337,4 +337,8 @@ ASALocalRun/
.localhistory/

# BeatPulse healthcheck temp database
healthchecksdb
healthchecksdb

# Ignore cached HID Table specifications
/HIDDevices/Generated/*.json
/HIDDevices/Generated/*.pdf
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
[submodule "HIDDevices/Usages/hid-usage-tables"]
path = HIDDevices/Usages/hid-usage-tables
url = https://github.com/IntergatedCircuits/hid-usage-tables
16 changes: 16 additions & 0 deletions HIDDevices.Generator/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## Release 3.0

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
HUT0001 | Generation | Info | Generation succeeded
HUT00FF | Generation | Error | Generation was cancelled
HUT1001 | Caching | Warning | Could not find/create HID Usage Table Caching folder.
HUT1002 | Caching | Warning | Caching disabled as cache file locations could not be determined.
HUT1003 | Caching | Error | PDF Not found
HUT2001 | Deserialization | Error | Deserialization of the JSON HID USage Tables failed.
HUT2002 | Deserialization | Error | JSON attachment not found in PDF file.
HUT2003 | Deserialization | Error | Error extracting JSON attachment from PDF file.

; See https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md for explanation
1 change: 1 addition & 0 deletions HIDDevices.Generator/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
### New Rules
194 changes: 194 additions & 0 deletions HIDDevices.Generator/Diagnostics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// Licensed under the Apache License, Version 2.0 (the "License").
// See the LICENSE file in the project root for more information.

using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.CodeAnalysis;

namespace HIDDevices.Generator;

public static class Diagnostics
{
// ReSharper disable ArrangeObjectCreationWhenTypeEvident - See https://github.com/dotnet/roslyn-analyzers/issues/5957
#pragma warning disable IDE0090 // Use 'new(...)'
public static readonly DiagnosticDescriptor Completed = new DiagnosticDescriptor(
"HUT0001",
"Generation succeeded",
"Generation succeeded for version {0}. {1} usages in {2} usage pages in {3:g}.",
"Generation",
DiagnosticSeverity.Info,
true);

public static readonly DiagnosticDescriptor Cancelled = new DiagnosticDescriptor(
"HUT00FF",
"Cancelled",
"Generation was cancelled",
"Generation",
DiagnosticSeverity.Error,
true);

public static readonly DiagnosticDescriptor CacheFolderCreationFailed = new DiagnosticDescriptor(
"HUT1001",
"Could not find/create HID Usage Table Caching folder",
"Folder {0} could not be created: {1}",
"Caching",
DiagnosticSeverity.Warning,
true);

public static readonly DiagnosticDescriptor CachingDisabled = new DiagnosticDescriptor(
"HUT1002",
"Caching disabled",
"Caching disabled as cache file locations could not be determined",
"Caching",
DiagnosticSeverity.Warning,
true);

public static readonly DiagnosticDescriptor PdfNotFound = new DiagnosticDescriptor(
"HUT1003",
"PDF Not found",
"Failed to find the PDF file {0}: {1}",
"Caching",
DiagnosticSeverity.Error,
true);

public static readonly DiagnosticDescriptor JsonDeserializationFailed = new DiagnosticDescriptor(
"HUT2001",
"JSON Deserialization Failure",
"Deserialization of the JSON HID USage Tables failed: {0}",
"Deserialization",
DiagnosticSeverity.Error,
true);

public static readonly DiagnosticDescriptor JsonAttachmentNotFound = new DiagnosticDescriptor(
"HUT2002",
"JSON attachment not found in PDF file",
"Could not find the JSON attachment in '{0}'",
"Deserialization",
DiagnosticSeverity.Error,
true);

public static readonly DiagnosticDescriptor JsonExtractionFailed = new DiagnosticDescriptor(
"HUT2003",
"Error extracting JSON attachment from PDF file",
"Extracting the JSON attachment from '{0}' failed: {1}",
"Deserialization",
DiagnosticSeverity.Error,
true);
// ReSharper restore ArrangeObjectCreationWhenTypeEvident
#pragma warning restore IDE0090 // Use 'new(...)'

/// <summary>
/// Adds a <see cref="Diagnostic" /> to the users compilation based on a <see cref="DiagnosticDescriptor" />.
/// </summary>
/// <param name="context">The execution context.</param>
/// <param name="descriptor">The diagnostic descriptor.</param>
/// <param name="location"></param>
/// <param name="messageArgs"></param>
/// <remarks>
/// The severity of the diagnostic may cause the compilation to fail, depending on the <see cref="Compilation" />
/// settings.
/// </remarks>
/// <seealso cref="GeneratorExecutionContext.ReportDiagnostic(Diagnostic)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Report(this GeneratorExecutionContext context, DiagnosticDescriptor descriptor,
Location location, params object[] messageArgs)
=> context.ReportDiagnostic(Diagnostic.Create(descriptor, location, messageArgs));

/// <summary>
/// Get's a C#-safe version of a name.
/// </summary>
/// <param name="unsafe"></param>
/// <returns></returns>
public static string GetSafe(this string @unsafe)
{
// Create safe name
var builder = new StringBuilder(@unsafe.Length);
var afterSpace = true;
foreach (var ch in @unsafe)
{
switch (char.GetUnicodeCategory(ch))
{
case UnicodeCategory.UppercaseLetter:
case UnicodeCategory.LowercaseLetter:
case UnicodeCategory.TitlecaseLetter:
case UnicodeCategory.ModifierLetter:
case UnicodeCategory.OtherLetter:
// Always allowed in C# class names
break;

case UnicodeCategory.ConnectorPunctuation:
// Language specification allows '_' as first character.
if (builder.Length < 1 && ch != '_')
{
continue;
}

break;

case UnicodeCategory.LetterNumber:
case UnicodeCategory.NonSpacingMark:
case UnicodeCategory.SpacingCombiningMark:
case UnicodeCategory.DecimalDigitNumber:
case UnicodeCategory.Format:
// Only valid after first character, so add a '_' prefix.
if (builder.Length < 1)
{
builder.Append('_');
}

break;

case UnicodeCategory.SpaceSeparator:
afterSpace = true;
continue;
default:
// Skip characters
continue;
}

char c;
if (afterSpace)
{
afterSpace = false;
c = char.ToUpperInvariant(ch);
}
else
{
c = ch;
}

builder.Append(c);
}

return builder.ToString();
}

public static IndentStringBuilder AppendComment(this IndentStringBuilder builder, string comment,
bool cStyle = false)
{
if (cStyle)
{
builder.AppendLine("/*").Indent(" *").AppendLine(comment).Outdent().AppendLine(" */");
}
else
{
builder.Indent("// ").AppendLine(comment).Outdent();
}

return builder;
}

public static IndentStringBuilder AppendSummary(this IndentStringBuilder builder, string comment)
=> builder.Indent("// ").AppendLine("<summary>").Indent().AppendLine(comment).Outdent().AppendLine("</summary>")
.Outdent();

public static IndentStringBuilder AppendDescription(this IndentStringBuilder builder, string description)
=> builder.Append("[Description(").AppendQuoted(description).AppendLine(")]");

public static IndentStringBuilder OpenBrace(this IndentStringBuilder builder)
=> builder.AppendLine("{").Indent();

public static IndentStringBuilder CloseBrace(this IndentStringBuilder builder)
=> builder.Outdent().AppendLine("}");
}
38 changes: 38 additions & 0 deletions HIDDevices.Generator/HIDDevices.Generator.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<IsRoslynComponent>true</IsRoslynComponent>
<Configurations>Debug;Release;GenerateFromCache;GenerateFromSource</Configurations>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="iTextSharp" Version="5.5.13.3" GeneratePathProperty="true" PrivateAssets="all" >
<NoWarn>NU1701</NoWarn>
</PackageReference>
<PackageReference Include="BouncyCastle" Version="1.8.9" GeneratePathProperty="true" PrivateAssets="all" >
<NoWarn>NU1701</NoWarn>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>


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

<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGiTextSharp)\lib\iTextSharp.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGBouncyCastle)\lib\BouncyCastle.Crypto.dll" IncludeRuntimeDependency="false" />
</ItemGroup>
</Target>
</Project>
67 changes: 67 additions & 0 deletions HIDDevices.Generator/HidUsageGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Licensed under the Apache License, Version 2.0 (the "License").
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using Newtonsoft.Json;

namespace HIDDevices.Generator;

/// <summary>
/// Generates HidUsages on-the-fly (according to a pattern) rather than at compile-time.
/// This is used primarily by the Button/Ordinal pages, where there are 65535 UsageIds (all 'pre-defined').
/// It would be silly (and wasteful) to pre-generate all of these, so rather it is done on demand.
/// </summary>
[JsonObject(MemberSerialization.OptIn)]
public class HidUsageGenerator
{
/// <summary>
/// Initializes a new instance of the <see cref="HidUsageGenerator" /> class.
/// </summary>
/// <param name="namePrefix">Name of every generated Usage.</param>
/// <param name="startUsageId">First valid UsageId.</param>
/// <param name="endUsageId">Last valid UsageId.</param>
/// <param name="kinds">Kinds to associate with generated UsageIds.</param>
public HidUsageGenerator(string namePrefix, ushort startUsageId, ushort endUsageId,
IReadOnlyCollection<HidUsageKind> kinds)
{
NamePrefix = namePrefix;
SafeNamePrefix = namePrefix.GetSafe();
StartUsageId = startUsageId;
EndUsageId = endUsageId;
Kinds = kinds;
}

/// <summary>
/// Gets the Name all generated Usages shall have.
/// </summary>
[JsonProperty]
public string NamePrefix { get; }

/// <summary>
/// Gets the Safe Name all generated Usages shall have.
/// </summary>
public string SafeNamePrefix { get; }

/// <summary>
/// Gets the first valid UsageId for this generator. All IDs between <see cref="StartUsageId" /> and
/// <see cref="EndUsageId" /> (inclusive) are valid.
/// </summary>
[JsonProperty]
public ushort StartUsageId { get; }

/// <summary>
/// Gets the ast valid UsageId for this generator. All IDs between <see cref="StartUsageId" /> and
/// <see cref="EndUsageId" /> (inclusive) are valid.
/// </summary>
[JsonProperty]
public ushort EndUsageId { get; }

/// <summary>
/// Gets the Usage kinds as defined the HID Usage Table. Most UsageIds will only have a single kind.
/// </summary>
[JsonProperty]
public IReadOnlyCollection<HidUsageKind> Kinds { get; }

/// <inheritdoc />
public override string ToString() => $"[{StartUsageId}-{EndUsageId}]";
}
Loading

0 comments on commit f565ecc

Please sign in to comment.