Reputation: 414
I started to put print-statements throughout my code. So as not to clutter up the output, I did something like:
dputs LEVEL, "string"
where LEVEL
is 0 for errors, 1 for important .. 5 for verbose and is compared to DEBUG_LEVEL
. Now my problem is, that in a statement like:
dputs 5, "#{big_class.inspect}"
the string is always evaluated, also if I set DEBUG_LEVEL
to 1. And this evaluation can take a long time. My favourite solution would be something like:
dputs 5, '#{big_class.inspect}'
and then evaluate the string if desired. But I don't manage to get the string in a form I can evaluate. So the only think I could come up with is:
dputs( 5 ){ "#{big_class.inspect}" }
but this looks just ugly. So how can I evaluate a '#{}' string?
Upvotes: 5
Views: 3085
Reputation: 61
I think it's of no value whatsoever, but I just came up with:
2.3.1 :001 > s = '#{a}'
=> "\#{a}"
2.3.1 :002 > a = 1
=> 1
2.3.1 :003 > instance_eval s.inspect.gsub('\\', '')
=> "1"
2.3.1 :004 > s = 'Hello #{a} and #{a+1}!'
=> "Hello \#{a} and \#{a+1}!"
2.3.1 :005 > instance_eval s.inspect.gsub('\\', '')
=> "Hello 1 and 2!"
Don't use that in production :)
Upvotes: 2
Reputation: 414
OK, obviously I was just too lazy. I thought there must be a more clean way to do this, Ruby being the best programming language and all ;) To evaluate a string like
a = '#{1+1} some text #{big_class.inspect}'
only when needed, I didn't find a better way than going through the string and eval all "#{}" encountered:
str = ""
"#{b}\#{}".scan( /(.*?)(#\{[^\}]*\})/ ){
str += $1
str += eval( $2[2..-2] ).to_s
}
if you're not into clarity, you can get rid of the temporary-variable str:
"#{b}\#{}".scan( /(.*?)(#\{[^\}]*\})/ ).collect{|c|
c[0] + eval( c[1][2..-2] ).to_s
}.join
The String.scan-method goes through every '#{}'-block, as there might be more than one, evaluating it (the 2..-2 cuts out the "#{" and "}") and putting it together with the rest of the string.
For the corner-case of the string not ending with a '#{}'-block, an empty block is added, just to be sure.
But well, after being some years in Ruby, this still feels clunky and C-ish. Perhaps it's time to learn a new language!
Upvotes: 0
Reputation: 96914
You could do this by having dputs
use sprintf
(via %
). That way it can decide not to build the interpolated string unless it knows it's going to print it:
def dputs(level, format_str, *vars)
puts(format_str % vars) if level <= LEVEL
end
LEVEL = 5
name = 'Andrew'
dputs 5, 'hello %s', name
#=> hello Andrew
Or, as you suggest, you can pass a block which would defer the interpolation till the block actually runs:
def dputs(level, &string)
raise ArgumentError.new('block required') unless block_given?
puts string.call if level <= LEVEL
end
Upvotes: 6
Reputation: 346
I don't think you can dodge the ugly there. The interpolation happens before the call to dputs unless you put it inside a block, which postpones it until dputs evaluates it. I don't know where dputs comes from, so I'm not sure what its semantics are, but my guess is the block would get you the lazy evaluation you want. Not pretty, but it does the job.
Upvotes: 1