Coopting String interpolation

A

aurelianito

We all know the #{} operator for String interpolation.
It's used like this:

name="Aure"
greeting="hi, #{name}"

and greeting evaluates to "hi, Aure". But I would like to change it.
Is there a pure Ruby way to force "hi, #{name}" to evaluate to "hi,
AURE" (get the uppercase of the original interpolated String).
Something like:

class String
# do some magic here
end

name="Aure"
greeting="hi, #{name}"

and now greeting evaluates to "hi, AURE". Is there a method in String
to override in order to take control of the String interpolation?

Thanks in advance,
Aureliano.
 
P

Peña, Botp

From: aurelianito [mailto:[email protected]] :
# Is there a pure Ruby way to force "hi, #{name}" to evaluate to "hi,
# AURE" (get the uppercase of the original interpolated String).
# Something like:

pols. think simple, then just do it.

interpolation can be any expression..

irb(main):001:0> name=3D"Aure"
=3D> "Aure"
irb(main):002:0> greeting=3D"hi, #{name}"
=3D> "hi, Aure"
irb(main):003:0> greeting=3D"hi, #{name.upcase}"
=3D> "hi, AURE"
irb(main):005:0> greeting=3D"hi, #{"Ms "+name.capitalize}"
=3D> "hi, Ms Aure"
irb(main):006:0>
 
P

Peña, Botp

# irb(main):005:0> greeting=3D"hi, #{"Ms "+name.capitalize}"
# =3D> "hi, Ms Aure"

sorry, was too fast on the last example; should be..

irb(main):006:0> greeting=3D"hi, #{name=3D"Anne";"Ms "+name.capitalize}"
=3D> "hi, Ms Anne"
 
A

aurelianito

# irb(main):005:0> greeting=3D"hi, #{"Ms "+name.capitalize}"
# =3D> "hi, Ms Aure"

sorry, was too fast on the last example; should be..

irb(main):006:0> greeting=3D"hi, #{name=3D"Anne";"Ms "+name.capitalize}"
=3D> "hi, Ms Anne"

I'm trying to do some metaprogramming and I need to apply some
operation to all the strings that are interpolated. But I can't change
the interpolation. In the example, I would like that "hi, #{name}" to
evaluate to "hi, AURE" instead of "hi, Aure". I know that inside the
#{} "operator" I can put any ruby code, so "hi, #{name.capitalize}"
would evaluate to what I want.

But I want it to execute code that I DON'T write there. Ideally, there
should be a method hook or something to change the way the
interpolation works. But I couldn't find it :(. May be a different
example may be more clear. How can I do to write to a file all the
strings generated via interpolations (id est, all the strings that are
generated evaluating the different #{} "operators" in a program)?

Thanks,
Aureliano.
 
N

Nobuyoshi Nakada

Hi,

At Thu, 17 May 2007 13:20:14 +0900,
aurelianito wrote in [ruby-talk:251879]:
class String
# do some magic here
end

name="Aure"
greeting="hi, #{name}"

and now greeting evaluates to "hi, AURE". Is there a method in String
to override in order to take control of the String interpolation?

If the value isn't a String, to_s is called for it to get a
String. Otherwise, the content is interpolated directly.
There is not a hook you want.
 
R

Robert Klemme

I'm trying to do some metaprogramming and I need to apply some
operation to all the strings that are interpolated. But I can't change
the interpolation. In the example, I would like that "hi, #{name}" to
evaluate to "hi, AURE" instead of "hi, Aure". I know that inside the
#{} "operator" I can put any ruby code, so "hi, #{name.capitalize}"
would evaluate to what I want.

But I want it to execute code that I DON'T write there. Ideally, there
should be a method hook or something to change the way the
interpolation works. But I couldn't find it :(. May be a different
example may be more clear. How can I do to write to a file all the
strings generated via interpolations (id est, all the strings that are
generated evaluating the different #{} "operators" in a program)?

Frankly, it has not become clear to me what you are up to. Can you
maybe just state which problem you are trying to solve?

As for a solution to your original example, you can do something like
this - but it's a hack and not very reliable:

class Env
def initialize(vals)
@vals = vals
end

def process(&b)
instance_eval(&b)
end

def method_missing(s,*a,&b)
super unless a.empty?
@vals[s.to_sym].upcase
end
end

irb(main):015:0> e=Env.new:)name=>'foo')
=> #<Env:0x7ff6d388 @vals={:name=>"foo"}>
irb(main):016:0> e.process { "bar #{name}" }
=> "bar FOO"
irb(main):017:0>

