ShellScriptBuilder for Capistrano
Posted by ezmobius Mon, 22 Jan 2007 02:31:00 GMT
This is a first release of ShellScriptBuilder. This release is to gather some feedback about this dsl and what it still needs to be completely useful.
ShellScriptBuilder is a small ruby dsl for buidling bash shell scripts in ruby. It will insulate you from the need to remember all of the flags a shell if statement can take. And it also insulates you from needing to remember all the complex escape rules that the shell has as well as how capistrano itself escapes commands.
script = shell do |sh|
sh.sudo.ln_nsf "foo/bar", "bar/bar"
sh.echo "some string" => "/path/to/log.txt"
sh.if :directory? => "some/dir" do |sub|
sub.rm_rf 'some/dir'
sub.ln_nsf "shared/foo", "some/dir"
sub.if_not :file? => 'foo' do |ssub|
ssub.mkdir_p "some/foo"
end
end
sh.unless :file? => 'foo/bario' do |sub|
sub.touch 'foo/bario'
end
sh.if :writable? => "some/file.txt" do |sub|
sub.echo "#{Time.now}" => 'some/file.txt'
end
sh.mkdir_p 'foo/bar'
sh.rm_rf 'foo/bar'
end
puts script
OUTPUT:
#!/bin/sh
sudo ln -nsf foo/bar bar/bar
echo "some string" >> /path/to/log.txt
if [ -d some/dir ]
then
rm -rf some/dir
ln -nsf shared/foo some/dir
if [ ! -a foo ]
then
mkdir -p some/foo
fi
fi
if [ ! -a foo/bario ]
then
touch foo/bario
fi
if [ -w some/file.txt ]
then
echo "Sun Jan 21 19:18:32 -0800 2007 " >> some/file.txt
fi
mkdir -p foo/bar
rm -rf foo/bar
Right now this is a standalone library with no external dependencies. But I plan on making it a capistrano plugin here shortly and rewriting all the standard recipes to use this dsl. Then it’s possible Jamis will put this in core capistrano.
I would love some feedback on the ‘feel’ of the dsl as well as anything else you feel it needs to support.
This builder uses a cool method_missing hack to allow for commands like this: “ln_nfs” to become “ln -nfs”. Let’s take a quick look at how that works.
def method_missing(sym,*args,&blk)
if sym.id2name =~ /(.*)_(.*)/
__send__($1,'-'+$2,*args)
else
@cmdbuff << "#{@nesting}#{sym} #{args.join(' ')}\n"
end
end
This is a double dispatch example. So when we call a method like:
script = ShellScriptBuilder.new script.ln_nfs "some/dir", "some/other/dir" puts script # => "ln -nfs some/dir some/other/dir\n"
So what happens is that method_missing gets called the first time like this:
method_missing(:ln_nfs, "some/dir", "some/other/dir")Since the missing method has an underscore it will match the regex in the first if statement here:
if sym.id2name =~ /(.*)_(.*)/
__send__($1,'-'+$2,*args)
What this does is munge the method name and args and then send’s that new method signature. Now method_missing gets called like this:
method_missing(:ln, '-nfs', "some/dir", "some/other/dir")
So there is still no ‘ln’ method defined in the code. Somethod missing gets called again, this time there is no _ in the method name so it goes into the else part of the if statement. All the else part does is create the new command and place it in the @cmdbuff:
else
@cmdbuff << "#{@nesting}#{sym} #{args.join(' ')}\n"
@nesting is just a way for the output to get indented when inside if statements to make the outputted shell script look nice and clean, @nesting is an empty string to represent no indentation when you are in a top level call like this. The output of the else part of the method_missing is just the name of the symbol(command) and all the arguments join’d together with spaces.
This little method missing hack makes it so we can use any arbitrary command line program with or without command line arguments in our new DSL. This allows for you to specify any command line program, you just use _ instead of a space and a – to represent the args. So somecmd_foo becomes somecmd -foo
The output of the method call we just disected is:
ln -nfs some/dir some/other/dir
Fun stuff. Anyway, I would appreciate it if people played with this a little bit and let me know what you think it still needs to be as useable as possible. The plan is to have this buidl shell scripts and then for Capistrano to upload the script to the server into a tmpfile and then execute the script like a normal shell script.
You can get a tarball here. I am still waiting for a rubyforge project so no svn yet. There is a full test suite in the tarball. You can build a gem by unpacking the tarball and cd’ing into the root dir and running:
$ rake package
$ sudo gem install pkg/shell-script-builder-1.0.0.gem
<pre>
Searching...




