magic.lambda.system 17.2.0

dotnet add package magic.lambda.system --version 17.2.0
NuGet\Install-Package magic.lambda.system -Version 17.2.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="magic.lambda.system" Version="17.2.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add magic.lambda.system --version 17.2.0
#r "nuget: magic.lambda.system, 17.2.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install magic.lambda.system as a Cake Addin
#addin nuget:?package=magic.lambda.system&version=17.2.0

// Install magic.lambda.system as a Cake Tool
#tool nuget:?package=magic.lambda.system&version=17.2.0

This project contains "system slots" to be able to invoke system commands, and/or dynamically compile and load plugins, etc. More specifically the project contains the following slots.

  • [system.compile] - Compiles a piece of C# code and returns the result as raw bytes
  • [system.plugin.load] - Loads the specified assembly and initialises any slots found in it
  • [system.plugin.unload] - Unloads a previously loaded plugin assembly and unregister any slots
  • [system.plugin.execute] - Short hand combining load with unload and a lambda object
  • [system.plugin.list] - Lists all dynamically loaded plugins
  • [system.terminal.create] - Creates a new terminal process on the server
  • [system.terminal.write-line] - Writes a line/command to a previously created terminal process on the server
  • [system.terminal.destroy] - Destroys/kills a previously created terminal process on the server
  • [system.execute] - Execute the specified command returning the result to caller

The most important feature of this project is probably that it gives you the ability to dynamically compile C# code, persist to an assembly/dll, and dynamically load this assembly as if it was a "plugin".

How to use [system.compile]

This method allows you to compile a piece of C# code and returns the assembly as a raw array of bytes. Example usage can be found below.

/*
 * Compiles a piece of C# and returns the compiled CLR assembky code as raw bytes.
 */
system.compile
   assembly-name:foo.dll
   references
      .:netstandard
      .:System.Runtime
      .:System.Private.CoreLib
      .:magic.node
      .:magic.signals.contracts
   code:@"

// Example C# code creating a Hyperlambda slot.
using System;
using magic.node;
using magic.signals.contracts;

// Our slot class.
[Slot(Name = ""foo"")]
public class Foo : ISlot
{
   public void Signal(ISignaler signaler, Node input)
   {
      input.Value = ""Foo was here!"";
   }
}"

The slot takes three arguments being as follows;

  • [assembly-name] - The name of your assembly
  • [references] - Being what references your code requires to compile. Notice, these are taken from the current AppDomain's assemblies, and assumes you've already got the assembly somehow loaded into your AppDomain. If you need to add assemblies that doesn't exist in your AppDomain you can use [system.plugin.load] before you compile your code.
  • [code] - Your actual C# code

If you want to persist the created assembly to disc you can use the [io.file.save.binary] slot from the "magic.lambda.io" project.

Dependency Injection and your IoC container

Because the IoC DI container is built as your assembly starts, you cannot rely upon the DI container in your code the way you can in normal statically compiled code. This implies you'll have to use the "service locator" pattern to resolve services in your C# code. The service locator pattern is considered an "anti pattern", but there aren't really many options here.

Below is an example of some C# code dynamically instantiating your IMagicConfiguration service while creating a slot that returns a configuration value to the caller.


// Example C# code creating a Hyperlambda slot.
using System;

using magic.node;
using magic.library;
using magic.signals.contracts;
using magic.node.contracts;

// Our slot class.
[Slot(Name = "foo")]
public class Foo : ISlot
{
    readonly IServiceProvider _services;

    public Foo(IServiceProvider services)
    {
        _services = services;
    }

    public void Signal(ISignaler signaler, Node input)
    {
        var configuration = (IMagicConfiguration)_services.GetService(typeof(IMagicConfiguration));
        input.Value = configuration["magic:smtp:host"];
    }
}

If you save the above code to "/etc/foo.cs" you can compile it and save it using the following code.

/*
 * The following code compiles the "/etc/foo.cs" file and saves it as an assembly
 * in "/etc/foo.dll".
 */
io.file.load:/etc/foo.cs
system.compile
   references
      .:netstandard
      .:System.Runtime
      .:System.Console
      .:System.Private.Uri
      .:System.ComponentModel
      .:System.Private.CoreLib
      .:System.Net.Primitives
      .:magic.node
      .:magic.node.extensions
      .:magic.library
      .:magic.signals.contracts
   code:x:@io.file.load
   assembly-name:foo.dll

// Saving our compiled CLR assembly to '/etc/foo.dll'.
io.file.save.binary:/etc/foo.dll
   get-value:x:@system.compile

