Building a scriptcs script pack

If you haven’t seen scriptcs yet you should check it out. It’s a scripting environment for C#, but more interestingly it incorporates a module system similar to what you’d see in node.js using NuGet as a packaging mechanism. I won’t go into an intro to scriptcs, because others like Scott Hanselman and the man himself, Glenn Block (and contributors) have done a better job that I could have.

Instead I’ll talk about a concept that (so far) hasn’t had a lot of documentation love – script packs.

What is a scriptcs script pack?

The basic idea is that a script pack is an object (generally compiled in a DLL and available in your script’s bin directory) that either exposes some functionality to the scripting environment or simplifies the usage of some other functionality. This seems like an overly broad definition so let’s start with an example of script pack usage. From the scriptcs.WebApi script pack.

public class TestController : ApiController {
  public string Get() {
    return "Hello world!";
  }
}

var webApi = Require<WebApi>();
var server = webApi.CreateServer("http://localhost:8080");
server.OpenAsync().Wait();

Console.WriteLine("Listening...");
Console.ReadKey();
server.CloseAsync().Wait();

That’s the complete script – no using statements and ridiculously simple API. The script pack takes care of “injecting” using statements into any script that uses the script pack (ie, calls that Require method above – we’ll get into that) and returning a script-friendly object to the shell.

Let’s get started building a script pack. I want to create a script pack that allows me to pull down arbitrary strings from an HTTP request – let’s keep it really nice and simple.

Fire up Visual Studio (note: you don’t actually need Visual Studio for this, but I’ll assume if you’re comfortable enough to use other tools then you’ll know when to adjust the steps) and create a new Class Library project. Delete that damn Class1.cs file, open the Package Manager console and execute the command

Install-Package scriptcs.contracts

The contracts library has a couple of interfaces we’ll need to implement to make our script pack work. Let’s start by creating our “convenience object”. We’ll call it Http, and all it’s going to do is make GET requests and return strings. So let’s implement this class.

using System.Net;
using ScriptCs.Contracts;

namespace ScriptCs.Http
{
    public class Http : IScriptPackContext
    {
        public string Get(string uri)
        {
            var client = new WebClient();
            return client.DownloadString(uri);
        }
    }
}

That IScriptPackContext interface there is a marker interface that scriptcs seems to use to identify objects that are capable of being returned by script packs. It has no members, so there’s nothing to implement.

The next step is to implement the script pack itself. This object will be picked up by the scriptcs engine and is capable of generating and returning IScriptPackContext objects. Because our script pack is so simple we don’t need to do much in the implementation of this interface:

using ScriptCs.Contracts;

namespace ScriptCs.Http
{
    public class HttpScriptPack : IScriptPack
    {
        IScriptPackContext IScriptPack.GetContext()
        {
            return new Http();
        }

        void IScriptPack.Initialize(IScriptPackSession session)
        {}
        void IScriptPack.Terminate()
        {}
    }
}

This is fairly self-explanatory. The scriptcs engine will cache all available IScriptPack objects and when a particular script pack is requested (see below) then the appropriate script pack context will be returned.

The Initialize method here is interesting. We’re not doing anything with it in this particular program, but it’s worth going over what can be done. This method gives us an IScriptPackSession object which has two important methods:

  • AddReference – use this method to add library references to be available in your script. After your script pack is loaded into a script the specified references will be available for your use with no further code inside the script.
  • ImportNamespace – this method can import namespaces for use in your scripts. It’s just a nice convenience so that your user’s scripts can stay nice and clean. We don’t need to do this in our current script pack, but if you refer back to the WebApi script above you’ll notice we’re using the ApiController class without any using statements. This is because the WebApi script pack imports this namespace for you.

Obviously the Terminate method should be used for cleaning up any resources after yourself and leaving the machine in a good state.

So that’s pretty much all there is to building a script pack. Now we’ll go ahead and use it. Build your library, grab the DLLs from your bin directory and move it into a test folder like c:\myscripts\bin. Create a new script that looks like

var http = Require<Http>();
Console.WriteLine(http.Get("http://google.com"));

Save it into your scripts folder (c:\myscripts\myscript.csx in my above example) and execute it using

scriptcs myscript.csx

And observe the output. All done! Now all that’s left is to package your script up in a NuGet package and publish.

This entry was posted in Programming and tagged , , . Bookmark the permalink.

4 Responses to "Building a scriptcs script pack"

Leave a reply

*