Sam-I-Am on Web Development

Sam Foster on the web and web-ish software developmen

Thursday, January 08, 2009

Setting up Rhino

I've been using the Rhino engine more and more to run command-line scripts, fiddle and try things out. But my setup has taken shape slowly, and it wasn't much fun to be honest when I first got started.

I'm on a mac (Leopard), and here's how I've got it now:

  1. Download the Rhino .jar file, you'll find it inside the latest (binary) release.
  2. Drop it in your {user}/Library/java/Extensions folder (create it if it doesnt already exist). That way its automatically added to your classpath whenever you run java, so no need for -jar cmdline params to pull it in (and the classpath insanity that brings).
  3. I made a shell script to invoke rhino and aliased that, but actually you could simply alias the one-liner it contains:
    java jline.ConsoleRunner org.mozilla.javascript.tools.shell.Main "$@"
    To make an alias, in my ~/.profile I've got the following:
    alias rhino='~/utils/runjs.sh'
  4. Now, in your terminal, you just type 'rhino' and it puts you into an interactive shell where you can load files, write js statements and see the results instantly.

But, Rhino's shell is frankly, a crappy user experience. Its got no history, no cursor movement at all. You can just type and hit enter. If you screw something up you have to type it all over again. And the whole beauty of writing *javascript* like this is that you can load a .js library repeatedly as you work on it and try calling its functions. But its a PITA out of the box.

Look again at that java cmd-line I'm using to run rhino and you'll see its using jline. This enlightening post on Taming the Rhino finally brought happiness to my rhino shell by introducing me to jline

You download jline and again, drop the .jar file into your Library/java/Extensions folder. Now the interactive shell is much more shell-like. You have a history on the up arrow, you can back up and move around the current line to edit, and do more scripting and less typing in general.

To run a particular .js file and exit, you do 'rhino yourfile.js'. Further cmd-line parameters populate the arguments object in your script, so rhino script.js filename1 filename2 myoption=true would populate arguments like this:

[
 filename1,
 filename2,
 myoption=true
]

FWIW I use a pattern like this to wrap my script body:

(function(scriptArgs) {
  function main() {
   // process scriptArgs,
   // e.g. split on = to get name/value pairs
   // and populate an options object
  }
  main();
})(Array.prototype.slice.apply(arguments));

Back in the shell, you can load, try, load again, try again:

$ jeltz:trunk sfoster$ rhino
Rhino 1.7 release 1 2008 03 06
js> load("lib/docblock.js");
js: "lib/docblock.js", line 7: uncaught JavaScript runtime exception: ReferenceError:\
"lang" is not defined. at lib/docblock.js:7 at lib/docblock.js:5 at <stdin>:2 js> load("lib/langUtils.js"); js: "<stdin>", line 3: Couldn't open file "lib/langUtils.js". js> load("lib/langUtil.js"); js> load("lib/docblock.js"); js> var p = docblock.getParser(); js> p.parse("/** @author sfoster */"); TAG:author:sfoster js> quit(); jeltz:trunk sfoster$

Try it, I think you'll like it.

Labels: , ,

Wednesday, November 28, 2007

Simple Clocks with the Dojo Toolkit

Something I was playing with - this page shows a couple of javascript clock/countdown treatments. None are as whizzy as the dojox.gfx (vector graphics) clock you might have seen around, or your various dashboard widgets - but this is just dojo core + 6k (uncompressed) of code.

Labels: , , ,

Tuesday, November 27, 2007

string.replace with substitution by function

It may or may not be news to you that in Javascript you can do:
someString.replace(
  /\w+/g,
  function(match) {
    return "blah"
  }
);
Which in this case turns "the original string" into "blah blah blah". Your function is passed the match, and you return whatever you want. That's pretty handy, as you can run the match through transformations, or even use it to lookup or generate some entirely new value. You can also have side-effects from the match, which has interesting possibilities, not least of which is debugging: function(match) { console.log("I matched %s", match); return match); If you're like me, you might even think you could do something like this:
someString.replace(
  /<(\w+)[^>]*>)/g,
  function(tag) {
    var tagName = RegExp.$1;
    return tagName.toLowerCase(); // or whatever...
  }
);
.. but sadly you'd be wrong. At least as soon as you went to check in IE. It seems IE doesnt populate the properties from the match into the static RegExp object until after the whole replace is done. And your substitution function only gets passed the matched string, not the array of match + sub-matches you might be used to :( Still, there's plenty of mileage there. How about this:
  function format(str, obj) {
   var str = str.replace(/\$\{[^}]+\}/g, function(mstr) {
    var key = mstr.substring(2, mstr.length-1);
    return (typeof obj[key] == "undefined") ? "" : obj[key];
   });
   return str;
  }
.. which takes a string like "<h1>${firstname} ${lastname}</h1>", and an object like: { firstname: "Bob", lastname: "Smith" } to produce <h1>Bob Smith</h1>

Labels: ,