SmolFennec.SpeCLI.Proxy
1.2.1
See the version list below for details.
dotnet add package SmolFennec.SpeCLI.Proxy --version 1.2.1
NuGet\Install-Package SmolFennec.SpeCLI.Proxy -Version 1.2.1
<PackageReference Include="SmolFennec.SpeCLI.Proxy" Version="1.2.1" />
paket add SmolFennec.SpeCLI.Proxy --version 1.2.1
#r "nuget: SmolFennec.SpeCLI.Proxy, 1.2.1"
// Install SmolFennec.SpeCLI.Proxy as a Cake Addin #addin nuget:?package=SmolFennec.SpeCLI.Proxy&version=1.2.1 // Install SmolFennec.SpeCLI.Proxy as a Cake Tool #tool nuget:?package=SmolFennec.SpeCLI.Proxy&version=1.2.1
SmolFennec.SpeCLI
SmolFennec is a collective name for small libraries and tools released by Levi licensed under MIT and hosted on github. SpeCLI provides a way to describe CLI behaviour of external processes.
<img src="https://raw.githubusercontent.com/Levi--G/SmolFennec.SpeCLI/master/SpeCLI/SmolFennec.png" width="300" height="300">
Support
Supported platforms: .Net Standard 2.0+
When in trouble: Submit an issue
Usage
EZ mode:
(The ping command was used below for short instructional purposes, ofcourse it is not meant to be used like this, use the ping class instead!)
Create a class for input and a class for output. Usable attributes:
- Parameter: Defines a parameter with name and value, like:
-n 50
- Switch: Defines a switch with name only, like:
-6
- HideName: Defines a parameter with value only, like:
Hostname
- When nothing is supplied a parameter with the property name and a value is created
class PingArguments
{
[Switch("t")]
public bool Continuous { get; set; }
[Parameter("n")]
public int? Count { get; set; }
[Parameter("i")]
public int? TTL { get; set; }
[Parameter("w")]
public int? Timeout { get; set; }
[Switch("4")]
public bool IPV4 { get; set; }
[Switch("6")]
public bool IPV6 { get; set; }
[HideName]
public string Host { get; set; }
}
class PingResult
{
public string ip { get; set; }
public string fail { get; set; }
public bool Success => !string.IsNullOrEmpty(ip) && string.IsNullOrEmpty(fail);
public int bytes { get; set; }
public TimeSpan time { get; set; }
public int ttl { get; set; }
}
Create an Executable with Commands Usable OutputProcessors at this time:
- JsonOutputProcessor: Parses the outputted lines as json
- RegexCaptureOutputProcessor: Populates an object with matched regex groups
- RegexSplitOutputProcessor: Splits the lines matching regexes over different OutputProcessors
- CloneOutputProcessor: Clones the output and sends it to other OutputProcessors
- Interface to write your own!
For the individual configuration please look at the configuration properties and methods.
// The regex we will use later
const string regex = @"Reply from (?<ip>[^:]+): (?:bytes=(?<bytes>\d+) time[^\d]*(?<time>[^ ]+) TTL=(?<ttl>\d+)|(?<fail>.+))";
// Create an executable to hold commands to be reused
var exe = new Executable("ping");
// Add a new command
exe.Add("ping")
// Add all parameters from this type
.AddParametersFromType(typeof(PingArguments))
.WithProcessor(
// Use this processor to handle regex output
new RegexCaptureOutputProcessor()
// Map this regex to the output type we made, this can be done multiple times
.AddRegex<PingResult>(new Regex(regex))
// Add a manual mapping for the non-standard timespan conversion
.AddPropertyMapping(s => TimeSpan.FromMilliseconds(int.Parse(s.TrimEnd('m', 's'))))
);
Use the constructed Executable:
List<PingResult> matches = exe.ExecuteCommandAndParseList<PingResult>("ping", new PingArguments() {
Count = 8,
Host = "Hostname",
Timeout = 50
});
Full sample can be found in the source.
Manual/Advanced mode:
IAsyncEnumerable
s can be obtained like this:
var iae = exe.ExecuteCommandAndParseIAsyncEnumerable<PingResult>("ping", new PingArguments() { Host = "Hostname" });
Parameters can also be declared manually. When invoking a nameless type, a Dictionary<string, object>
or another type with similar properties can also be used. When you specify a List<object>
any objects generated from whatever type will be returned
exe.Add("ping")
.AddParameter<int?>("n")
.AddParameter(new Parameter<string>("host").WithHideName());
.AddParameter(new Switch("6"))
var objs = exe.ExecuteCommandAndParseList<object>("ping", new { Host = "Hostname" });
An Execution object can also be obtained, altho short-lived (pun intended) these can allow more low-level access to the process, events and Stdin.
var exec = exe.CreateExecution("ping", new PingArguments() { Host = "Hostname" });
exec.OutputDataReceived += (s, e) => { }; // raw stdout
exec.ErrorDataReceived += (s, e) => { }; // raw stderr
exec.OnOutput += (s, e) => { }; // Parsed objects
exec.OnError += (s, e) => { }; // Exceptions during parsing
exec.Start(); // Needs to be started manually;
exec.SendInputLine("Hello World!"); // Can send stdin
SmolFennec.SpeCLI.Proxy
Usage
Define Arguments and results exactly like normal SpeCLI configuration. Create a Executable class:
- Enter path to executable or the command in the Executable attribute
- Inherit from IExecutableConfigurator or IExecutionConfigurator to add an OnConfiguring method to apply further configuration during proxy or Execution generation (optional)
- Add any OutputProcessors or additional configuration in the OnConfiguring method
[Executable("ping")]
public abstract class Ping : IExecutableConfigurator
{
public void OnConfiguring(Executable executable)
{
var processor = new RegexCaptureOutputProcessor()
.AddRegex<PingResult>(new Regex(@"Reply from (?<ip>[^:]+): (?:bytes=(?<bytes>\d+) time[^\d]*(?<time>[^ ]+) TTL=(?<ttl>\d+)|(?<fail>.+))"))
.AddPropertyMapping((string s) => !string.IsNullOrEmpty(s) ? TimeSpan.FromMilliseconds(int.Parse(s.TrimEnd('m', 's'))) : (TimeSpan?)null);
foreach (var command in executable.Commands)
{
command.Processor = processor;
}
}
}
Configure commands in these steps:
- Add Command attribute to a public abstract method with or without custom name
- Add a supported return type: any object supported by selected OutputProcessors or a List<> or IAsyncEnumerable<> of that object, or an Execution object
- Add arguments for your parameters:
- a single defined object containing parameter attributes (like ping/PingArguments)
- a set of parameters with their attributes (like ping2)
- an
object
orDictionary<string, object>
⇒ need to add parameter attributes to the method (like ping3/4)
- Adding parameters can be done on the method and defaults can be assigned without adding them as parameter if you want to make a specific method for a certain CLI option (like SinglePing)
Examples:
[Command("ping1")]
public abstract List<PingResult> ping(PingArguments arguments);
[Command]
public abstract List<PingResult> ping2([Switch("t")] bool Continuous, [Parameter("n")] int? Count, [Parameter("w")] int? Timeout, [HideName] string Host);
[Command]
[Parameter("n")]
[Parameter("Host", HideName = true)]
[Parameter("w")]
public abstract List<PingResult> ping3(object arguments);
[Command]
[Parameter("n")]
[Parameter("Host", HideName = true)]
[Parameter("w")]
public abstract List<PingResult> ping4(Dictionary<string, object> arguments);
[Command]
[Parameter("n", typeof(int?), 1)]
public abstract PingResult SinglePing([HideName] string Host, [Parameter("w")] int? Timeout = null);
[Command]
[Parameter("n", typeof(int?), 1)]
public abstract Execution SinglePingExecution([HideName] string Host, [Parameter("w")] int? Timeout = null);
In your code make and use the proxy like this:
var ping = SpeCLIProxy.Create<Ping>();
var matches = ping.ping(new PingArguments() { Count = pings, Host = "127.0.0.1", Timeout = 50 });
This will run the CLI and parse the output all automatically, providing a strongly typed interface to a CLI you only need to configure once.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 is compatible. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Castle.Core (>= 5.1.0)
- SmolFennec.SpeCLI (>= 1.2.1)
-
.NETStandard 2.1
- Castle.Core (>= 5.1.0)
- SmolFennec.SpeCLI (>= 1.2.1)
-
net5.0
- Castle.Core (>= 5.1.0)
- SmolFennec.SpeCLI (>= 1.2.1)
-
net6.0
- Castle.Core (>= 5.1.0)
- SmolFennec.SpeCLI (>= 1.2.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Bugfix Release