ruby: workarounds for nested string interpolation - ruby

In the string
"#{x ? (x.to_s + ' is ') : ''}ok", Rubocop's Style/StringConcatenation suggests avoiding the +.
But that requires a nested string interpolation
"#{x ? '#{x.to_s} is ' : ''}ok)",
which at least in Ruby 2.7 is not expanded: #{x.to_s} is treated like any other literal.
Is the + version alright because it's on the fringes of what a style guide could cover, or must one introduce a temporary variable?
tmp = x ? '#{x.to_s} is ' : ''
"#{tmp}ok"
Context: the string is sent to a logfile. ok is actually a long list of details. x is worth logging, but only when it exists.

Yes, a variable will make this more readable (imo):
prefix = "#{x} is " if x
"#{prefix}ok"
(this relies on the fact that nil#to_s == '')

Given that "ok" is actually:(according to the comments)
"...a long string that has even more interpolations. Duplicating that string isn't DRY".
I would go with
ok = generate_some_long_string()
ok.prepend("#{x} is ") if x
ok
This does mutate ok but based on my understanding of the question this may actually be desirable.
Nesting Interpolation
As an aside and I would not recommend it (because it is difficult to read) but nesting interpolation is completely valid ruby e.g.
x="val"
"#{x ? "#{x} is " : ""}ok"
#=> "val is ok"
This works because what is inside the interpolation closure is treated like any other ruby code. The inner double quotes open and close a new String rather than closing the first and opening another because the interpolation closure is waiting for a closing curly brace. You could technically do this at any depth.
"#{x ? "#{"the #{y = x}ue of"} #{x} is " : ""}#{y.inspect}"
#=> "the value of val is \"val\""

Related

Regexp.escape adds weird escapes to a plain space

I stumbled over this problem using the following simplified example:
line = searchstring.dup
line.gsub!(Regexp.escape(searchstring)) { '' }
My understanding was, that for every String stored in searchstring, the gsub! would cause that line is afterwards empty. Indeed, this is the case for many strings, but not for this case:
searchstring = "D "
line = searchstring.dup
line.gsub!(Regexp.escape(searchstring)) { '' }
p line
It turns out, that line is printed as "D " afterwards, i.e. no replacement had been performed.
This happens to any searchstring containing a space. Indeed, if I do a
p(Regexp.escape(searchstring))
for my example, I see "D\\ " being printed, while I would expect to get "D " instead. Is this a bug in the Ruby core library, or did I misuse the escape function?
Some background: In my concrete application, where this simplified example is derived from, I just want to do a literal string replacement inside a long string, in the following way:
REPLACEMENTS.each do
|from, to|
line.chomp!
line.gsub!(Regexp.escape(from)) { to }
end
. I'm using Regexp.escape just as a safety measure in the case that the string being replaced contains some regex metacharacter.
I'm using the Cygwin port of MRI Ruby 2.6.4.
line.gsub!(Regexp.escape(searchstring)) { '' }
My understanding was, that for every String stored in searchstring, the gsub! would cause that line is afterwards empty.
Your understanding is incorrect. The guarantee in the docs is
For any string, Regexp.new(Regexp.escape(str))=~str will be true.
This does hold for your example
Regexp.new(Regexp.escape("D "))=~"D " # => 0
therefore this is what your code should look like
line.gsub!(Regexp.new(Regexp.escape(searchstring))) { '' }
As for why this is the case, there used to be a bug where Regex.escape would incorrectly handle space characters:
# in Ruby 1.8.4
Regex.escape("D ") # => "D\\s"
My guess is they tried to keep the fix as simple as possible by replacing 's' with ' '. Technically this does add an unnecessary escape character but, again, that does not break the intended use of the method.
This happens to any searchstring containing a space. Indeed, if I do a
p(Regexp.escape(searchstring))
for my example, I see "D\\ " being printed, while I would expect to get "D " instead. Is this a bug in the Ruby core library, or did I misuse the escape function?
This looks to be a bug. In my opinion, whitespace is not a Regexp meta character, there is no need to escape it.
Some background: In my concrete application, where this simplified example is derived from, I just want to do a literal string replacement inside a long string […]
If you want to do literal string replacement, then don't use a Regexp. Just use a literal string:
line.gsub!(from, to)

Why is `?something` invalid?

?. is a string literal:
?. #=> "."
However, I failed to declare a variable with a name like that:
?some_var = 100 #=> Error
How is?something invalid when ?. is valid?
? cannot describe any string literal; it is valid only for a single character.
Even if ?something were a valid string literal (counter to fact),
?something = ...
will be assignment to a string, which does not make sense. You cannot assign a value to a string.
?a is the same as "a". So it is a value, which belongs on the right hand side of an assignment, not the left hand side. It is not a variable name.
The Syntax exists as a relic from Ruby <=1.9, where it was equivalent to "a".bytes[0] and ?d could be used to shave off one character of code golf. I haven't seen any legitimate use otherwise.

Escaping single and double quotes in a string in ruby?

How can I escape single and double quotes in a string?
I want to escape single and double quotes together. I know how to pass them separately but don't know how to pass both of them.
e.g: str = "ruby 'on rails" " = ruby 'on rails"
My preferred way is to not worry about escaping and instead use %q, which behaves like a single-quote string (no interpolation or character escaping), or %Q for double quoted string behavior:
str = %q[ruby 'on rails" ] # like single-quoting
str2 = %Q[quoting with #{str}] # like double-quoting: will insert variable
See https://docs.ruby-lang.org/en/trunk/syntax/literals_rdoc.html#label-Strings and search for % strings.
Use backslash to escape characters
str = "ruby \'on rails\" "
Here is a complete list:
From http://learnrubythehardway.org/book/ex10.html
You can use Q strings which allow you to use any delimiter you like:
str = %Q|ruby 'on rails" " = ruby 'on rails|
>> str = "ruby 'on rails\" \" = ruby 'on rails"
=> "ruby 'on rails" " = ruby 'on rails"
I would go with a heredoc if I'm starting to have to worry about escaping. It will take care of it for you:
string = <<MARKER
I don't have to "worry" about escaping!!'"!!
MARKER
MARKER delineates the start/end of the string. start string on the next line after opening the heredoc, then end the string by using the delineator again on it's own line.
This does all the escaping needed and converts to a double quoted string:
string
=> "I don't have to \"worry\" about escaping!!'\"!!\n"
I would use just:
str = %(ruby 'on rails ")
Because just % stands for double quotes(or %Q) and allows interpolation of variables on the string.
Here is an example of how to use %Q[] in a more complex scenario:
%Q[
<meta property="og:title" content="#{#title}" />
<meta property="og:description" content="#{#fullname}'s profile. #{#fullname}'s location, ranking, outcomes, and more." />
].html_safe
One caveat:
Using %Q[] and %q[] for string comparisons is not intuitively safe.
For example, if you load something meant to signify something empty, like "" or '', you need to use the actual escape sequences. For example, let's say qvar equals "" instead of any empty string.
This will evaluate to false
if qvar == "%Q[]"
As will this,
if qvar == %Q[]
While this will evaluate to true
if qvar == "\"\""
I ran into this issue when sending command-line vars from a different stack to my ruby script. Only Gabriel Augusto's answer worked for me.

gsub! On an argument doesn't work

I am making a function that turns the first argument into a PHP var (useless, I know), and set it equal to the second argument. I'm trying to gsub! it to get rid of all the characters that can't be used in a PHP var. Here is what I have:
dvar = "$" + name.gsub!(/.?\/!#\#{}$%^&*()`~/, "") { |match| puts match }
I have the puts match there to make sure some of the characters were removed. name is a variable passed into a method in which this is its purpose. I am getting this error:
TypeError: can't convert nil into String
cVar at ./Web.rb:31
(root) at C:\Users\Andrew\Documents\NetBeansProjects\Web\lib\main.rb:13
Web.rb is the file this line is in, and main.rb is the file calling this method. How can I fix this?
EDIT: If I remove the ! in gsub!, it goes through, but the characters aren't removed.
Short answer
Use dvar = "$" + name.tr(".?\/!#\#{}$%^&*()``~", '')
Long answer
The problem you are facing is that the gsub! call is returning nil. You can't concatenate (+) a String with a nil.
That's happening because you have a malformed Regexp. You aren't escaping the special regex symbols, like $, * and ., just for a start. Also, the way it is now, gsub will only match if your string contains all that symbols in sequence. You should use the pipe (|) operator to make an OR like operation.
gsub! will also return nil if no substitutions happened.
See the documentation for gsub and gsub! here: http://ruby-doc.org/core/classes/String.html#M001186
I think you should replace gsub! with gsub. Do you really need name to change?
Example:
name = "m$var.name$$"
dvar = "$" + name.gsub!(/\$|\.|\*/, "") # $ or . or *
# dvar now contains $mvarname and name is mvarname
Your line, corrected:
dvar = "$" + name.gsub(/\.|\?|\/|\!|\#|\\|\#|\{|\}|\$|\%|\^|\&|\*|\(|\)|\`|\~/, "")
# some things shouldn't (or aren't needed to) be escaped, I don't remember them all right now
As J-_-L appointed, you could also use a character class ([]), that makes it a little clearer, I guess. Well, it's hard to mentally parse anyway.
dvar = "$" + name.gsub(/[\.\?\/\!\#\\\#\{\}\$\%\^\&\*\(\)\`\~]/, "")
But because what you are doing is simple character replacement, the best method is tr (again reminded by J-_-L!):
dvar = "$" + name.tr(".?\/!#\#{}$%^&*()`~", '')
Way easier to read and make modifications.
You cannot apply a second parameter
and a block to gsub (the block is ignored)
The regex is wrong, you forgot the
square brackets:
/[.?\/!#\#{}$%^&*()~]/`
Because your regex is wrong, it
didn't match anything and because
gsub! returns nil if nothing was
replaced, you get this strange nil no
method error
btw: you should use gsub not gsub! in
this case, because you are using the
return value (and not name itself) --
and the error would not have happened
i dont see what the block is for
just do
name = 'hello.?\/!##$%^&*()`~hello'
dvar = "$" + name.gsub(/\.|\?|\\|\/|\!|\#|\#|\{|\}|\$|\%|\^|\&|\*|\(|\)|\`|\~/, "")
puts dvar # => "$hellohello"
or use [] to denote OR
dvar = "$" + name.gsub(/[\.\?\\\/\!\#\\\#\{\}\$\%\^\&\*\(\)\`\~]/, "")
you have to escape the special characters and then OR them so it will remove them individually not just if they are all found together
also there is really no need to use gsub! to modify the string in place use the non mutator gsub() since you assign it to a new variable,
gsub! returns nil for which the operator + is not defined for stings, which gives you the no method error mentioned
It seems as the 'name' object is nil, you may be calling gsub! on nil which usually complains with a NoMethodError: private method gusb! called for nilNilClass, since I don't know the version of ruby you are using I am not sure if the error would be the same, but it's a good place to start looking at.

How do I parse a quoted string inside another string?

I want to extract the quoted substrings from inside a string. This is an example:
string = 'aaaa' + string_var_x + 'bbbb' + string_var_y
The output after parsing should be:
["'aaaa'", "'bbbb'"]
The initial solution was to string.scan /'\w'/ which is almost ok.
Still I can't get it working on more complex string, as it's implied that inside '...' there can be any kind of characters (including numbers, and !##$%^&*() whatever).
Any ideas?
I wonder if there's some way to make /'.*'/ working, but make it less greedy?
Lazy should fix this:
/'.*?'/
Another possibility is to use this:
/'[^']*'/
An alternate way to do it is:
>> %{string = 'aaaa' + string_var_x + 'bbbb' + string_var_y}.scan(/'[^'].+?'/)
#=> ["'aaaa'", "'bbbb'"]
String.scan gets overlooked a lot.

Resources