Tuesday, December 13, 2005

lab 49 - wrappers

NAME

lab 49 - wrappers

NOTES

Acme is the hub of my activity where all my tools are at hand. It is inconvenient to have to step outside it while working on a task. But it is impractical to port all the tools I need into Inferno. However, Inferno is an integrating environment. It is oftentimes easy to put a wrapper around an external tool so that it is usable within Acme.

In this lab I show a few examples of putting wrappers around external tools so that they blend in to the Inferno environment and work together with Inferno's tools.

The first, very simple example is awk.

fn awk {os awk $*}

With this definition it is only possible to use awk as a filter without naming files as command line arguments. But this is often good enough.

Very often I work on my laptop which has NT as the host os. But I need to use dict and spell all the time, and my laptop doesn't have these installed. I integrate these tools into my environment by running emu on a Plan9 or Linux machine and exporting a styx service that will include the #C device bound to /cmd. Then I just need to bind remote service before running the command.

fn spell {
 bind /n/linux/cmd /cmd
 cat $* |os spell
 unmount /n/linux/cmd /cmd > /dev/null >[2=1]
}

Now I can use >spell within acme and become oblivious to the fact that spell is running elsewhere on my network.

Let's say I want to run javac hello.java from within Acme, and also button-3-clicking on any error messages would jump to the relevant line within the file. The problem here is the Inferno namespace needs to be mapped to the host namespace. This can only be done if the file actually resides on the host. But given that assumption we can easily establish a convention whereby names within inferno map to hostnames.

fn file2host {
 for (i in $*) {
  f := `{cleanname -d `pwd $i}
  echo $f  |sed 's@/n/d@D:@
    s@/n/c@C:@'
 }
}

fn host2file {
  sed 's@D:@/n/d@
  s@C:@/n/c@' |sed 's@'^`{pwd}^'/@@'
}

fn javac {
 classpath='your favourite jars'
 args=$*
 os javac -classpath $"classpath `{file2host $args} | host2file
}

This assumes we have bound, say the C: and D: drives to /n/c and /n/d. The mapping is simple. The benefit is that I can use mk to manage builds, and use plumber to plumb the compiler errors to Acme.

Here's a more complicated example, used for building the emu source code and libs. The assumption now is the source code is on the host and the mapping from inferno to host namespace is just prepending the $emuroot.

fn file2root {
 args = $*
 for (i in $args) {
  i = `{cleanname -d `pwd $i}
  echo $emuroot ^ $i |sed 's/\//\\/g'
 }
}

fn 9c {
 load arg
 args := $*
 flags=''
 (arg
  I+ {flags = $flags  -I ^ `{file2root $arg}}
  D+ {flags = $flags -$opt ^ $arg}
  '*' {echo unknown option $opt}
  - $args
 )
 cc=cl
 cwd=`{echo $emuroot ^ `pwd | sed 's/\//\\/g'}
 cflags=(-c 
  -nologo
  -W3 
  -Zi
  -Yd
  -MT
  '-D_WIN32_WINNT=0x0400'
  -I$cwd
  -I$emuroot\Nt\386\include
  -I$emuroot\include
  -I$emuroot\libinterp
  -I$emuroot\emu\port
  -I'C:\Program Files\Microsoft Platform SDK\Include'
  -I'C:\Program Files\Microsoft Visual C++ Toolkit 2003\include'
  -Fo$cwd  )
 os $cc $cflags $flags `{file2root $args}
}

This is interesting because emu could be built using mk from inside Inferno. Given a definition of 9c, 9l and 9ar for each platform the mkfiles could be simplified (however, shifting the complexity to the shell).

The final example is for XML tools. Often these tools take URLs instead of filenames. The trick is to serve the files I'm working on using httpd. Httpd is now serving as a general mapping from my local namespace to a universal namespace. I can bind the files in from anywhere and then work on them with XML tools, using tools that might also be bound in from anywhere. For example, I want to use xalan to transform XML using XSLT. I bind my working directory onto /services/httpd/root and run svc/httpd/httpd. Then files below the root get mapped to URLs.

domain:=.my.domain.name
fn file2url {
 sysname:=`{cat /dev/sysname}
 url:='http://'$sysname ^$domain
 for (i in $*) {
  f := `{cleanname -d `pwd $i}
  echo $f  |sed 's@/services/httpd/root@'^$url^'@'
 }
}

fn xslt {
 classpath='/java/lib/xalan.jar'
 args := $*
 flags=''
 (arg
  t+ {flags =-XSL `{file2url $arg}}
  - $args
 )
 args=$*
 if {~ $#* 1} {
  args= -IN `{file2url $args}
 } {
  echo 'usage: xslt -t transform file ' >/fd/2
  raise usage
 }
 bind /n/some_java_host/cmd /cmd
 os java -cp $classpath org.apache.xalan.xslt.Process $flags $args
 unmount /n/some_java_host/cmd /cmd >/dev/null >[2=1]
}

This last approach is possibly the most flexible. But all of them demonstrate that Inferno supports, in the classic UNIX way, using shell to modify a program's interface. In doing so the external tools and Inferno tools can be made to work together, creating a richer, more productive environment.

2 comments:

Anonymous said...

I use Inferno to remotely control itunes in OSX by doing
os -m /n/whitegene/cmd osascript
tell application "iTunes"
playpause
end tell

Jay said...

As you have said, Wrappers seem to be one of the most powerful tools that Inferno has, especially since Hosted Inferno can distribute the access to tools that run only on their native platforms to a single integrated environment. "iTunes" being an example here: It is also possible to route the audio output through the network back to the controlling Inferno terminal by exporting the audio device to the Macintosh Hosted Inferno.