From: ptkwt@...1.aracnet.com (Phil Tomson) Date: 2001-10-29T11:00:25+09:00 Subject: [ruby-talk:23703] Simple templating system (cool) In article , David Alan Black wrote: >Hello -- > >On Sun, 28 Oct 2001, Phil Tomson wrote: > >> So, in order to get it to work you would have to pass a binding in to the >> interpolate method: >> >> class String >> def interpolate(bind) >> eval ("\"#{self.to_s}\"",bind) >> end >> end >> var = 99 >> bstr = 'now try this: #{var}' >> p bstr.interpolate( binding()) >> >> Now it will work, but it's a bit nasty passing in the binding, couldn't > >I respectfully submit that this is, to a large extent, a solution in >search of a problem. I could be wrong... but, especially if it's >going to involve the, to me, very awkward syntax of passing in >variables by name and/or bindings, any time it's used it's probably >just a sign that something in the program needs to be reorganized. (I >know the same can be argued for the use of "eval" in general :-) Here's what I'm trying to do: In order to test a compiler for a different language (actually a hardware description language, so it's not just a compiler but also a synthesis tool) by generating source code files from template files. I've got some text files which have embedded ruby code (between #{ and }) which are the template files. Like: #File: vhdl vector(0 to #{i}) = '#{"X"*(i+1)}' (actually, real template files would be much longer :) I want Ruby to do as much work for me as possible, that's why I'm taking advantage of the '#{}' interpolator. Here's the code I came up with to do this: #template.rb class String def interpolate(bind) eval (self.inspect,bind) end end class TemplateFile attr_reader :fileString def initialize(file,bind) @stateArray = Array.new @stateArray << true #default read state @fileString = "" @bind = bind File.foreach(file) {|line| case line when /^#>IF/ #check condition condition = line.gsub(/#>IF/,"").strip puts "condition is: "+condition if $DEBUG @stateArray << condition.interpolate(@bind) puts "#{line} : #{@stateArray.to_s}" if $DEBUG when /^#>ELSE/ #invert condition @stateArray[-1] = !@stateArray[-1] when /^#>END/ @stateArray.pop when /^#>INCLUDE/ fields = line.split file = fields[1].strip puts "file to include is: #{file}" @fileString << include(fields[1].strip) if @stateArray.last else @fileString << line if @stateArray.last end } end def include(file) tf = TemplateFile.new(file,@bind) tf.fileString end def interpolate(b=nil) if b @fileString.interpolate(b) else @fileString.interpolate(@bind) end end end if $0 == __FILE__ var = "string" tf = TemplateFile.new("vhdl",binding) #test different vector lengths: 5.times do |i| puts "for #{i}:" puts tf.interpolate(binding) end end #end Template.rb Here's the result: for 0: vector(0 to 0) = 'X' for 1: vector(0 to 1) = 'XX' for 2: vector(0 to 2) = 'XXX' for 3: vector(0 to 3) = 'XXXX' for 4: vector(0 to 4) = 'XXXXX' These each could be written into seperate files and then the compiler to be tested can be run on them. BTW: the code in template.rb above also can handle some 'directives': #>IF #{condition} #>ELSE #>END #>INCLUDE - reads in a specified file and includes it. I haven't tested this exhaustively yet, but so far I think this will work pretty nicely for what I need to do. I had implemented the same thing in Perl a couple of years ago and it took about 3-4 times as much code to do the same thing. Some people coming from the Perl side complain about having to enclose variables in strings with '#{..}' (I did at first too), but this actually allowed me to implement this templating class so concisely (let Ruby do all of the work). Phil