It turns out, RubyShell 0.1 couldn’t handle method definitions or local variables because of the way Ruby code was being evaluated. Whoops…

Anyway, that’s only one of the several new features (okay, that one was a bug fix…) in RubyShell 0.2. If you want to skip the explanation, you can go ahead and download it now or look at the list of fixed issues.

RubyShell 0.2 brings a few new features in addition to the sizeable bug fix of working method definitions and local variables.

Multiline Ruby

First in my mind (probably because it was the most work) is the presence of support for multiline Ruby. For example:

RubyShell> def my_method
RubyShell> 'test'
RubyShell> end
=> nil
RubyShell> my_method
=> "test"

Other constructs supported as multiline constructs are if, while, until, and class.

There’s still no real parser for RubyShell. Our Parser class is more of a decider class—deciding whether code will be run as regular Ruby or as a command. However, for this release it has taken on very limited parsing capabilities. Basically, Ruby can be handled multiline for specific constructs (see above) that appear at the start of a line. Basically, if any of these words start the line, then the `multiline level’ is increased by one. Every time an `end’ is seen, that level is decreased by one. When it reaches 0 again (its initial state), the block is considered finished and is fully evaluated as Ruby. A backslash on the end of the line will also increase the multiline level, this time until a line appears without a backslash on its end. Finally, a do anywhere in the current line will also trigger an increase in the multiline level.

Amongst other things, this approach means that commands that have the same name as any of these keywords will be completely unsupported (since seeing them automatically opens a multiline Ruby mode). This is only a small problem, since these constructs aren’t typically used as command names anyway.

Multistatement RubyShell Code

In addition to handling multiline Ruby, this release also supports the semicolon (;) as a statement separator. So, for example:

RubyShell> def my_method; 'test'; end; echo magic
=> nil
magic
RubyShell> my_method
=> "test"

In this context, Ruby code and regular commands can still be freely intermixed. Internally, the parser takes this line apart and actually parses them as individual statements as if the input had come in on separate lines. Currently, splitting on the semicolon is done in a fairly hackish way (a simple split, then a recombination in a loop to handle semicolons inside strings); this would be better handled by a regular expression, but I was unable to come up with a satisfactory one. If any ideas are forthcoming, I would definitely appreciate some help in this regard.

Shell History

Shell history is implemented. In fact, it was a fairly trivial addition (just had to pass an extra parameter to the readline method). However, it’s still missing part of what I wanted it to be able to do, namely the compression of multiline Ruby input into a single line separated by semicolons (and the ability to make that optional).

Customizable Prompt

The funnest new feature in this release is the customizable prompt. The prompt can now be set to either a string or a callable object (usually a proc, but anything that responds to call will do, so you could technically write your own full-blown prompt-generating class if you wanted to). Some examples:

RubyShell> ReadlineMonitor.prompt = 'Man am I Cool# '
=> "Man am I Cool# " 
Man am I Cool# ReadlineMonitor.prompt = lambda { "#{ENV['USER']}@#{Dir::pwd}> " }
=> #<Proc:0xb7b8b890@./ruby_shell/ruby_evaluator.rb:31>
shadowfiend@/home/shadowfiend/ruby_shell_tags/ruby_shell_0.2/lib> cd ..
shadowfiend@/home/shadowfiend/ruby_shell_tags/ruby_shell_0.2> 

ANSI color codes should work for this prompt, too; so, combined with the `colored’ gem:

RubyShell> require 'colored'
=> true
RubyShell> ReadlineMonitor.prompt = lambda { "#{ENV['USER'].green}@#{Dir::pwd.blue}> " }
=> #<Proc:0xb7b8b890@./ruby_shell/ruby_evaluator.rb:31>
shadowfiend@/home/shadowfiend/ruby_shell_tags/ruby_shell_0.2/lib> cd ..
shadowfiend@/home/shadowfiend/ruby_shell_tags/ruby_shell_0.2> 

(But with colors this time ;))

I’m hoping to make that syntax cleaner in the future. Specifically, I’m hoping “self.prompt = ‘whatever’” will work. The next step would be a simple “prompt = ‘whatever’”, but that one requires a fair bit of voodoo, since it involves catching the assignment to a local variable and making it call a method instead. As such, I’m uncertain whether or not I’ll be implementing it.

On the Horizon

Items on the horizon are listed on the open issues page on Software Without Incident. Output redirection and more readable pipe handling in Ruby is next, with the following syntax being the target piping syntax:

RubyShell> ls | with_each { |file| puts file[0..3] }
ruby
ruby

Downloading

Again, the 0.2 release file is available on the RubyShell files page on Software Without Incident.

6 Responses to “RubyShell 0.2: Now With 100% More Ruby!”

  1. Jeff Says:

    When I try to download 0.2, I get a 404 which says, “The page you were trying to access doesn’t exist or has been removed.”

  2. Shadowfiend Says:

    Crap… Sorry about that, I updated redMine yesterday and I forgot to double-check the files. It’s back now.

  3. Frederick Ros Says:

    Just a quick fix to handle entering nothing (i.e. just pressing enter):

    - ruby_shell_0.2/lib/ruby_shell/parser.rb 2007-08-12 19:52:37.000000000 +0200 + ruby_shell_0.2-fix/lib/ruby_shell/parser.rb 2007-08-14 14:59:54.000000000 +0200 @ -36,6 +36,9 @ # single command, and running them appropriately. Also see parse_pipes # and parse_command. def parse_input(data) + + return if data.nil? || (data.respond_to?(:empty?) && data.empty?) + # We don’t always run the split so we don’t constantly invoke the overhead # of the regexp engine + splitting. if data.include?(’;’) sl

  4. Shadowfiend Says:

    Thanks! I’ve just committed that in revision 90, along with the associated new spec. I’ve been meaning to get around to doing that, but every time I got the error I was in the middle of something else :-P

  5. Diogo Says:

    Cool, i liked it. keep up the good work. (“cd ~/” dont work for me: bug, or feature?)

  6. Shadowfiend Says:

    Bug, as it were. Or, rather, missing feature. The CdCommand is currently very simple, it just uses Dir::chdir. Since that doesn’t handle ~, I’ll have to add in a little extra code for it.

Sorry, comments are closed for this article.