Ruby format string with variable inside quotes - ruby

If I do
template_string = "class=btn submit-button %<additional_classes>"
format(template_string, additional_classes: 'some-class')
it works. However, if I do
template_string = "class='btn submit-button %<additional_classes>'"
format(template_string, additional_classes: 'some-class')
it fails, giving
ArgumentError:
malformed format string - %'
(Notice the quotation marks surrounding the classes in the second template_string - this is the only difference between the two blocks of Ruby code). How do I make it work? In other words, how do I produce the following?
class='btn submit-button some-class'
I don't believe that I can just use interpolation, because sometimes I need to pass in other variables. In other words, I can't do
additional_classes = 'some-class'
"class='btn submit-button #{additional_classes}'"
because sometimes I want to reuse the same string "template" but pass in other variables, to produce strings such as the following:
class='btn submit-button some-other-class'
or
class='btn submit-button some-third-class'

From the fine manual:
format(format_string [, arguments...] ) → string
[...]
For more complex formatting, Ruby supports a reference by name. %<name>s style uses format style, but %{name} style doesn't.
The documentation isn't as clear as it could be but when you use the %<...> form, it is expecting to see %<name>s where name is the hash key and s is the format type: s for string, d for number, ... If you say:
%<additional_classes>'
then format will try to interpret ' as a type when there is no such type specifier so you get an ArgumentError because the format string is malformed.
You probably want to use the %{...} form instead:
template_string = "class='btn submit-button %{additional_classes}'"
#--------------------------------------------^------------------^

Your format string is missing the field type specifier. The field type specifier is mandatory in a format string.
It is not clear to me why the first example does not raise an error, since the mandatory field type specifier is missing. It could be a bug, or I am completely misreading the documentation.
However, it is not clear to me why you consider this example to work:
template_string = "class=btn submit-button %<additional_classes>"
format(template_string, additional_classes: 'some-class')
#=> 'class=btn submit-button %'
# ↑
As you can see, the % is not interpreted as the part of a format string but as a literal %. I would consider this a bug, it should raise an error, just like the second example does.
In the second example, you can clearly see the problem:
ArgumentError: malformed format string - %'
↑
Since a format string must have a field type specifier, and the only character after the % (except the field name) is ', this is interpreted as the field type specifier. And since ' is not a legal field type, format raises an error in which it explicitly tells you that it interpreted the ' as part of the format string.
Since you want to format strings, you should use the s (string) field type specifier:
template_string = "class=btn 'submit-button %<additional_classes>s'"
# ↑
format(template_string, additional_classes: 'some-class')
#=> "class=btn 'submit-button some-class'"
# ↑↑↑↑↑↑↑↑↑↑↑
Alternatively, you can use the %{} form:
template_string = "class=btn 'submit-button %{additional_classes}'"
# ↑ ↑
format(template_string, additional_classes: 'some-class')
#=> "class=btn 'submit-button some-class'"
# ↑↑↑↑↑↑↑↑↑↑↑

Related

How do I remove "/" and "/i" in a returned string with gsub?

I have this line:msg = "Couldn't find column: #{missing_columns.map(&:inspect).join(',')}"
that outputs: Couldn't find column: /firstname/i, /lastname/i
Is there a way that I can use gsub to return only the name of the column without the "/" and "/i"? Or is there a better way to do it?
I've tried errors = msg.gsub(/\/|i/, '') but it returns the the first missing column with "frstname".
Given that these appear to be case insensitive regular expressions meaning
missing_columns
#=> [/firstname/i,/lastname/i]
In this case rather than converting them to strings and trying to manipulate them from there you can use methods that a Regexp already responds to e.g. Regexp#source
Regexp#source - "Returns the original string of the pattern." It will not return the literal boundaries (/) or the options (i in this case)
missing_columns.map(&:source).join(', ')
#=> "firstname, lastname"
/\/|i/
Let's break this down. The // on the outside are delimiters, sort of like quotation marks for strings. So the actual regex is on the inside.
\/|i
\/ says to match a literal forward slash. \ prevents it from being interpreted as the end of the regular expression.
i says to match a literal i. So far nothing fancy. But | is an alternation. It says to match either the thing on the left or the thing on the right. Effectively, this removes all slashes and i from your string. You want to remove all / or /i, but not i on its own. You can still do that with alternation, provided you include the slash on both sides.
/\/|\/i/
You can also do it more compactly with the ? modifier, which makes the thing before it optional.
/\/i?/
Finally, you can avoid the /\/ fencepost shenanigans by using the %r{...} regular expression form rather than /.
%r{/i?}
All in all, that's
errors = msg.gsub(%r{/i?}, '')
It seems that missing_columns contains an array of Regexps. So you can use Regexp#source instead of Regexp#inspect.
For instance
msg = "Couldn't find column: #{missing_columns.map(&:source).join(', ')}"
pp msg # => "Couldn't find column: firstname, lastname"
instead of
msg = "Couldn't find column: #{missing_columns.map(&:inspect).join(', ')}"
pp msg # => "Couldn't find column: /firstname/i, /lastname/i"
Feel free to browse the documentation for Regexp#source.
hope this helps!

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)

FoxPro convert currency to numeric

