String interpolation when not using a string literal - ruby

I have a Ruby script that used string interpolation to build error messages.
p "#{vName} is not a defined variable" => 'xxx is not a defined variable'
Another programmer came through and attempted to externalize the string literals to a separate configuration file. Of course, he doesn't get the substitution.
p err_string_from_config => '#{vName} is not a defined variable'
I've looked around, but couldn't come up with anything better than converting to sprintf strings and using printf.
Does anybody know how to get the #{} substitution to work on strings that are not double quote literals within the Ruby script?

Actually Ruby has functionality very similar to John's Python example:
$ irb
>> greeting = 'hello %s, my name is %s!'
>> interpolated = greeting % ['Mike', 'John']
=> "hello Mike, my name is John!"
>>
This is also useful if your argument is an array constant. If you must use #{} style interpolation you could use eval:
>> greeting = 'hi #{name}' # notice name is not defined yet
>> name = "mike"
>> eval '"' + greeting + '"'
The eval approach is going to be much slower than using % style interpolation, so it's a trade-off.

I suggest that you look at Liquid templating language which provides more powerful features (e.g. you can reference parameters by name).
Previous example would look like:
greeting = Liquid::Template.parse("hello {{your_name}}, my name is {{my_name}}!")
interpolated = greeting.render('your_name' => 'Mike', 'my_name' => 'John')
# => "hello Mike, my name is John!"

Here's how I do it, just for the record. A bit clearer imo.
gief = '#{later} please'
later = "later"
puts eval(%Q["#{gief}"])
# => later please
But honestly, that is such a hack. Unless you have a really good reason to use a string, use a proc instead. I always try to use plain ruby instead of evaling strings.
gief = Proc.new {|l| "#{l} please" }
later = "later"
puts gief.call(later)
# => later please

Related

Ruby single and double quotes

