On Thu, 25 Jan 2001, Kevin Smith wrote:

> Niklas Backlund wrote:
> >parameter value
> >parameter value
> >long-parameter WHATEVER
> >  data
> >  data
> >WHATEVER
> >parameter value
> >...
> >Is there a neat, "Ruby-way" of pulling this off? Or do I
> >just have to reconsider my choice of file format?
> 
> Hmmm....my first cut (in pseudocode) would be:
> for each line
>   if it's a short parameter
>      process that parameter
>   else
>      memorize the terminator
>      read lines until you hit the terminator
>      process that parameter
>   end
> end

Kevin, one comment on your style. I tend to outline my routines in a
same way, and I guess it's a good thing to do. But if you pay
attention to outlining style, then you have half-baked Ruby code
already.

  1) drop "each", since "for line in stream" works already
  2) use '_' instead of space, and you get method names on the fly
  3) plain English doesn't always give me hint what could be the
     variables, but Ruby like pseudo-code tries to talk to me

I guess I'm approaching the point when it's as fast to write real (but
rough) ruby code instead of using English.

If these tests are along the way it should be

###
class TestParamReader < RUNIT::TestCase
  def test_read2
    pr = ParamReader.new
    pr.read("foo bar")
    assert_equals({"foo" => "bar"}, pr.params)

    pr = ParamReader.new
    pr.read("foo bar\nzak zok")
    assert_equals({"foo" => "bar", "zak" => "zok"}, pr.params)

    pr = ParamReader.new
    pr.read("foo bar\nzak zok\n\n")
    assert_equals({"foo" => "bar", "zak" => "zok"}, pr.params)

    pr = ParamReader.new
    pr.read("foo bar\n bar2\nzak zok\n zok2\n")
    assert_equals({"foo" => "bar\nbar2", "zak" => "zok\nzok2"},
                  pr.params)

    pr = ParamReader.new
    pr.read("foo bar\nzak  zok  \n zok2  \n\n  zok3\nfoo  bar2\n")
    assert_equals({"foo" => "bar\nbar2", 
                   "zak" => "zok  \nzok2  \n zok3"}, 
	pr.params)
  end

end
###

Following snippet passes these tests:

###
class ParamReader
  attr_reader :params
  def initialize
    @params = {}
  end

  def read(file)
    new_param_re = /^[^\s]/
    for line in file
      next if line.strip == ""  # skip empty rows

      value = line

      name, value = line.split(/\s/, 2) if line =~ new_param_re

      if @params.has_key? name
	value = "\n" + value   # if appending, add new lines too
      else
	@params[name] = ""     # if not, initialize hash
      end

      # remove leading space (only one) and trailing new line if any
      @params[name] += value.chomp.sub(/^ /, "") 
    end
  end    

  # If you're willing to live with some constraints (not tested):
  # - requires newer ruby: @params = Hash.new { "" }
  # - doesn't handle empty rows gracefully
  # - doesn't handle the formatting of the beginning of the 
  #   long parameters continuation lines (strip leading space away?)
  def read2(file)
    @params = Hash.new { "" }
    new_param_re = /^[\s]/
    for line in file
      value = line
      name, value = value.split(/\n/) if line =~ new_param_re
      @params[name] += value
    end
  end

end
###

Kevin notes:
> You could extract the entire if/else into a 
> method to keep the main loop simpler. Aside from 
> Ruby's nice line-oriented input methods, I don't 
> see any obvious opportunities to come up with a 
> really cool Ruby-specific solution.

Agreed. I kept everything in one loop and one routine, but it messes
up the code. Also I couldn't see any really nice way of doing this.


    - Aleksi