plainblack.com
Username Password
search
Bookmark and Share

Writing Macros for WebGUI

Perl programmers are lazy people. They like to do only the smallest amount of work they need in order to accomplish a task, and they have a penchant for reusing others' work. The official literature for the Perl programming language even goes so far as to extol laziness as a programmer virtue. The Comprehensive Perl Archive Network©, or CPAN (http://www.cpan.org/) exemplifies this laziness perhaps better than anything else.

 

WebGUI, being a GUI for web content management and development, also encourages laziness. Among other features, WebGUI provides macros, which are special plaintext commands that invoke custom Perl code to perform specific functionality (most often insertion of text / markup). Sounds a lot like an asset, doesn't it? That's because macros and assets share some common characteristics:

 

  • they both comprise specific functionality designed to be reused

  • they're both configurable, albeit in different ways

  • they're both implemented in Perl and, for the most part, output HTML

  • they both have skeleton files from which you can start writing your own custom functionality.

 

But that's where the similarities end. While your users will be directly viewing assets, they won't be directly viewing macros. That's because macros are included in assets, and are expanded when the asset is rendered.

 

When invoked, the macro does its thing and (most of the time) returns some content at the point in the asset where it was invoked. This makes it very easy to determine exactly how a macro will affect a page. There's no delayed invocation or anything like that. Just call it and go. But how do you call a macro? Unsurprisingly, it looks rather like a Perl subroutine call. Following are some macro examples:

 

  • ^AdminBar;

  • ^FileUrl(/some/path/to/an/image.jpg);

  • ^AddSomeNumbers(1, 2, 3);

 

There are three important parts here: the initial caret (the ^ character), the name of the macro, and the terminating semi-colon.

 

These three elements comprise macro invocation; without them, nothing will happen. Additionally, for macros that take parameters, the parentheses enclosing the parameters are required. Commas separate parameters. Parameters included, but not separated by commas, are taken to be literal strings. For macros that don't take parameters, or for invocations of those that do which don't require passing parameters, the parentheses can be omitted.

 

How Macros Work

Macros are processed upon asset rendering, at the point in the asset's content where they are found. This simplicity means that it's relatively straightforward to get started developing macros: write some code that produces some kind of markup, and return it. That's it. But of course, there can be more to it than that.

 

When invoked, the macro's process subroutine (subroutine! It is not a method!) is invoked. This subroutine is passed to the WebGUI::Session object, along with any parameters specified for the macro. As pointed out before, invoking a macro works very much like calling a subroutine in Perl. Parameters are all passed as very simple text strings, and thus must be parsed by the process subroutine if they use key=value syntax or something else not directly usable by the process subroutine. Because of how Perl treats string and number data, there doesn't need to be any magical conversion of strings containing numeric values into Perl numbers. The process subroutine's job is to generate some markup and return it to the code invoking the macro. Of course, the macro doesn't always need to return markup, or anything at all. It just won't present any visible content to the user.

 

The real power in macros comes from the fact that their logic is coded in Perl, as opposed to Javascript. Macros also have access to the WebGUI API, giving them access to fun things like database access and the ability to manipulate anything in WebGUI. Macros also have access to the full power of Perl – all of CPAN is at your fingertips. Caution must be taken, however, as overly expensive operations will delay page loads.

 

The Macro Skeleton

Like most aspects of WebGUI, there's a skeleton file available for macros from which you can start developing your own custom functionality. Within a WebGUI installation, this file is located at WebGUI/lib/WebGUI/Macro/_macro.skeleton. The process subroutine is called automatically by WebGUI, and must be present for the macro to function.

 

The job of a macro is to process any given parameters and use them to produce some kind of meaningful output, or to do something useful. This macro skeleton does neither of those, but it does contain everything needed for a fully functioning macro.

 

Hello World

Let's start with a fully functional, albeit not altogether useful, macro. This macro will output the text “Hello, world!” to page where the macro is called. It's extremely simple and straightforward, but illustrates the main principles involved in developing a macro. There's more to developing a macro than just writing the code, but not much. For this specific example, the “other stuff” is more complicated than the macro itself, but that will only be true for these simplest of cases. This section will cover those details in addition to providing the (short!) code required for the macro itself.

 

First, the more difficult task: telling WebGUI about your brand new macro. WebGUI looks up information about each macro in its configuration file. Take a look at part of the relevant section from the default configuration file that ships with version 7.5:

 

"macros" : {

"#" : "Hash_userId",

"/" : "Slash_gatewayUrl",

"@" : "At_username",

"AOIHits" : "AOIHits",

"AOIRank" : "AOIRank",

"AdminBar" : "AdminBar",

"AdminText" : "AdminText",

"AdminToggle" : "AdminToggle",

"AdSpace" : "AdSpace",

"AssetProxy" : "AssetProxy",

"CanEditText" : "CanEditText",

"D" : "D_date",

[...]

"u" : "u_companyUrl"

},

 