Kind regards

robert
 
A

Aureliano Calvo

I'm trying to do some metaprogramming and I need to apply some
Frankly, it has not become clear to me what you are up to. Can you
maybe just state which problem you are trying to solve?

NDAs are a bitch. I can't state the exact problem (bah! I can, but I
could be fired, in a trial, and I also might inhibit my coworkers to
publish a paper with their findings). I'm working in a research
project and I need to do some "clever stuff" (that's the thing I can't
disclose) to all the Strings. This thing is "different" depending on
how strings are composed. All the ways I know for string composition
but string interpolation (<<, +, concat, gsub, etc.) can be overridden
redefining methods in the string class. I'm looking for a way to
intercept the string expansion to do my thing. Does ruby internally
call some overridable method to compose the strings used in a
interpolation?

Now I see that if I can transform all the <"a#{expression}c"> in <"a"
+ (expression) + "c"> in a ruby source code string, I could change
expression with "a#{expression}c" in "a" + clever_stuff(expression) +
"c" and use my changed + method in Strings. Is there an easy way to
manipulate ruby code in ruby to do this?

Thanks for your time,
Aureliano.
 
M

Mariusz Pękala

--XF85m9dhOBO43t/C
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

Now I see that if I can transform all the <"a#{expression}c"> in <"a"
+ (expression) + "c"> in a ruby source code string, I could change
expression with "a#{expression}c" in "a" + clever_stuff(expression) +
"c" and use my changed + method in Strings. Is there an easy way to
manipulate ruby code in ruby to do this?

Maybe this will be of some use:

irb(main):005:0> "asd_#{raise caller.to_s}_"
RuntimeError: /usr/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'/usr/li=
b/ruby/1.8/irb/workspace.rb:52
from (irb):5

--=20
No virus found in this outgoing message.
Checked by 'grep -i virus $MESSAGE'
Trust me.

--XF85m9dhOBO43t/C
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7-ecc0.1.6 (GNU/Linux)

iD8DBQFGTGkXsnU0scoWZKARAp84AKCzKh5oifiGgcMRjj019v+dmLNQAgCeNlnx
irZBHeDZuZ9P9b7LLN5ggx0=
=O+DC
-----END PGP SIGNATURE-----

--XF85m9dhOBO43t/C--
 
M

Mariusz Pękala

--Pk6IbRAofICFmK5e
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
=20
Maybe this will be of some use:
=20
irb(main):005:0> "asd_#{raise caller.to_s}_"
RuntimeError: /usr/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'/usr/= lib/ruby/1.8/irb/workspace.rb:52
from (irb):5

Oh, no - it's stupid. Sorry ;-)


--=20
No virus found in this outgoing message.
Checked by 'grep -i virus $MESSAGE'
Trust me.

--Pk6IbRAofICFmK5e
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7-ecc0.1.6 (GNU/Linux)

iD8DBQFGTGvTsnU0scoWZKARAnSgAKDKXs1ESeBq3NHvHsVV7LmCGr2hdgCgqos6
+ZCI9DEBWfQE3n+wrkJTJ+o=
=qdZG
-----END PGP SIGNATURE-----

--Pk6IbRAofICFmK5e--
 
P

Phrogz

Now I see that if I can transform all the <"a#{expression}c"> in <"a"
+ (expression) + "c"> in a ruby source code string, I could change
expression with "a#{expression}c" in "a" + clever_stuff(expression) +
"c" and use my changed + method in Strings. Is there an easy way to
manipulate ruby code in ruby to do this?

Can you make life easier by changing to use ERB?
You cannot change Ruby string interpolation in Ruby. You can hack the
source code if you really need to. You can manually change your
strings, or you can (as below) use gsub to change the string before
evaluating.

def clever_stuff( str )
str.upcase
end

