From 188526b22a1bdc4ff1536368ebdf9be1bbe66603 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Thu, 17 Sep 2026 10:13:49 +0100 Subject: [PATCH] Require a target language for interface generation The metadata argument and output option are now declared on the csharp and python commands rather than on their parent, so the target language is named before the metadata path. Naming the language is now required, so generate interface reports that a command was not provided instead of generating the .NET interface. The python command no longer declares a --namespace option, so help states the options that apply to each target and the validator rejecting the option is gone. The metadata argument is renamed from metadataPath to metadata. --- docs/README.md | 2 +- docs/articles/generate.md | 10 ++--- .../GenerateCSharpInterfaceCommand.cs | 26 ++++++++++--- .../Generate/GenerateInterfaceCommand.cs | 37 ++----------------- .../GeneratePythonInterfaceCommand.cs | 16 +++----- .../Generate/MetadataPathArgument.cs | 2 +- 6 files changed, 36 insertions(+), 57 deletions(-) diff --git a/docs/README.md b/docs/README.md index 0d7a97a..b79feaf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -57,7 +57,7 @@ See [Firmware Update](https://harp-tech.org/toolkit/articles/update.html) for th `harp.toolkit` can also generate device interface and firmware code from a `device.yml` metadata file. With a `device.yml` in the current directory, the following generates the .NET device interface, targeting [Bonsai.Harp](https://harp-tech.org/api/Bonsai.Harp.html): ```text -dotnet harp.toolkit generate interface +dotnet harp.toolkit generate interface csharp ``` To generate the [Harp Python](https://harp-tech.org/python) interface instead: diff --git a/docs/articles/generate.md b/docs/articles/generate.md index 01488ce..412a3d3 100644 --- a/docs/articles/generate.md +++ b/docs/articles/generate.md @@ -30,10 +30,10 @@ A device interface can be generated from the `device.yml` metadata file, in eith A register or payload member may also declare a `converter`, for an `interfaceType` the generator cannot synthesize from the metadata alone. The implementation is then written by hand and referenced by the generated code. -The .NET interface is the default target. The language can also be named explicitly, and a metadata path given on the command line precedes it. +The target language is always named, and an optional metadata path follows it. Without a path the generator reads `device.yml` from the current directory. ```text -dotnet harp.toolkit generate interface path/to/device.yml csharp +dotnet harp.toolkit generate interface csharp path/to/device.yml ``` ### .NET interface @@ -41,7 +41,7 @@ dotnet harp.toolkit generate interface path/to/device.yml csharp An interface for reactive programming targeting [Bonsai.Harp](https://harp-tech.org/api/Bonsai.Harp.html). ```text -dotnet harp.toolkit generate interface +dotnet harp.toolkit generate interface csharp ``` Registers are additionally exposed as [operators](https://harp-tech.org/articles/operators.html), alongside an asynchronous API for use from .NET applications. @@ -55,7 +55,7 @@ The following options are available to configure the generated output. -ns, --namespace ``` -Specifies the namespace for the generated code. The default namespace is `Harp.DeviceName` where `DeviceName` is the name of the device specified in the `device.yml` file. This option applies only to the .NET interface. +Specifies the namespace for the generated code. The default namespace is `Harp.DeviceName` where `DeviceName` is the name of the device specified in the `device.yml` file. ### Python interface @@ -67,7 +67,7 @@ dotnet harp.toolkit generate interface python Custom converters are supplied in a companion `converters` module, which the generated module imports from. -The `--namespace` option does not apply to this target, since the generated module declares no namespace, and is rejected if supplied. +The `--namespace` option is not available for this target, since the generated module declares no namespace. ## Generating device firmware code diff --git a/src/Harp.Toolkit/Generate/GenerateCSharpInterfaceCommand.cs b/src/Harp.Toolkit/Generate/GenerateCSharpInterfaceCommand.cs index 3c3fe81..67d035b 100644 --- a/src/Harp.Toolkit/Generate/GenerateCSharpInterfaceCommand.cs +++ b/src/Harp.Toolkit/Generate/GenerateCSharpInterfaceCommand.cs @@ -1,21 +1,35 @@ using System.CommandLine; +using Harp.Generators; namespace Harp.Toolkit.Generate; class GenerateCSharpInterfaceCommand : Command { - public GenerateCSharpInterfaceCommand( - Argument metadataPathArgument, - Option namespaceOption, - Option outputPathOption) - : base("csharp", "Generate reactive programming API and async API. This is the default.") + public GenerateCSharpInterfaceCommand() + : base("csharp", "Generate reactive programming API and async API.") { + MetadataPathArgument metadataPathArgument = new(); + Option namespaceOption = new("-ns", "--namespace") + { + Description = "The namespace for the generated code. The default is `Harp.DeviceName`." + }; + OutputPathOption outputPathOption = new(); + + Arguments.Add(metadataPathArgument); + Options.Add(namespaceOption); + Options.Add(outputPathOption); + SetAction(parseResult => { var outputPath = parseResult.GetRequiredValue(outputPathOption); var metadataPath = parseResult.GetRequiredValue(metadataPathArgument); var ns = parseResult.GetValue(namespaceOption); - GenerateInterfaceCommand.GenerateCSharpInterface(metadataPath, ns, outputPath); + + var deviceMetadata = GeneratorHelper.ReadDeviceMetadata(metadataPath.FullName); + var generator = new InterfaceGenerator(deviceMetadata, ns ?? $"Harp.{deviceMetadata.Device}"); + var implementation = generator.GenerateImplementation(); + if (GeneratorHelper.AssertNoGeneratorErrors(generator.Errors)) + GenerateCommand.WriteFileContents(outputPath.FullName, implementation); }); } } diff --git a/src/Harp.Toolkit/Generate/GenerateInterfaceCommand.cs b/src/Harp.Toolkit/Generate/GenerateInterfaceCommand.cs index 427cbfa..60266dd 100644 --- a/src/Harp.Toolkit/Generate/GenerateInterfaceCommand.cs +++ b/src/Harp.Toolkit/Generate/GenerateInterfaceCommand.cs @@ -1,44 +1,13 @@ using System.CommandLine; -using Harp.Generators; namespace Harp.Toolkit.Generate; class GenerateInterfaceCommand : Command { public GenerateInterfaceCommand() - : base("interface", "Generate reactive programming API and async API.") + : base("interface", "Generate device interface code for a target language.") { - MetadataPathArgument metadataPathArgument = new(); - Option namespaceOption = new("-ns", "--namespace") - { - Description = "The namespace for the generated code. The default is `Harp.DeviceName`.", - Recursive = true - }; - OutputPathOption outputPathOption = new() { Recursive = true }; - - Arguments.Add(metadataPathArgument); - Options.Add(namespaceOption); - Options.Add(outputPathOption); - Subcommands.Add(new GenerateCSharpInterfaceCommand( - metadataPathArgument, namespaceOption, outputPathOption)); - Subcommands.Add(new GeneratePythonInterfaceCommand( - metadataPathArgument, namespaceOption, outputPathOption)); - - SetAction(parseResult => - { - var outputPath = parseResult.GetRequiredValue(outputPathOption); - var metadataPath = parseResult.GetRequiredValue(metadataPathArgument); - var ns = parseResult.GetValue(namespaceOption); - GenerateCSharpInterface(metadataPath, ns, outputPath); - }); - } - - internal static void GenerateCSharpInterface(FileInfo metadataPath, string? ns, DirectoryInfo outputPath) - { - var deviceMetadata = GeneratorHelper.ReadDeviceMetadata(metadataPath.FullName); - var generator = new InterfaceGenerator(deviceMetadata, ns ?? $"Harp.{deviceMetadata.Device}"); - var implementation = generator.GenerateImplementation(); - if (GeneratorHelper.AssertNoGeneratorErrors(generator.Errors)) - GenerateCommand.WriteFileContents(outputPath.FullName, implementation); + Subcommands.Add(new GenerateCSharpInterfaceCommand()); + Subcommands.Add(new GeneratePythonInterfaceCommand()); } } diff --git a/src/Harp.Toolkit/Generate/GeneratePythonInterfaceCommand.cs b/src/Harp.Toolkit/Generate/GeneratePythonInterfaceCommand.cs index 6c9924c..d556b2b 100644 --- a/src/Harp.Toolkit/Generate/GeneratePythonInterfaceCommand.cs +++ b/src/Harp.Toolkit/Generate/GeneratePythonInterfaceCommand.cs @@ -5,18 +5,14 @@ namespace Harp.Toolkit.Generate; class GeneratePythonInterfaceCommand : Command { - public GeneratePythonInterfaceCommand( - Argument metadataPathArgument, - Option namespaceOption, - Option outputPathOption) + public GeneratePythonInterfaceCommand() : base("python", "Generate the Harp Python device interface.") { - Validators.Add(commandResult => - { - var namespaceResult = commandResult.GetResult(namespaceOption); - if (namespaceResult is not null && !namespaceResult.Implicit) - commandResult.AddError("The --namespace option does not apply to the Python interface."); - }); + MetadataPathArgument metadataPathArgument = new(); + OutputPathOption outputPathOption = new(); + + Arguments.Add(metadataPathArgument); + Options.Add(outputPathOption); SetAction(parseResult => { diff --git a/src/Harp.Toolkit/Generate/MetadataPathArgument.cs b/src/Harp.Toolkit/Generate/MetadataPathArgument.cs index b8a3acd..4738850 100644 --- a/src/Harp.Toolkit/Generate/MetadataPathArgument.cs +++ b/src/Harp.Toolkit/Generate/MetadataPathArgument.cs @@ -5,7 +5,7 @@ namespace Harp.Toolkit.Generate; public class MetadataPathArgument : Argument { public MetadataPathArgument() - : base("metadataPath") + : base("metadata") { ArgumentValidation.AcceptExistingOnly(this); Description = "The path to the file describing the device registers.";