What you see here is a JSON dictionary. WebGUI uses JSON throughout, and if you're going to be hacking on WebGUI, you'll need to be comfortable with it. Basically, to develop a macro, you only need to know a few things. Macros are specified in this dictionary in key-value pairs. The pairs are separated by colons and enumerated with commas. There must not be a comma after the last item in this enumeration of key-value pairs, quite contradictory to Perl, which doesn't care either way. The keys are the strings that will appear in your assets when you invoke the macro. For example, you'd use ^/ or ^@ instead of ^Slash_gatewayUrl. The values, then, are the class names (with respect to the WebGUI::Macro part) of the macros themselves. Thus, if you have a macro whose class was WebGUI::Macro::HelloWorld, and you wanted to invoke it as ^Hello; in your assets, the line would look like this:

 

“Hello” : “HelloWorld”,

 

Remember to leave off the comma if it's the last key-value pair in the dictionary.

 

Now for the code. First, copy the macro skeleton to a new file, such as HelloWorld.pm. Change the package and file names to whatever you specified in the configuration file, and replace the existing process method with this code:

 

sub process {

my $session = shift;

return 'Hello, world!';

}

 

The macro receives the session object as a parameter, and returns the message. That's it. The return value from the process sub is what WebGUI displays on the item (asset, snippet, anything capable of containing a macro) where the macro is in the content for that item.

Note: It's very important to change the package name after you've copied the skeleton file. While easy to forget, it will cause your macro to not function.

 

Getting Argumentative: Accepting Parameters

Not all macros can be invoked without any kind of parameter. Some need a bit of extra information to do their jobs correctly. This is possible, and again relatively straightforward. Take a look at an illustrative example. For the purpose of this example, say you want to pass a custom greeting to the user and have it insert the full user name. This could easily be obtained with an appropriate call of the User macro, but will serve this purpose well.

 

That said, take a look at the code.

 

sub process {

my $session = shift; # get the session

my $greeting = shift; # get the greeting for the user

my $userId = $session->user; # get the user ID

my $userName = $user->userName; # and the user

 

# jostle them a bit and inspire them to log in

if($userName eq 'Visitor') {

$userName = 'anonymous coward';

}

 

my $markup = <<HTML;

<p>Why, hello there, ${userName}! $greeting</b>

HTML

 

return $markup;

}

 

Take a look at this step by step. First is the WebGUI::Session object. Next, is the greeting string. That's a string of text, so no commas should be present in the invocation of the macro, otherwise you'll get part of it in $greeting and the other half will stay in limbo, never to see the light of day. Next, pull the user ID associated with the session, and then get the user's name as stored in his/her profile. Having users log in is a good thing, most of the time, so anonymous users are jostled a bit, and then construct the greeting and return it.

 

Now for Something Completely Different

The more serious usages of macros have been covered, but you can have a bit of fun with them, too. A perennial favorite is something that will output fortunes to users. Different databases exist for the venerable fortune program, each containing much wisdom. Take a look at the process subroutine for this macro. This subroutine will entail processing key=value argument syntax, rather than the positional syntax used in previous macros. Take a look at the code.

 

sub process {

# get the session

my $session = shift;



# and the arguments to the fortune command

my @arguments = @_;

my %fortuneArguments;



# process the arguments passed to the macro

for my $keyValuePair ( @arguments ) {

my ($key, $value) = split /=/, $keyValuePair;

$fortuneArguments{$key} = $value;

}

 

# set up base fortune command (change this if your path is different)

my $baseCommand = '/opt/local/bin/fortune';

my @commandArguments;



if( defined $fortuneArguments{'offensive'} ) {

push @commandArguments, '-o',

}

if( defined $fortuneArguments{'database'} ) {

push @commandArguments, $fortuneArguments{'database'};

}



# finally, run the command

my @responseToUser = qx|$baseCommand @commandArguments|;



# check if something went wrong

if( ($? >> 8) != 0) {

return "<p>Sorry, an error occurred retrieving your fortune. Watch out for black cats, ladders, and mirrors!</p>"

}

else {

my $output = "<pre>@responseToUser</pre>";

return $output;

}

}

 

As always, you start with getting the session object. Next, get the list of parameters. They're to be passed as key=value pairs, separated by spaces, so the macro processing code sees them as “words”. This entails processing the “words”, splitting them up on the '=', and adding them to a hash to keep track of them and their values. Next, configure the base command. The example given here is for a Mac OS X Leopard© installation using Macports©. Most traditional Unix© systems will use /usr/games/fortune or /usr/bin/fortune. Windows© users will probably have to fetch a pre-compiled fortune program to use this example. Following this, the given arguments are checked and command line options to the fortune command are constructed accordingly. Finally, run the command, check for errors, and give either an error message or the fortune to the user. That's it!

Keywords: macro

Search | Most Popular | Recent Changes | Wiki Home
© 2023 Plain Black Corporation | All Rights Reserved