Skip to content
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
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 5 additions & 5 deletions docs/articles/generate.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ 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

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.
Expand All @@ -55,7 +55,7 @@ The following options are available to configure the generated output.
-ns, --namespace <ns>
```

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

Expand All @@ -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

Expand Down
26 changes: 20 additions & 6 deletions src/Harp.Toolkit/Generate/GenerateCSharpInterfaceCommand.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
using System.CommandLine;
using Harp.Generators;

namespace Harp.Toolkit.Generate;

class GenerateCSharpInterfaceCommand : Command
{
public GenerateCSharpInterfaceCommand(
Argument<FileInfo> metadataPathArgument,
Option<string> namespaceOption,
Option<DirectoryInfo> 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<string> 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);
});
}
}
37 changes: 3 additions & 34 deletions src/Harp.Toolkit/Generate/GenerateInterfaceCommand.cs
Original file line number Diff line number Diff line change
@@ -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<string> 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());
}
}
16 changes: 6 additions & 10 deletions src/Harp.Toolkit/Generate/GeneratePythonInterfaceCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,14 @@ namespace Harp.Toolkit.Generate;

class GeneratePythonInterfaceCommand : Command
{
public GeneratePythonInterfaceCommand(
Argument<FileInfo> metadataPathArgument,
Option<string> namespaceOption,
Option<DirectoryInfo> 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 =>
{
Expand Down
2 changes: 1 addition & 1 deletion src/Harp.Toolkit/Generate/MetadataPathArgument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Harp.Toolkit.Generate;
public class MetadataPathArgument : Argument<FileInfo>
{
public MetadataPathArgument()
: base("metadataPath")
: base("metadata")
{
ArgumentValidation.AcceptExistingOnly(this);
Description = "The path to the file describing the device registers.";
Expand Down
Loading