(post in english for LiquidDevelopment compat :) )

Chiaroscuro is having fun with ruby metaprogramming recipes and so will I.

My version does not contain cool tricks such as allow_method_chaining but I think it is fun anyway, so I here it is.

I’ll start straight with some numeric stuff:

class Integer
  names  = %w[  one two three four five six seven eight nine
                      ten eleven twelve thirteen fourteen fifteen
                      sixteen seventeen eighteen nineteen ]
  NAMES=Hash[*names.zip((1..15).to_a).flatten]
end

Yeah, I know I could hack the 11..19 group with a map, but I think it is not that nice.

Now, replace the ugly $global with a constant, and make @ingredients readable:

class Recipe
  KNOWN={}
  attr :ingredients

The proposal is to make something like this work:

Recipe.new \"breakfast\" {
 one of cappuccino
 two of brioche
}

so we have to handle one/two etc as methods. Obviously we even have to handle of and then get the rest with method_missing, so the basic code for initialize will need to call instance_eval:

  def  initialize(recipe_name , &block)
    @name  = recipe_name
    @ingredients = {}
    instance_eval &block
    KNOWN[recipe_name]=self
  end

Notice the ultrasimple handling of registered recipes ;)

Now, we can define of as a simple method that returns it’s argument:

  def of(thing)
    thing
  end

and handle ingredients via method_missing:

  # XXX: we could raise if args.size > 0
  def method_missing(sym,*args)
    sym.to_s
  end

Finally, the code for the numbers/methods may be something like:

  for name in Integer::NAMES.keys
    eval %{
    def #{name}(*values)
      first= values.shift #coffee
      values.push \"#{name}\" # [ \"macchiato\", \"one\"]
      @ingredients[first]=values
    end
  }
  end

There are two things to say here: first, we need to store the name of the ingredient as the key and put the number as a value in it, together with any optional thing. Second, we can’t use for or each with define_method and we have to fall back on eval.

This happens because if we used a block with it, it would be a closure, and thus the last value assigned to name would be carried on by all the methods.

Mh.. is this perfect?
Not really, as of now this code allows you to write

  Recipe.new(n) do
    one coffee, macchiato
    ten of saccottino
  end

But it will fail miserably with one of coffee, macchiato.

It is quite easy to handle this case if you choose to disallow commands with missing of, by redefining it to take variable arguments (and changing the number-methods accordingly), but to make it work fine in both cases there is the need for ugly checks with and this is left as an exercise for the reader :)

Finally, you have to set $VERBOSE to nil so that the intepreter does not spit out warnings when it tries to interpret foo bar baz, quux, which it recognizes as foo(bar(baz,quux)).

Oh, by the way, I did not convert numbers to integers, since I found it was useless, but you can safely use the Hash that I set up for this.

Thanks for reading till here, now get the full code with my minimal test suite :)

PS
chiaroscuro, ho cancellato un tuo vecchio commento qui sopra per sbaglio, scusami :(