I've recently been coding in Ruby and have come from Python, where single and double quotes made no difference to how the code worked as far as I know.
I moved to Ruby to see how it worked, and to investigate the similarities between Ruby and Python.
I was using single-quoted strings once and noticed this:
hello = 'hello'
x = '#{hello} world!'
puts x
It returned '#{hello} world!' rather than 'hello world!'.
After noticing this I tried double quotes and the problem was fixed. Now I'm not sure why that is.
Do single and double quotes change this or is it because of my editor (Sublime text 3)? I'm also using Ruby version 2.0 if it works differently in previous versions.
In Ruby, double quotes are interpolated, meaning the code in #{} is evaluated as Ruby. Single quotes are treated as literals (meaning the code isn't evaluated).
var = "hello"
"#{var} world" #=> "hello world"
'#{var} world' #=> "#{var} world"
For some extra-special magic, Ruby also offers another way to create strings:
%Q() # behaves like double quotes
%q() # behaves like single quotes
For example:
%Q(#{var} world) #=> "hello world"
%q(#{var} world) #=> "#{var} world"
You should read the Literals section of the official Ruby documentation.
It is very concise, so you need to read carefully. But it explains the difference between double-quoted and single-quoted strings, and how they are equivalent to %Q/.../ and %q/.../ respectively.
If you enclose Ruby string in single qoutes, you can't use interpolation. That's how Ruby works.
Single-quoted strings don't process escape sequence \ and they don't do string interpolation.
For a better understanding, take a look at String concatenation vs. interpolation
To answer your question, you have to use "" when you want to do string interpolation:
name = 'world'
puts "Hello #{name}" # => "Hello world"
Using escape sequence:
puts 'Hello\nworld' # => "Hello\nworld"
puts "Hello\nworld" # => "Hello
world"
Ruby supports single-quoted string, for many uses like as follow:
>> 'foo'
=> "foo"
>> 'foo' + 'bar'
=> "foobar"
In above example, those two types of strings are identical. We can use double quote in place of single quote and we will get same output like above example.
As you face problem, while using interpolation in single quoted string because Ruby do not interpolate into single-quoted string. I am taking one example for more understanding:
>> '#{foo} bar'
=> "\#{foo} bar"
Here you can see that return values using double-quoted strings, which requires backslash to escape special characters such as #.
Single quoted string often useful because they are truly literal.
In the string interpolation concept, the essential difference between using single or double quotes is that double quotes allow for escape sequences while single quotes do not.
Let's take an example:
name = "Mike"
puts "Hello #{name} \n How are you?"
The above ruby code with string interpolation will interpolate the variable called name which is written inside brackets with its original value which is Mike. And it will also print the string How are you? in a separate line since we already placed an escape sequence there.
Output:
Hello Mike
How are you?
If you do the same with single quotes, it will treat the entire string as a text and it will print as it is including the escape sequence as well.
name = Mike'
puts 'Hello #{name} \n How are you'?
Output:
Hello #{name} \n How are you?

Why does capturing named groups in Ruby result in "undefined local variable or method" errors?

I am having trouble with named captures in regular expressions in Ruby 2.0. I have a string variable and an interpolated regular expression:
str = "hello world"
re = /\w+/
/(?<greeting>#{re})/ =~ str
greeting
It raises the following exception:
prova.rb:4:in <main>': undefined local variable or methodgreeting' for main:Object (NameError)
shell returned 1
However, the interpolated expression works without named captures. For example:
/(#{re})/ =~ str
$1
# => "hello"
Named Captures Must Use Literals
You are encountering some limitations of Ruby's regular expression library. The Regexp#=~ method limits named captures as follows:
The assignment does not occur if the regexp is not a literal.
A regexp interpolation, #{}, also disables the assignment.
The assignment does not occur if the regexp is placed on the right hand side.
You'll need to decide whether you want named captures or interpolation in your regular expressions. You currently cannot have both.
Assign the result of #match; this will be accessible as a hash that allows you to look up your named capture groups:
> matches = "hello world".match(/(?<greeting>\w+)/)
=> #<MatchData "hello" greeting:"hello">
> matches[:greeting]
=> "hello"
Alternately, give #match a block, which will receive the match results:
> "hello world".match(/(?<greeting>\w+)/) {|matches| matches[:greeting] }
=> "hello"
As an addendum to both answers in order to make it crystal clear:
str = "hello world"
# => "hello world"
re = /\w+/
# => /\w+/
re2 = /(?<greeting>#{re})/
# => /(?<greeting>(?-mix:\w+))/
md = re2.match str
# => #<MatchData "hello" greeting:"hello">
md[:greeting]
# => "hello"
Interpolation is fine with named captures, just use the MatchData object, most easily returned via match.

resolve #{var} in string

I have loaded a string with #{variable} references in it. How would I resolve those variables in the string like puts does?
name="jim"
str="Hi #{name}"
puts str
Instead of puts, I would like to have the result available to pass as a parameter or save into a variable.
you could eval it
name = "Patrick"
s = 'hello, #{name}'
s # => "hello, \#{name}"
# wrap the string in double quotes, making it a valid interpolatable ruby string
eval "\"#{s}\"" # => "hello, Patrick"
puts doesn't resolve the variables. The Ruby parser does when it creates the string. if you passed str to any other method, it would be the same as passing 'Hi jim', since the interpolation is already done.
String has a format option that appears as %. It can be used to pass arguments into a predefined string much like interpolation does.
message = "Hello, %s"
for_patrick = message % "Patrick" #=> "Hello, Patrick"
for_jessie = message % "Jessie" #=> "Hello, Jessie"
messages = "Hello, %s and %s"
for_p_and_j = messages % ["Patrick", "Jessie"] #=> "Hello, Patrick and Jessie"
It may not look "Rubyish" but I believe it is the functionality you are looking for.
So, if you have a string coming in from somewhere that contains these placeholders, you can then pass in values as arguments as so:
method_that_gets_hello_message % "Patrick"
This will also allow you to only accept values you are expecting.
message = "I can count to %d"
message % "eleven" #=> ArgumentError: invalid value for Integer()
There's a list on Wikipedia for possible placeholders for printf() that should also work in Ruby.
The eval seems to be the only solution for this particular task. But we can avoid this dirty-unsafe-dishonourable eval if we modify the task a bit: we can resolve not local, but instance variable without eval using instance_variable_get:
#name = "Patrick"
#id = 2 # Test that number is ok
#a_b = "oooo" # Test that our regex can eat underscores
s = 'hello, #{name} !!#{id} ??#{a_b}'
s.gsub(/#\{(\w+)\}/) { instance_variable_get '#'+$1 }
=> "hello, Patrick !!2 ??oooo"
In this case you even can use any other characters instead of #{} (for example, %name% etc), by only modifying the regex a bit.
But of course, all this smells.
It sounds like you want the basis for a template system, which Ruby does easily if you use String's gsub or sub methods.
replacements = { '%greeting%' => 'Hello', '%name%' => 'Jim' }
pattern = Regexp.union(replacements.keys)
'%greeting% %name%!'.gsub(pattern, replacements)
=> "Hello Jim!"
You could just as easily define the key as:
replacements = { '#{name}' => 'Jim' }
and use Ruby's normal string interpolation #{...} but I'd recommend not reusing that. Instead use something unique.
The advantage to this is the target => replacement map can easily be put into a YAML file, or a database table, and then you can swap them out with other languages, or different user information. The sky is the limit.
The benefit to this also, is there is no evaluation involved, it's only string substitution. With a bit of creative use you can actually implement macros:
macros = { '%salutation%' => '%greeting% %name%' }
replacements = { '%greeting%' => 'Hello', '%name%' => 'Jim' }
macro_pattern, replacement_pattern = [macros, replacements].map{ |h| Regexp.union(h.keys) }
'%salutation%!'.gsub(macro_pattern, macros).gsub(replacement_pattern, replacements)
=> "Hello Jim!"

Replace single quote with backslash single quote

I have a very large string that needs to escape all the single quotes in it, so I can feed it to JavaScript without upsetting it.
I have no control over the external string, so I can't change the source data.
Example:
Cote d'Ivoir -> Cote d\'Ivoir
(the actual string is very long and contains many single quotes)
I'm trying to this by using gsub on the string, but can't get this to work:
a = "Cote d'Ivoir"
a.gsub("'", "\\\'")
but this gives me:
=> "Cote dIvoirIvoir"
I also tried:
a.gsub("'", 92.chr + 39.chr)
but got the same result; I know it's something to do with regular expressions, but I never get those.
The %q delimiters come in handy here:
# %q(a string) is equivalent to a single-quoted string
puts "Cote d'Ivoir".gsub("'", %q(\\\')) #=> Cote d\'Ivoir
The problem is that \' in a gsub replacement means "part of the string after the match".
You're probably best to use either the block syntax:
a = "Cote d'Ivoir"
a.gsub(/'/) {|s| "\\'"}
# => "Cote d\\'Ivoir"
or the Hash syntax:
a.gsub(/'/, {"'" => "\\'"})
There's also the hacky workaround:
a.gsub(/'/, '\#').gsub(/#/, "'")
# prepare a text file containing [ abcd\'efg ]
require "pathname"
backslashed_text = Pathname("/path/to/the/text/file.txt").readlines.first.strip
# puts backslashed_text => abcd\'efg
unslashed_text = "abcd'efg"
unslashed_text.gsub("'", Regexp.escape(%q|\'|)) == backslashed_text # true
# puts unslashed_text.gsub("'", Regexp.escape(%q|\'|)) => abcd\'efg

How does Ruby's replace work?

I'm looking at ruby's replace: http://www.ruby-doc.org/core/classes/String.html#M001144
It doesn't seem to make sense to me, you call replace and it replaces the entire string.
I was expecting:
replace(old_value, new_value)
Is what I am looking for gsub then?
replace seems to be different than in most other languages.
I agree that replace is generally used as some sort of pattern replace in other languages, but Ruby is different :)
Yes, you are thinking of gsub:
ruby-1.9.2-p136 :001 > "Hello World!".gsub("World", "Earth")
=> "Hello Earth!"
One thing to note is that String#replace may seem pointeless, however it does remove 'taintediness". You can read more up on tained objects here.
I suppose the reason you feel that replace does not make sense is because there is assigment operator = (not much relevant to gsub).
The important point is that String instances are mutable objects. By using replace, you can change the content of the string while retaining its identity as an object. Compare:
a = 'Hello' # => 'Hello'
a.object_id # => 84793190
a.replace('World') # => 'World'
a.object_id # => 84793190
a = 'World' # => 'World'
a.object_id # => 84768100
See that replace has not changed the string object's id, whereas simple assignment did change it. This difference has some consequences. For example, suppose you assigned some instance variables to the string instance. By replace, that information will be retained, but if you assign the same variable simply to a different string, all that information is gone.
Yes, it is gsub and it is taken from awk syntax. I guess replace stands for the internal representation of the string, since, according to documentation, tainted-ness is removed too.

Resources