Announcing ErlCtl, 0.1

// February 21st, 2010 // Uncategorized

There has always been a need to have a good command-line interface to most system software. While Erlang is great for writing this kind of stuff, it's natural command-line can be less than stellar. If we were left to use the erl command itself, command-lines would be long to type and arduous to construct.

Most projects avoid this problem with the standard Apache idiom of having a control script.

As it so happens, Erlang is a lovely language for this idiom. Rather than lots of trouble managing processes and working with sockets, Distributed Erlang neatly solves these issues of simple security, finding running programs, and generally making it easy for two Erlang programs to interact.

While the major Erlang projects have their own implementations of this (ejabberdctl, rabbitmqctl, riak-admin), there isn't really anything built-in to fill this need.

After a little frustration trying to adapt other code to fit the bill, I just wrote my own for use with my latest super-sekret project. After a little cleanup, here's a generic library for building simple command-line interfaces and an example application that uses it! World, meet ErlCtl. It's young, so, please, be gentle.

How Do I Use It?

How might you use this software, you ask? Well, it's really quite simple. After installing erlctl-0.1 into your Erlang lib_dir (not automated yet), follow these two easy steps.

First, somewhere in your command path, make a symlink from the erlctl binary (in erlctl-0.1/bin) to the name of your control program. For this example, let's say you have an application named awesome. In this case, you'd link the program to awesomectl (or, alternately awesome_ctl).

Second, in your application, create a module based on the same name. In this case, you would create awesome_cli.erl. This module should export functions (which I call "commands") that take two arguments. The first argument is the "context" within which that command runs, and the second is the list of arguments for that function.

Commands For Any Season

For example, let's say you have a command that should print out the version of your application. If you define that "version" command with the "always" context, then it will simply run your code. For example:

version ( always ,[]) -> { ok , "Version: ~p " ,[ 0 . 1 ]}.

This simple function will return a successful return code and print out the version number. Presto, instant CLI! You can run this a command line like "awesomectl version". It's that easy!

Starting Things Up

Let's say that you need to start a server process. If you provide a command in the "not_running" context, it can start a server as easily as it can return the atom "start".

start ( not_running ,[]) -> { start ,[], "Starting." };

It doesn't have to stop there. Need to run some code after starting the server? Create the same command with the "started" context, and it will be run in the started server.

start ( started ,[]) ->

ok = application : start ( ctl_ex ),

{ ok, "Started! " } .

Notice that the first command decided to start a new server, the server started, and the second command took over inside of the newly started server? Neat, huh! You would run this with the simple command line of "awesomectl start".

It's Like An "Erlang Endoscope"™

Let's say your server is happily running along, but you need to monkey about with its internals. The "running" context will cause the command to happily run inside of the server with no messy work required!

list_users ( running ,[]) ->

{ ok , Users } = ctl_ex : list_users (),

format ( "Users: ~n " ),

case Users of

[] ->

io : format ( " <no users> ~n " );

_ ->

lists : foreach (

fun ( User ) ->

format ( " ~s~n " ,[ User ])

end ,

Users

)

end ,

ok .

Notice how there are a few useful commands to help out? The format command transparently handles printing out stuff at the client side, without worrying about where your code is actually running. That's location-transparency that saves you time! As you might have imagined, the command here is "awesomectl list_users".

What's even better is that commands may implement any combination of these contexts as they see fit. So you can give intelligent errors when a server isn't running, or implement actual functionality when it is!

A Fully ARMED and OPERATIONAL Example Application

Is this a bit too much to take in at once? Why not take a look at the example application I wrote for you. It's a simple application, with a simple gen_server that keeps track of a list of users. A full CLI is provided for it in this single file.