require 'erb'
def clever_template( str )
ERB.new( str.gsub( /<%=(.+?)%>/, '<
%=clever_stuff(\1)%>' ) ).result
end

name1, name2 = %w|Gavin Kistner|

str1 = "Hello there, <%=name1%> <%=name2%>! Nice to meet you!"
puts clever_template( str1 )
#=> Hello there, GAVIN KISTNER! Nice to meet you!

str2 = "Hello there, <%=name1 + ' ' + name2%>! Nice to meet you!"
puts clever_template( str2 )
#=> Hello there, GAVIN KISTNER! Nice to meet you!
 
P

Pit Capitain

Aureliano said:
Now I see that if I can transform all the <"a#{expression}c"> in <"a"
+ (expression) + "c"> in a ruby source code string, I could change
expression with "a#{expression}c" in "a" + clever_stuff(expression) +
"c" and use my changed + method in Strings. Is there an easy way to
manipulate ruby code in ruby to do this?

Aureliano, is it right that you don't need to manipulate the code at
runtime, and you can work with the source code instead? This can be more
or less hard to do, depending on the complexity of the expressions which
are allowed inside the strings. For simple expressions a regex solution
might be enough. Otherwise you'd need to use one of the Ruby parser
libraries.

If this is for a research project, then maybe you could patch the Ruby
interpreter to add the hook you need. This shouldn't be too difficult.

Regards,
Pit
 
D

Daniel DeLorme

Aureliano said:
Now I see that if I can transform all the <"a#{expression}c"> in <"a"
+ (expression) + "c"> in a ruby source code string, I could change
expression with "a#{expression}c" in "a" + clever_stuff(expression) +
"c" and use my changed + method in Strings. Is there an easy way to
manipulate ruby code in ruby to do this?

Maybe you can take a look at ParseTree to decompose a dynamic string
into its components:

require "rubygems"
require "parse_tree"
class Foo
def foo
"interpolation: #{42}!"
end
end
ParseTree.new.parse_tree(Foo)
=> [[:class, :Foo, [:const, :Object], [:defn, :foo, [:scope, [:block,
[:args], [:dstr, "interpolation: ", [:lit, 42], [:str, "!"]]]]]]]

Daniel
 
A

Aureliano Calvo

Now I see that if I can transform all the said:
+ (expression) + "c"> in a ruby source code string, I could change
expression with "a#{expression}c" in "a" + clever_stuff(expression) +
"c" and use my changed + method in Strings. Is there an easy way to
manipulate ruby code in ruby to do this?

Maybe you can take a look at ParseTree to decompose a dynamic string
into its components:

require "rubygems"
require "parse_tree"
class Foo
def foo
"interpolation: #{42}!"
end
end
ParseTree.new.parse_tree(Foo)
=> [[:class, :Foo, [:const, :Object], [:defn, :foo, [:scope, [:block,
[:args], [:dstr, "interpolation: ", [:lit, 42], [:str, "!"]]]]]]]

Thanks!
I'll take a look at it.
 
G

Gary Wright

We all know the #{} operator for String interpolation.
It's used like this:

name="Aure"
greeting="hi, #{name}"

and greeting evaluates to "hi, Aure". But I would like to change it.
Is there a pure Ruby way to force "hi, #{name}" to evaluate to "hi,
AURE" (get the uppercase of the original interpolated String).

I just released my Jig package that might be helpful to you.

require 'rubygems'
require 'jig'

# Create a named 'gap' that upcases everything
upcase = Jig::Gap.new:)name) { |x| x.upcase }

# Create a jig consisting of a string, and the customize 'upcasing' gap.
greeting = Jig["hi, ", upcase]

# Fill the gap with data to generate a new jig.
# puts calls Jig#to_s to convert the new jig to a string

puts greeting % { :name => "Aure" } # => hi, AURE
puts greeting % { :name => "Gary" } # => hi, GARY
 
R

Robert Klemme

NDAs are a bitch. I can't state the exact problem (bah! I can, but I
could be fired, in a trial, and I also might inhibit my coworkers to
publish a paper with their findings).

You can tell us - we won't tell anybody else. ;-)
I'm working in a research
project and I need to do some "clever stuff" (that's the thing I can't
disclose) to all the Strings. This thing is "different" depending on
how strings are composed. All the ways I know for string composition
but string interpolation (<<, +, concat, gsub, etc.) can be overridden
redefining methods in the string class. I'm looking for a way to
intercept the string expansion to do my thing. Does ruby internally
call some overridable method to compose the strings used in a
interpolation?