How to use [system.plugin.load] and [system.plugin.unload]

These slots allows you to load and unload assemblies and instantiate any slots found in it, and such dynamically during runtime changes your available slots. If you imagine you saved the above resulting compiled assembly as "/etc/foo.dll", and you want to load it as a plugin, you could use code resembling the following.

system.plugin.load:/etc/foo.dll

Once the above code has executed you will have a native C# Hyperlambda slot called [foo] that you can use. Plugins loaded this way will stay in memory as a part of your AppDomain until either the process restarts, and/or you explicitly unload the plugin - Implying if your process restarts you'll need to ensure the plugin is reloaded again if you wish to continue using it. Dynamically loaded plugins will not be automatically reloaded in any ways.

To unload the plugin after having used the assembly and it is no longer required, you should use [system.plugin.unload] to unload the assembly. If you recompile some C# code using the same assembly name this will be automatically done for you by consecutive invocations to [system.plugin.load] as long as the [assembly-name] argument is the same (case sensitive).

If you unload an assembly previously loaded it will be collected by the garbage collector automatically as long as you don't have references to it from another native assembly.

You can also load an assembly as raw bytes, such as the following example illustrates.

/*
 * Compiles a snippet of C# code down to a library,
 * for then to dynamically load it as a plugin.
 */
system.compile
   references
      .:netstandard
      .:System.Runtime
      .:System.Private.CoreLib
      .:magic.node
      .:magic.node.extensions
      .:magic.signals.contracts
   code:@"

// Example C# code creating a Hyperlambda slot.
using System;
using magic.node;
using magic.node.extensions;
using magic.signals.contracts;

// Our slot class.
[Slot(Name = ""foo"")]
public class Foo : ISlot
{
   public void Signal(ISignaler signaler, Node input)
   {
      input.Value = $""Hi {input.GetEx<string>()}, how may I assist you?"";
   }
}"
   assembly-name:foo.dll

// Loading assembly from raw bytes now that we've created it.
system.plugin.load:x:@system.compile

// Executing [foo] slot now dynamically injected into AppDomain.
.name:John Doe
foo:x:@.name

// Unloading plugin.
system.plugin.unload:foo.dll

Notice that in the last example above, we never actually save the assembly, but load it as a plugin from the raw byte[] returned from the [system.compile] invocation allowing you to dynamically compile and execute C# code without ever having to actually save the compiled result as a dll on disc. You still have to unload the assembly from your AppDomain using [system.plugin.unload].

Also notice that the name of the assembly is not necessary the path of the assembly, but determined from [assembly-name], allowing you to at least in theory have an assembly with "/foo.dll" as its path, but "bar.dll" being its name - Implying to unload the assembly you'll need to unload it as "bar.dll", and listing it will return "bar.dll" - While the physical location on disc of your assembly is "/foo.dll".

Implying the assembly's name and its filename might differ.

[system.plugin.execute]

This is a short hand slot for dynamically loading a plugin and executing a lambda object with the plugin loaded, for then to automatically unload the plugin at the end of the scope. Below is an example of usage that dynamically compiles some C# code that creates a slot, for then to executing a lambda object having access to this slot, and finish the execution with automatically cleaning up and unloading the dynamically added plugin.

/*
 * Compiles a snippet of C# code down to a library,
 * for then to dynamically load it as a plugin.
 *
 * This particular code creates a slot, but you can create any
 * code you wish, and it doesn't have to be Hyperlambda related.
 */
system.compile
   references
      .:netstandard
      .:System.Runtime
      .:System.Private.CoreLib
      .:magic.node
      .:magic.signals.contracts
   code:@"

// Example C# code creating a Hyperlambda slot.
using System;
using magic.node;
using magic.signals.contracts;

// Our slot class.
[Slot(Name = ""bar"")]
public class Bar : ISlot
{
   public void Signal(ISignaler signaler, Node input)
   {
      input.Value = ""Hello from C#"";
   }
}"
   assembly-name:bar.dll

// Loading assembly now that we've created it.
system.plugin.execute:x:@system.compile

   // Invoking dynamically created C# slot.
   bar

/*
 * Now the [bar] slot is no longer available
 * since the assembly has been unloaded due to
 * having gone out of scope.
 */
system.plugin.list

This slot is probably the closest you come to being able to using C# as a dynamic "scripting language", allowing you to use it more like an interpreted language than a compiled language - Even though internally it's actually compiled to IL code and executed as such - Only the loading of the assembly resulting from the compilation process is 100% dynamic and automatic, including the cleaning up and unloading of the assembly from the AppDomain once the code has been executed.