I'm using Visual FoxPro and I need to convert currency amount into numeric. The 2 columns in the table are tranamt(numeric,12,2) and tranamt2(character)
Here's my example:
tranamt2=-$710,000.99
I've tried
replace all tranamt with val(tranamt2)
and
replace all tranamt with val(strtran(tranamt2, ",",""))
both results give me zero. I know it has something to do with the negative sign but I can't figure it out. Any help is appreciated.
Try this:
replace all tranamt with VAL(STRTRAN(STRTRAN(tranamt2, "$", ""), ",", ""))
This removes the dollar sign and comma in one shot.
need to convert currency amount into numeric
tranamt(numeric,12,2) and tranamt2(character)
First of all a neither a Character Field Type nor a Numeric Field type (tranamt2) are Not a VFP Currency Field type
You may be using the value of a Character field to represent currency, but that does not make it a currency value - just a String value.
And typically when that is done, you do NOT store the Dollar Sign '$' and Comma ',' as part of the data.
Instead, you store the 'raw' value (in this case: "-710000.99") and merely format how that 'raw' value is displayed when needed.
So in your Character field you have a value of: -$710,000.99
Do you have the Dollar Sign '$' and the Comma ',' as part of the field data?
If so, to convert it to a Numeric, you will first have to eliminate those extraneous characters prior to the the conversion.
If they are not stored as part of your field value, then you can use the VAL() 'as is'.
Example:
cStr = "-710000.99" && The '$' and ',' are NOT stored as part of Character value
nStr = VAL(cStr)
?nStr
However if you have the Dollar Sign and the Comma as part of the field data itself, then you can use STRTRAN() to eliminate them during the conversion.
Example:
cStr = "-$710,000.99" && Note both '$' and ',' are part of data value
* --- Remove both '$' and ',' and convert with VAL() ---
nStr = VAL(STRTRAN(STRTRAN(cStr,",",""),"$",""))
?nStr
Maybe something like:
REPLACE tranamt WITH VAL(STRTRAN(STRTRAN(tranamt2,",",""),"$",""))
EDIT: Another alternative would be to use CHRTRAN() to remove the '$' and ','
Something like:
cRemoveChar = "$," && Characters to be removed from String
REPLACE tranamt WITH VAL(CHRTRAN(tranamt2,cRemoveChar,""))
Good Luck
A little late but I use this function call
function MoneyToDecimal
LPARAMETER tnAmount
LOCAL lnAmount
IF VARTYPE(tnAmount) = "Y"
lnAmount = VAL(STRTRAN(TRANSFORM(tnAmount), "$", ""))
ELSE
lnAmount = tnAmount
ENDIF
return lnAmount
endfunc
And can be tested with these calls:
wait wind MoneyToDecimal($112.50)
wait wind MoneyToDecimal($-112.50)
Use the built-in MTON() function to convert a currency value into a numeric value:
replace all tranamt with mton(tranamt2)

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.

What does this Ruby line say?

I am working with a custom renderer, and I used some copy paste from another site. I can't seem to figure out what this piece is doing right here.
"#{options[:callback]}(#{data})"
Here is the piece of code in full context.
ActionController.add_renderer :as3 do |data, options|
data = ActiveSupport::JSON.encode(data) unless data.respond_to?(:to_str)
data = "#{options[:callback]}(#{data})" unless options[:callback].blank?
self.content_type ||= Mime::JSON
self.response_body = data
end
It's simple string interpolation. It will produce a string like this, where callback is the value of options[:callback], and value is whatever is in the variable data.
"callback(value)"
In Ruby, double-quoted strings support interpolation via #{} syntax. That is, if you have a variable x containing the value 3, the string "The value of x is #{x}" will be evaluated to "The value of x is 3". Inside a #{} you can have any arbitrarily complex Ruby expression, including array/hash indexing. So, the first part of the string, "#{options[:callback]}" is simply substituting the value of options[:callback] into the string.
The next part, the () is simply raw string data, not executable code. Inside the (), you have a second #{} substitution of data. It might be clearer if you replace the two variable substituions with x and y:
x = 3
y = 4
"#{ x }(#{ y })"
The above will evaluate to the string "3(4)"
This is converting a JSON response to JSONP; imagine data is:
'{"some": "thing", "goes": "here"}'
JSONP states that the data should be wrapped in a JavaScript function call. So of options[:callback] is the string test (the name of the function to call), the resulting JSONP would be:
'test({"some": "thing", "goes": "here"})'
It's a template that replaces the first field with the value of options poiinted to by the interned string :callback, and the second field, inside the parens with the contents of data.
I'd bet a buck that the resulting string is going to be eval'd somewhere else, where it will become a call to a procedure. That would work something like this:
options[:callback] = "foo"
data="arg,arg,arg"
(Notice that data is being encoded into JSON, so the string passed as data is a json string.
The string then turns into "foo(arg.arg.arg)", and when it's eval'd it becomes a call to routine foo with those arguments.
http://www.ruby-doc.org/core-1.9.3/Kernel.html#method-i-eval
Update:
Actually, I take it back about the Ruby eval -- although that would work, it's more likely turning into a Javascript function call. This would then let you pass the name of a javascript function as a string and the code would return the appropriate callback function for execution by javascript later.
You can rewrite
"#{options[:callback]}(#{data})"
as
options[:callback].to_s + "(" + data.to_s + ")"

Resources