Ruby error using sub method with closure - ruby

I am not a Ruby developer, but I'm trying to use 1 line of Ruby script to increment a 3-part version number for a Grails application that we're building with Jenkins. The version number is stored in a simple properties file called application.properties. I know there's tons of ways to do this, but my question here is specific to Ruby and why it's not working.
For testing, I reduced application.properties to a single line:
app.version=0.2.8
All I want to do is increment the last number. That's it. So, I found some code online and hacked it a bit:
ruby -pi.bak -e 'sub(/^app\.version=(\d+)\.(\d+)\.(-?\d+)/) { "app.version=#{$1}.#{$2}.#{$3.to_i.next}" }' application.properties
This code works perfectly on Ruby 1.8.7 on OS X, but fails on Ruby 1.9.3 on Linux:
-e:1:in `sub': wrong number of arguments (1 for 1..2) (ArgumentError)
from -e:1:in `sub'
from -e:1:in `<main>'
Note that the expression works on Linux if applied directly to a string literal:
ruby -e 'puts "app.version=0.2.8".sub(/^app\.version=(\d+)\.(\d+)\.(-?\d+)/) { "app.version=#{$1}.#{$2}.#{$3.to_i.next}" }'
I also tried gsub but that didn't work either (but for a different reason).
I must be missing something simple here. I've tried other examples using a closure with sub() and it seemed to work fine.

Since BernardK suggested that not all versions of Ruby treat -p the same, and since my code worked when applied to a string literal, then here is a solution that works on both 1.8.7 and 1.9.3. Create a file called incAppVersion.rb:
if $_ =~ /^\s*app\.version\s*=/
print $_.sub(/(\d+)\.(\d+)\.(-?\d+)/) { "#{$1}.#{$2}.#{$3.to_i.next}" }
else
print $_
end
and run it with:
ruby -ni.bak incAppVer.rb application.properties
-n is cool!
Please feel free to offer suggestions for improvement.

$ ruby -pi.bak -e'$_.chomp!; $_ = $_[0..-2] + $_[-1, 1].succ + "\n" if $_[0..11] == "app.version="' application.properties
As the Pickaxe(*) explains for -p : Places your program code within the loop while gets; ...; print; end.
gets puts the line just read into $_.
chomp! removes CRLF.
print without arguments prints $_, thus we must modify $_ : $_ = ...
$_[0..-2] is the input line except the last character
$_[-1, 1] is the last character, for a length one (needed in Ruby 1.8 to get a char, not a number)
$_[-1, 1].succ is the next character/number
+ "\n" to put CRLF again (change it to "\r\n" on Windows)
if $_[0..11] == "app.version=" to process only the desired property
$ ruby -v
ruby 1.9.2p320 (2012-04-20 revision 35421) [x86_64-darwin12.2.0]
Tested on OS X ML.
File before :
app.version=0.2.8
line2
File after two executions :
app.version=0.2.10
line2
(*) http://pragprog.com/book/ruby3/programming-ruby-1-9

Related

Workaround "flip-flop deprecated" warning in Ruby

I invoke Ruby from my shell script like this:
ruby -n -e "print if %r($fromre)...%r($tore)" "$#"
Since Ruby 2.6, I get the warning
warning: flip-flop is deprecated
So it seems that i have to be prepared that the wise high priests of Ruby have decided that this (IMO useful) construct will be completely gone one day.
What would be an easy work-around? Of course I can replace
print if %r($fromre)...%r($tore)
by
inside ||= %r($fromre)
if inside
print
inside = false if %r($tore)
end
but I wonder whether there is a more concise way to write this.

Escaping in %q notation won't work in irb

Here is a sample code called test.rb:
s = %Q_abc\_def\_ghi_
puts s
s = %q_abc\_def\_ghi_
puts s
It works fine as expected:
➜ Desktop ruby test.rb
abc_def_ghi
abc_def_ghi
However, when I run it in irb, nothing happened after s = %q_abc\_def\_ghi_:
➜ Desktop irb
irb(main):001:0> s = %Q_abc\_def\_ghi_
=> "abc_def_ghi"
irb(main):002:0> puts s
abc_def_ghi
=> nil
irb(main):003:0>
irb(main):004:0* s = %q_abc\_def\_ghi_
irb(main):005:1> puts s
irb(main):006:1>
irb(main):007:1*
irb(main):008:1*
Why it won't work? And how can I escape '_' (or other delimiters) in %q notation?
My Ruby version is:
ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]
IRB has its own Ruby lexer/parser which it uses to try and keep track of the state of code entered so that it can do things like display different prompts depending on things like if you are in the middle of a string or defining a method or class. The code is the passed to Ruby to be evaluated “properly”.
It looks like this has a bug relating to how it handles escaping of single quoted style strings that aren’t actually using using single quotes.
Ruby itself handles the escaping just fine, so normally I don’t think this bug would actually have much affect, but in your example you happen to have used the string def right after the second _, which is a keyword that IRB also looks for.
This combination appears to put IRB into a strange state where its understanding of what is going on differs from what’s actually happening. This is the odd behaviour you are seeing.
A little playing around with a checked out version of the IRB code seems to support this. The snippet I think is to blame looks like this:
elsif ch == '\\' and #ltype == "'" #'
case ch = getc
when "\\", "\n", "'"
else
ungetc
end
Changing the when line to also look for the actual character being used:
when "\\", "\n", "'", quoted
(quoted is a parameter passed to the function) appears to fix it, and your examples all work fine with this modified version. I don’t know if that is a sufficient fix though, I don’t know the code—this is just a quick hack.
It might be worth opening a bug about this.
I'm not sure why this displays differently in your Ruby file and IRB but lowercase percent strings do not escape. See Difference between '%{}', '%Q{}', '%q{}' in ruby string delimiters
Since %q does not support escaping, there is probably some undefined behavior when you try to use different delimiters and escape characters.
This probably isn't the answer you were looking for but I think it should help a bit.

Ruby IO.readlines stops before the last line of a file

I'm using Ruby 1.9.3 on a Windows machine.
When I run IO.readlines on a file, I get an odd and unexpected result. The result of running IO.readlines on a file is an array that just ends in a line that isn't the last line in the file. For example, the last element in the array is this string:
MLOGIC(RESPONSE_FREQUENCY): Parameter NUMERATOR_SUBSET has value \x01where
This is the last element in the array even though the file actually has many more lines.
When I run IO.readlines on the exact same file on OS X using Ruby 2.0.0, there is no issue; it reads the whole file into an array as expected.
Any assistance with this would be greatly appreciated.
The main difference of IO.readlines (docs 1.9.3 and 2.0.0) on Windows vs. OSX is the default line seperator $/—on unixoid system you'll find that to be "\n" and on Windows it is "\r\n".
To get the same behaviour, you can call IO.readlines like so:
arr = IO.readlines('./testfile', "\n")

How to get latest stable Ruby version from command line?

I'm interested in querying the latest stable version of Ruby from the command line. I'm primarily interested in a string of the form "1.9.3p327".
Update #1
To clarify, the goal is to consistently query the latest stable version, whether that be 1.9.3p327 or 3.0.2p392.
Something like:
curl 'http://ftp.ruby-lang.org/pub/ruby/1.9/' | ruby ./extract-and-print-max-patchlevel.rb
Where the implementation of extract-and-print-max-patchlevel.rb script is an exercise for the reader but here's a start:
#!/usr/bin/env ruby
maxpatch=0
maxstr=nil
STDIN.each_line do |line|
next unless line =~ /1\.9\.3-p(\d+)\b/
patch = $1.to_i
if patch > maxpatch
maxpatch = patch
maxstr = $&
end
end
puts maxstr
Note that it assumes Ruby 1.9.3 is the latest, so you might want to revisit that.
Came back to this after starting to use rbenv. If you have rbenv installed, the following one-liner does the trick:
#!/usr/bin/env ruby
puts `~/.rbenv/bin/rbenv install --list`.split("\n").map{|item| item.strip}.select{|item| item[/^\d*\.\d*\.\d*/]}.reject{|item| (item.include? '-') && !(item =~ /-p\d*$/)}.last
You can play with sed or awk. For example:
curl "http://ftp.ruby-lang.org/pub/ruby/1.9/" | sed -E 's/^.*"(ruby-)(1.9.3-p[0-9]+)(.*)".*$/\2/' | sed -e '/^1.*/!d' | sort | sed '$!d'
I'm not sed expert and here could be better solution but this line works.
Choosing between #maerics' and #YevgeniyAnfilofyev's answers, I found the Ruby one more authentic and turned it into one-liner, assuming that the list from curl is already sorted, so we simply need the last line meeting the condition:
curl 'http://ftp.ruby-lang.org/pub/ruby/1.9/' 2> /dev/null | ruby -e "puts STDIN.lines.map { |x| /1\.9\.3-p\d+\b/.match(x) }.compact.last[0]"
Or, Ruby part expanded:
puts STDIN.lines.map do |x|
/1\.9\.3-p\d+\b/.match(x) # After such map we will get an array of MatchData or nils
end.compact.last[0] # Then we remove nils using Array#compact,
# get last MatchData and retrieve matched string with [0]
Following a good tradition, adding sorting to this script left as an excercise for the reader :)
UPD: Of course, hard-coded 1.9.3 is bad, but if we replace each digit 1, 9 and 3 with \d, the regex seems to become more or less independent. The other problem is that we only look into .../ruby/1.9 folder of that ftp. This may be fixed if we, instead, look into .../ruby folder first, and find all version-numbered folders with regex /\d\.\d\//. Then repeat the above query, joining results from all folders. But, of course, this already cannot made into a one-liner...

Any way to automagically `puts` the last expression in a Ruby script?

I'm working on implementing Project Euler solutions as semantic Ruby one-liners. It would be extremely useful if I could coerce Ruby to automatically puts the value of the last expression. Is there a way to do this? For example:
#!/usr/bin/env ruby -Ilib -rrubygems -reuler
1.upto(100).into {|n| (n.sum.squared - n.map(&:squared).sum)
I realize I can simply puts the line, but for other reasons (I plan to eval the file in tests, to compare against the expected output) I would like to avoid an explicit puts. Also, it allots me an extra four characters for the solution. :)
Is there anything I can do?
You might try running it under irb instead of directly under a Ruby interpreter.
It seems like the options -f --noprompt --noverbose might be suitable (.
#!/usr/bin/env irb -f --noprompt --noverbose -Ilib -rrubygems -reuler
'put your one-liner here'
The options have these meanings:
-f: do not use .irbrc (or IRBRC)
--noverbose: do not display the source lines
--noprompt: do not prefix the output (e.g. with =>)
result = calculate_result
puts result if File.exist?(__FILE__)
result of eval is last executed operation just like any other code block in ruby
is doing
puts eval(file_contents)
an option for you?
EDIT
you can make use of eval's second parameter which is variables binding
try the following:
do_not_puts = true
eval(file_contents, binding)
and in the file:
....
result = final_result
if defined?(do_not_puts)
result
else
puts(result)
end
Is it an option to change the way you run scripts?
script.rb:
$_= 1.upto(100).into {|n| (n.sum.squared - n.map(&:squared).sum)
invoke with
echo nil.txt | /usr/bin/env/ruby -Ilib -rrubygems -reuler -p script.rb, where nil.txt is a file with a single newline.

Resources