This slot accepts either a string or a byte array. If you give it a string, it assumes your string is a relative path to an assembly on disc. If you provide it with a byte array, it assumes it's your raw assembly content. In such a regard it works similarly to [system.plugin.load].

[system.plugin.list]

This slot returns a list of all dynamically loaded plugins allowing you to traverse these as you see fit. Below is an example of usage.

system.plugin.list

Terminal system commands

There are also terminal slots in this project that allows you to not only execute terminal commands, but also in fact create your own terminal session, through where you can dynamically submit commands that are executed in a terminal on your server.

By combining these slots with for instance the "magic.lambda.sockets" project, you can spawn off terminal/bash processes on your server, creating "virtual web based terminal sessions" on your server. To create a new terminal process, use something such as the following.

system.terminal.create:my-terminal
   folder:/

   /*
    * STDOUT callback, invoked when something is channeled over STDOUT.
    */
   .stdOut

      /*
       * Do standard out stuff here, with the incoming [.arguments]/[cmd] command.
       */
      log.info:x:@.arguments/*/cmd


   /*
    * STDERROR callback, invoked when something is channeled over STDOUT.
    */
   .stdErr

      /*
       * Do standard out stuff here, with the incoming [.arguments]/[cmd] command.
       */
      log.info:x:@.arguments/*/cmd

The above [.stdOut] and [.stdErr] lambda objects are invoked when output data or error data is received from the process, allowing you to handle it any ways you see fit. The [folder] argument is the default/initial folder to spawn of the process in. All of these arguments are optional.

The name or the value of the [system.terminal.create] invocation however is important. This becomes a unique reference for you, which you can later use to de-reference the instance, while for instance feeding the terminal lines of input, using for instance the [system.terminal.write-line] slot. To write a command line to an existing terminal window, such as the one created above, you can use something such as the following.

system.terminal.write-line:my-terminal
   cmd:ls -l

The above will execute the ls -l command in your previously create "my-terminal" instance, and invoke your [.stdOut] callback once for each line of output the command results in. To destroy the above created terminal, you can use something such as the following.

system.terminal.destroy:my-terminal

All terminal slots requires a name to be able to uniquely identify which instance you wish to create, write to, or destroy. This allows you to create as many terminals as you wish on your server, only restricted by memory on your system, and/or your operating system of choice. The terminal slots works transparently for both Windows, Linux and Mac OS X, except of course the commands you pass into them will differ depending upon your operating system.

Notice - If you don't reference a terminal session for more than 30 minutes, the process will be automatically killed and disposed, and any future attempts to reference it, will resolve in an error. This is to avoid having hanging processes on the server, in case a terminal process is started, and then something happens, which disconnects the client, resulting in "hanging sessions".

Notice, by default Magic's Docker image will run in a restriced user called "magic", which implies you do not have root access to the underlying operating system unless you modify this somehow yourself, which is not recommended.

How to use [system.execute]

If you only want to execute a specific program in your system you can use [system.execute], and pass in the name of the command as a value, and any arguments as children, optionally applying a [structured] argument to signifiy if you want each line of input to be returned as a single node or not. Below is an example.

system.execute:ls
   structured:true
   .:-l

The above will result in something such as follows.

system.execute
   .:total 64
   .:"-rw-r--r--  1 thomashansen  staff   495  9 Nov 10:37 Dockerfile"
   .:"-rw-r--r--  1 thomashansen  staff  1084 29 Oct 14:51 LICENSE"
   .:"-rw-r--r--  1 thomashansen  staff   604 29 Oct 14:51 Program.cs"
   .:"drwxr-xr-x  3 thomashansen  staff    96 29 Oct 16:53 Properties"
   .:"-rw-r--r--  1 thomashansen  staff  3154 29 Oct 14:51 Startup.cs"
   .:"-rw-r--r--  1 thomashansen  staff  1458 11 Nov 10:57 appsettings.json"
   .:"-rw-r--r--  1 thomashansen  staff   650  9 Nov 08:26 backend.csproj"
   .:"drwxr-xr-x  3 thomashansen  staff    96  9 Nov 10:23 bin"
   .:"-rw-r--r--  1 thomashansen  staff   700  9 Nov 07:29 dev_backend.csproj"
   .:"drwxr-xr-x  9 thomashansen  staff   288 12 Nov 10:31 files"
   .:"drwxr-xr-x  9 thomashansen  staff   288  9 Nov 19:05 obj"
   .:"drwxr-xr-x  6 thomashansen  staff   192 29 Oct 14:51 slots"
   .:"-rw-r--r--  1 thomashansen  staff  1905 29 Oct 14:51 web.config"

Notice - If you ommit the [structured] argument, or set its value to "false", the result of the above invocation will return a single string.

Querying operating system version

In addition to the above slots this project also contains the following slots.

  • [system.os] - Returns description of your operating system
  • [system.is-os] - Returns true if your underlaying operating system is of the specified type

The last slot above takes an argument such as "Windows", "OSX, "Linux", etc, and will return true of the operating system you are currently running on belongs to the specified family of operating systems. Below is example usage of both.

system.is-os:OSX
system.os

Magic's GitHub project page

Magic is 100% Open Source and you can find the primary project GitHub page here.

Project website for magic.lambda.system

The source code for this repository can be found at github.com/polterguy/magic.lambda.system, and you can provide feedback, provide bug reports, etc at the same place.

  • Build status
  • Quality Gate Status
  • Bugs
  • Code Smells
  • Coverage
  • Duplicated Lines (%)
  • Lines of Code
  • Maintainability Rating
  • Reliability Rating
  • Security Rating
  • Technical Debt
  • Vulnerabilities

The projects is copyright Thomas Hansen 2023 - 2024, and professionally maintained by AINIRO.IO.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  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 was computed. 
.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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
17.2.0 169 1/22/2024
17.1.7 128 1/12/2024
17.1.6 114 1/11/2024
17.1.5 130 1/5/2024
17.0.1 153 1/1/2024
17.0.0 294 12/14/2023
16.11.5 232 11/12/2023
16.9.0 244 10/9/2023
16.7.0 356 7/11/2023
16.4.1 204 7/2/2023
16.4.0 201 6/22/2023
16.3.1 210 6/7/2023
16.3.0 221 5/28/2023
16.1.9 348 4/30/2023
15.10.11 289 4/13/2023
15.9.1 325 3/27/2023
15.9.0 310 3/24/2023
15.8.2 301 3/20/2023
15.7.0 338 3/6/2023
15.5.0 497 1/28/2023
15.2.0 450 1/18/2023
15.1.0 798 12/28/2022
14.5.7 479 12/13/2022
14.5.5 522 12/6/2022
14.5.1 432 11/23/2022
14.5.0 424 11/18/2022
14.4.5 506 10/22/2022
14.4.1 442 10/22/2022
14.4.0 477 10/17/2022
14.3.1 608 9/12/2022
14.3.0 404 9/10/2022
14.1.3 676 8/7/2022
14.1.2 437 8/7/2022
14.1.1 427 8/7/2022
14.0.14 482 7/26/2022
14.0.12 468 7/24/2022
14.0.11 423 7/23/2022
14.0.10 443 7/23/2022
14.0.9 426 7/23/2022
14.0.8 524 7/17/2022
14.0.5 579 7/11/2022
14.0.4 556 7/6/2022
14.0.3 459 7/2/2022
14.0.2 457 7/2/2022
14.0.0 446 6/25/2022
13.4.0 1,140 5/31/2022
13.3.4 1,398 5/9/2022
13.3.0 886 5/1/2022
13.2.0 1,092 4/21/2022
13.1.0 965 4/7/2022
13.0.0 687 4/5/2022
11.0.5 1,348 3/2/2022
11.0.4 706 2/22/2022
11.0.3 711 2/9/2022
11.0.2 738 2/6/2022
11.0.1 727 2/5/2022
10.0.21 707 1/28/2022
10.0.20 713 1/27/2022
10.0.19 699 1/23/2022
10.0.18 690 1/17/2022
10.0.15 882 12/31/2021
10.0.14 503 12/28/2021
10.0.7 1,351 12/22/2021
10.0.5 701 12/18/2021
9.9.9 1,618 11/29/2021
9.9.4 979 11/21/2021
9.9.3 527 11/9/2021
9.9.2 578 11/4/2021
9.9.0 691 10/30/2021
9.8.9 679 10/29/2021
9.8.7 591 10/27/2021
9.8.6 598 10/27/2021
9.8.5 686 10/26/2021
9.8.0 1,292 10/20/2021
9.7.9 598 10/19/2021
9.7.5 1,424 10/14/2021
9.7.0 793 10/9/2021
9.6.6 1,143 8/14/2021
9.2.1 5,855 6/1/2021