Likely but also likely not accessible to pure Ruby code. But in your
case (research project) it might be ok to hack the interpreter if you
need to catch all string interpolations. Did you look into this yet?
Now I see that if I can transform all the <"a#{expression}c"> in <"a"
+ (expression) + "c"> in a ruby source code string, I could change
expression with "a#{expression}c" in "a" + clever_stuff(expression) +
"c" and use my changed + method in Strings. Is there an easy way to
manipulate ruby code in ruby to do this?

You could even change all "a#{b}c" into "a#{your_magic b}c" which is
considerably easier to do than a complete parse. But when I think about
it it may break for some b's. Whether that's ok or not, I don't know.

Kind regards

robert
 
B

Brian Candler

Likely but also likely not accessible to pure Ruby code. But in your
case (research project) it might be ok to hack the interpreter if you
need to catch all string interpolations. Did you look into this yet?

Here's a simpler idea. Given that string interpolation calls to_s, you can
use this as your hook if you wrap your objects in a proxy object. You need
to ensure that operations like '+' also return an instance of this proxy
object, so you can do all the operations you want whilst keeping the string
wrapped. Then the only case that to_s is called is when the interpolation
takes place, which gives you the hook you're looking for (as long as you're
not calling to_s in any other context)

Example:

class WrapString
def initialize(str)
@str = str
end

def str
@str
end
protected :str

def +(other)
self.class.new(str + (other.str rescue other.to_s))
end
# rinse and repeat

# Here is your interpolation hook:
def to_s
str.to_s * 2
end
end

a = WrapString.new("abc")
b = WrapString.new("def")

puts "Answer is #{a + b}"

a = WrapString.new("abc")
b = "def"

puts "Answer is #{a + b}"

The result of a + b is <WrapString @str="abcdef">, and then the
interpolation calls to_s which in this case just doubles it to
"abcdefabcdef"

There's probably a neater implementation of the above using method_missing
or a delegation pattern rather than enumerating all the methods of String,
but you get the idea.

Just a thought?

Brian.
 
R

Robert Klemme

Here's a simpler idea. Given that string interpolation calls to_s, you can
use this as your hook if you wrap your objects in a proxy object. You need
to ensure that operations like '+' also return an instance of this proxy
object, so you can do all the operations you want whilst keeping the string
wrapped. Then the only case that to_s is called is when the interpolation
takes place, which gives you the hook you're looking for (as long as you're
not calling to_s in any other context)

Example:

class WrapString
def initialize(str)
@str = str
end

def str
@str
end
protected :str

def +(other)
self.class.new(str + (other.str rescue other.to_s))
end
# rinse and repeat

# Here is your interpolation hook:
def to_s
str.to_s * 2
end
end

a = WrapString.new("abc")
b = WrapString.new("def")

puts "Answer is #{a + b}"

a = WrapString.new("abc")
b = "def"

puts "Answer is #{a + b}"

The result of a + b is <WrapString @str="abcdef">, and then the
interpolation calls to_s which in this case just doubles it to
"abcdefabcdef"

There's probably a neater implementation of the above using method_missing
or a delegation pattern rather than enumerating all the methods of String,
but you get the idea.

Just a thought?

Not bad! Question is: what does it do to the rest of the code? Just
think about using obj.to_s for Hash keys. Also, I am not sure what
happens in core code if to_s returns something other than a string:

15:51:07 [~]: irb
irb(main):001:0> o=Object.new
=> #<Object:0x7ef9e3f8>
irb(main):002:0> puts o
#<Object:0x7ef9e3f8>
=> nil
irb(main):003:0> def o.to_s() "foo" end
=> nil
irb(main):004:0> puts o
foo
=> nil
irb(main):005:0> def o.to_s() 123 end
=> nil
irb(main):006:0> puts o
#<Object:0x7ef9e3f8>
=> nil

Seems to be there could be wired effects...

Kind regards

robert
 
A

Aureliano Calvo

I'm working in a research
Likely but also likely not accessible to pure Ruby code. But in your
case (research project) it might be ok to hack the interpreter if you
need to catch all string interpolations. Did you look into this yet?

I'll see if I can get away with it. I'll provide a patch to have an
interpolation hook if I manage to implement it.

Thanks,
Aureliano.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,534
Members
45,008
Latest member
Rahul737

Latest Threads

Top