Catch errors in vim/ruby - ruby

I'm writing a vim plugin using the ruby interface.
When I execute VIM::command(...), how can I detect if vim raised an error during execution of this command, so that I can skip further commands and also present a better message to the user?

Vim's global variable v:errmsg will give you the last error. If you want to check whether an error occured, you can first set it to an empty string and then check for it:
let v:errmsg = ""
" issue your command
if v:errmsg != ""
" handle the error
endif;
I'll leave it up to you to transfer this to the Ruby API. Also see :h v:errmsg from inside Vim. Other useful global variables may be:
v:exception
v:throwpoint
Edit – this should work (caution: some magic involved):
module VIM
class Error < StandardError; end
class << self
def command_with_error *args
command('let v:errmsg=""')
command(*args)
msg = evaluate('v:errmsg')
raise ::VIM::Error, msg unless msg.empty?
end
end
end
# Usage
# use sil[ent]! or the error will bubble up to Vim
begin
VIM::command_with_error('sil! foobar')
rescue VIM::Error => e
puts 'Rescued from: ' + e.message;
end
# Output
Rescued from: E492: Not an editor command: sil! foobar

Related

Taking a specific time from command line is not working

I have a program, which calculate many things. While I run the code by ruby code.rb everything is okay. The problem starts, when I want to run it by command line with additional option: ruby code.rb --time 201712121100.
The piece of problematic code is below:
include Options #here I have some options to choose, like --time
def calculate_p(time, mode)
if mode
calculator = calc1
else
calculator = calc2
end
calculate_t(time, calculator)
end
def calculate_t(time, calculator)
date_ymd = time.strftime("%Y%m%d")
time_hm = time.strftime("%H%M")
calculator
.with(date_ymd, time_hm)
.run do |result|
if result.ok?
result.stdout.pop.split.first
else
msgw("Program returned with errors.", :error)
msgw("stdout: %s; stderr: %s" % [result.stdout, result.stderr], :error)
false
end
end
end
time = Options.get('--time')
.andand do |time_op|
msgw('Taking time from command line arguments') do
time_op.pop.andand
end
end || msgw('Calculating time for now.') do
Time.now.utc
end || abort
calc=calculate_p(time, mode)
msgw is just define to print messages.
mode takes true or false values.
I received an error:
"calculate_t: undefined method strftime for "201712121100":String (NoMethodError)"
What am I doing wrong? Why using Time.now.utc is working while giving a specific time is not?
I also checked the solutions from here Rails undefined method `strftime' for "2013-03-06":String
and Date.parse() gives the same error.
The issue is here:
time_op.pop.andand
time_op taken from the command line is a string, and you need a Time instance. The get it, use DateTime#strptime:
DateTime.strptime(time_op.pop, "%Y%m%d%H%M").to_time.andand

Ruby: unexpected ',', expecting keyword_end

Very new to Ruby, unable to see the titular syntax error in this bit of code:
#! /usr/bin/env ruby
require 'sensu-plugin/metric/cli'
class MetricAvailableUpdates < Sensu::Plugin::Metric::CLI::Graphite
option :scheme,
description: 'Metric naming scheme',
long: '--scheme SCHEME',
short: '-s SCHEME',
default: "#{Socket.gethostname}"
def run
# Get the metrics.
output = %x[/usr/lib/update-notifier/apt-check --human-readable]
output_lines = output.split(/(\n)/)
metrics = {}
updates_pattern = " packages can be updated."
updates = output_lines[0].tr(upgrades_pattern, "").to_i
metrics[:available_updates] = updates
security_updates_pattern = " updates are security updates."
security_updates = output_lines[2].tr(security_updates_pattern, "").to_i
metrics[:available_security_updates] = security_updates
# Print them in graphite format.
metrics.each do |k, v|
output [config[:scheme], k].join('.'), v
end
# Done
ok
end
end
I can add the code that precedes this if the syntax error is in fact before this section. Edit: added complete file contents per comment request
The complete error, in case that is useful:
./metrics-available-updates.rb:29: syntax error, unexpected ',', expecting keyword_end
output [config[:scheme], k].join('.'), v
If you play around a bit, you will notice that the syntax error goes away either when you comment out the offending line, or alternatively the line
output = %x[/usr/lib/update-notifier/apt-check --human-readable]
When Ruby parses a file, it needs to guess, whether a symbol denotes a method call, or a variable reference. In this case, output springs into existence as a variable, but further down, you write
output [config[:scheme], k].join('.'), v
which means it suddenly becomes a method call.
I admit that the Ruby lexer should give a more helpful error message....
Add the parentheses
...
metrics.each do |k, v|
output([config[:scheme], k].join('.'), v)
end
...

Using rescue and ensure in the middle of code

Still new to Ruby - I've had a look at some of the answers to seemingly similar questions but, to be honest, I couldn't get my head around them.
I have some code that reads a .csv file. The data is split into groups of 40-50 rows per user record and validates data in the rows against a database accessed via a website.
A login is required for each record, but once that user has logged in each row in the .csv file can be checked until the next user is reached, at which point the user logs out.
All that's working, however, if an error occurs (e.g. a different result on the website than the expected result on the .csv file) the program stops.
I need something that will
a) at tell me which line on the file the error occurred
b) log the row to be output when it's finished running, and
iii) restart the program from the next line in the .csv file
The code I have so far is below
Thanks in advance,
Peter
require 'csv-mapper'
loginrequired = true
Given(/^I compare the User Details from rows "(.*?)" to "(.*?)"$/) do |firstrow, lastrow|
data = CsvMapper.import('C:/auto_test_data/User Data csv.csv') do
[dln, nino, pcode, endor_cd, ct_cd]
end
#Row number changed because Excel starts at 'row 1' and Ruby starts counting at 'row 0'
(firstrow.to_i-1..lastrow.to_i-1).each do |row|
#licnum1 = data.at(row).dln
#licnum2 = data.at(row+1).dln
#nino = data.at(row).nino
#postcode = data.at(row).pcode
#endor_cd = data.at(row).endor_cd
#ct_cd = data.at(row).ct_cd
#Login only required once for each new user-account
if
loginrequired == true
logon_to_vdr #def for this is in hooks
click_on 'P and D'
loginrequired = false
end
#This is the check against the database and is required for every line in the .csv file
check_ctcd #def for this is in hooks
#Need something in here to log errors and move on to the next line in the .csv file
#Compare the ID for the next record and logout if they're different
if #licnum1 == #licnum2
loginrequired = false
else
loginrequired = true`enter code here`
click_on 'Logout'
end
end
end
It seems like you need some error logging since you apparently don't know what type of error you're receiving or where. If this script is standalone you can redirect $stderr to file so that you can read what went wrong.
# put this line at the top of your script
$stderr = File.open("/path/to/your/logfile.log","a")
When an error occurs, ruby will automatically write the error message, class, and backtrace to the log file you specify so that you can trace back the line where things are not going as expected. (When you run a script from the command line, normally this information will just get blurted back to the terminal when an error happens.)
For example, on my desktop I created a file log_stderr.rb with the following (line numbers included):
1 $stderr = File.open("C:/Users/me/Desktop/my_log.log","w")
2
3 #require a file which will raise an error to see the backtrace
4 require_relative 'raise_error.rb'
5
6 puts "code that will never be reached"
Also on my desktop I created the file raise_error.rb with the following (to deepen the backtrace for better example output):
1 # call raise to generate an error arbitrarily
2 # to halt execution and exit the program.
3 raise RuntimeError, 'the program stopped working!'
When I run ruby log_stderr.rb from the command line, my_log.log is created on my desktop with the following:
C:/Users/me/Desktop/raise_error.rb:3:in `<top (required)>': the program stopped working! (RuntimeError)
from C:/Users/me/Desktop/log_stderr.rb:4:in `require_relative'
from C:/Users/me/Desktop/log_stderr.rb:4:in `<main>'
If you are working in a larger environment where your script is being called amidst other scripts then you probably do not want to redirect $stderr because this would affect everything else running in the environment. ($stderr is global as indicated by the $ variable prefix.) If this is the case you would want to implement a begin; rescue; end structure and also make your own logfile so that you don't affect $stderr.
Again, since you don't know where the error is happening you want to wrap the whole script with begin; end
# at the very top of the script, begin watching for weirdness
begin
logfile = File.open("/path/to/your/logfile.log", "w")
require 'csv-mapper'
#. . .
# rescue and end at the very bottom to capture any errors that have happened
rescue => e
# capture details about the error in your logfile
logfile.puts "ERROR:", e.class, e.message, e.backtrace
# pass the error along since you don't know what it is
# and there may have been a very good reason to stop the program
raise e
end
If you find that your error is happening only in the block (firstrow.to_i-1..lastrow.to_i-1).each do |row| you can place the begin; end inside of this block to have access to the local row variable, or else create a top level variable independent of the block and assign it during each iteration of the block to report to your logfile:
begin
logfile = File.open("/path/to/your/logfile.log", "w")
csv_row = "before csv"
#. . .
(firstrow.to_i-1..lastrow.to_i-1).each do |row|
csv_row = row
#. . .
end
csv_row = "after csv"
rescue => e
logfile.puts "ERROR AT ROW: #{csv_row}", e.class, e.message, e.backtrace
raise e
end
I hope this helps!
It doesn't seem like you need to rescue exception here. But what you could do is in your check_ctcd method, raise error if records doesn't match. Then you can rescue from it. In order to know which line it is, in your iteration, you could use #each_with_index and log the index when things go wrong.
(firstrow.to_i-1..lastrow.to_i-1).each_with_index do |row, i|
#licnum1 = data.at(row).dln
#licnum2 = data.at(row+1).dln
#nino = data.at(row).nino
#postcode = data.at(row).pcode
#endor_cd = data.at(row).endor_cd
#ct_cd = data.at(row).ct_cd
#Login only required once for each new user-account
if
loginrequired == true
logon_to_vdr #def for this is in hooks
click_on 'P and D'
loginrequired = false
end
#This is the check against the database and is required for every line in the .csv file
check_ctcd #def for this is in hooks
rescue => e
# log the error and index here
...
And you can make your own custom error, and rescue only the certain type so that you don't silently rescue other errors.

Discussion with a sub-process, using Ruby with IO and threading

I am trying to use IO.popen in order to put (with .puts method) and to get (with .gets method) messages from a process to its sub-process.
I am not very experimented and I have a question about. Having the following code, I have an error because it is not possible to write in a closed stream.
class Interface
def initialize(path)
#sub_process = IO.popen(path, 'w+')
end
def start!
if ok?
#sub_process.puts 'Hello', 'my name is ...'
# and more...
end
end
protected
def ok?
is_ready?(#sub_process) && is_cool?(#sub_process)
end
def is_ready?(sub_process)
reply = process_command(sub_process, 'are u ready?')
reply.chomp.match(/yes_i_am_ready$/)
end
def is_cool?(sub_process)
reply = process_command(sub_process, 'are u cool?')
reply.chomp.match(/yes_i_am_cool$/)
end
def process_command(sub_process, command)
rdr = Thread.new { sub_process.read } # alternative: io.readlines
sub_process.puts "#{command}"
sub_process.close_write
rdr.value # joins and fetches the result
end
end
a = Interface.new("./program")
a.start!
(...) in `write': not opened for writing (IOError)
As we can see, this error occur during is_cool? test (as explained at: http://ruby-doc.org/core/classes/IO.html#M002289).
But if I try to comment in process_command method the line:
# sub_process.close_write
the script seems to sleep... infinitely :s
I believe that it is not possible to open again a closed stream. And I can't create an other IO.popen instance of my program "./program" because it needs to be initialized with some command (like 'are u ready?' and 'are u cool?') at the beginning, before I use it (by sending and receiving messages like a simple discussion).
How changes can I do over the current code in order to solve this problem?
Edit: in other words, I would like to establish a such communication (according to a given protocol):
Parent message: Child answer:
-------------- ------------
'are u ready?' 'yes_i_am_ready'
'are u cool?' 'yes_i_am_cool'
'Hello' 'foo'
'my name is ...' 'bar'
Many thanks for any help.
Perhaps it will help to have a working example. Here's one, tested and known to work in MRI 1.8.7 on Linux.
bar.rb
#!/usr/bin/ruby1.8
begin
loop do
puts "You said: #{gets}"
$stdout.flush
end
rescue Errno::EPIPE
end
foo.rb
#!/usr/bin/ruby1.8
class Parent
def initialize
#pipe = IO.popen(CHILD_COMMAND, 'w+')
end
def talk(message)
#pipe.puts(message)
response = #pipe.gets
if response.nil?
$stderr.puts "Failed: #{CHILD_COMMAND}"
exit(1)
end
response.chomp
end
private
CHILD_COMMAND = './bar.rb'
end
parent = Parent.new
puts parent.talk('blah blah blah')
puts parent.talk('foo bar baz')
foo.rb output
You said: blah blah blah
You said: foo bar baz
A closed IO can not be used anymore. You should not close an IO if you intend on still using it.
If you remove the IO#close_write there still remains the problem with your code in the following line.
rdr = Thread.new { sub_process.read }
IO#read read's until EOF. So until the stream get's closed it never terminates. You mentioned IO#readline in your code, this would be the better alternative. Using IO#readline your program would only hang if the popend process never send's a newline.
Another problem with popen is the following. IO#popen create's a new process. Process's may be killed by you, other users, memory shortages, …. Don't expect your process to always run all the time. If the process is killed IO#readline will throw an EOFError, IO#read will return imidiatley. You can determine the termination reason with the following code.
Process::wait(io.pid)
status= $?
status.class # => Process::Status
status.signaled? # killed by signal?
status.stopsig # the signal which killed it
status.exited # terminated normal
status.exitstatus # the return value
status.ki
Does it help to use this form of Thread.new?
rdr = Thread.new(sub_process) {|x| x.readlines }

How do I get ruby to print a full backtrace instead of a truncated one?

When I get exceptions, it is often from deep within the call stack. When this happens, more often than not, the actual offending line of code is hidden from me:
tmp.rb:7:in `t': undefined method `bar' for nil:NilClass (NoMethodError)
from tmp.rb:10:in `s'
from tmp.rb:13:in `r'
from tmp.rb:16:in `q'
from tmp.rb:19:in `p'
from tmp.rb:22:in `o'
from tmp.rb:25:in `n'
from tmp.rb:28:in `m'
from tmp.rb:31:in `l'
... 8 levels...
from tmp.rb:58:in `c'
from tmp.rb:61:in `b'
from tmp.rb:64:in `a'
from tmp.rb:67
That "... 8 levels..." truncation is causing me a great deal of trouble. I'm not having much success googling for this one: How do I tell ruby that I want dumps to include the full stack?
Exception#backtrace has the entire stack in it:
def do_division_by_zero; 5 / 0; end
begin
do_division_by_zero
rescue => exception
puts exception.backtrace
raise # always reraise
end
(Inspired by Peter Cooper's Ruby Inside blog)
You could also do this if you'd like a simple one-liner:
puts caller
This produces the error description and nice clean, indented stacktrace:
begin
# Some exception throwing code
rescue => e
puts "Error during processing: #{$!}"
puts "Backtrace:\n\t#{e.backtrace.join("\n\t")}"
end
IRB has a setting for this awful "feature", which you can customize.
Create a file called ~/.irbrc that includes the following line:
IRB.conf[:BACK_TRACE_LIMIT] = 100
This will allow you to see 100 stack frames in irb, at least. I haven't been able to find an equivalent setting for the non-interactive runtime.
Detailed information about IRB customization can be found in the Pickaxe book.
One liner for callstack:
begin; Whatever.you.want; rescue => e; puts e.message; puts; puts e.backtrace; end
One liner for callstack without all the gems's:
begin; Whatever.you.want; rescue => e; puts e.message; puts; puts e.backtrace.grep_v(/\/gems\//); end
One liner for callstack without all the gems's and relative to current directory
begin; Whatever.you.want; rescue => e; puts e.message; puts; puts e.backtrace.grep_v(/\/gems\//).map { |l| l.gsub(`pwd`.strip + '/', '') }; end
This mimics the official Ruby trace, if that's important to you.
begin
0/0 # or some other nonsense
rescue => e
puts e.backtrace.join("\n\t")
.sub("\n\t", ": #{e}#{e.class ? " (#{e.class})" : ''}\n\t")
end
Amusingly, it doesn't handle 'unhandled exception' properly, reporting it as 'RuntimeError', but the location is correct.
Almost everybody answered this. My version of printing any rails exception into logs would be:
begin
some_statement
rescue => e
puts "Exception Occurred #{e}. Message: #{e.message}. Backtrace: \n #{e.backtrace.join("\n")}"
Rails.logger.error "Exception Occurred #{e}. Message: #{e.message}. Backtrace: \n #{e.backtrace.join("\n")}"
end
I was getting these errors when trying to load my test environment (via rake test or autotest) and the IRB suggestions didn't help. I ended up wrapping my entire test/test_helper.rb in a begin/rescue block and that fixed things up.
begin
class ActiveSupport::TestCase
#awesome stuff
end
rescue => e
puts e.backtrace
end
[examine all threads backtraces to find the culprit]
Even fully expanded call stack can still hide the actual offending line of code from you when you use more than one thread!
Example: One thread is iterating ruby Hash, other thread is trying to modify it. BOOM! Exception! And the problem with the stack trace you get while trying to modify 'busy' hash is that it shows you chain of functions down to the place where you're trying to modify hash, but it does NOT show who's currently iterating it in parallel (who owns it)! Here's the way to figure that out by printing stack trace for ALL currently running threads. Here's how you do this:
# This solution was found in comment by #thedarkone on https://github.com/rails/rails/issues/24627
rescue Object => boom
thread_count = 0
Thread.list.each do |t|
thread_count += 1
err_msg += "--- thread #{thread_count} of total #{Thread.list.size} #{t.object_id} backtrace begin \n"
# Lets see if we are able to pin down the culprit
# by collecting backtrace for all existing threads:
err_msg += t.backtrace.join("\n")
err_msg += "\n---thread #{thread_count} of total #{Thread.list.size} #{t.object_id} backtrace end \n"
end
# and just print it somewhere you like:
$stderr.puts(err_msg)
raise # always reraise
end
The above code snippet is useful even just for educational purposes as it can show you (like x-ray) how many threads you actually have (versus how many you thought you have - quite often those two are different numbers ;)
You can also use backtrace Ruby gem (I'm the author):
require 'backtrace'
begin
# do something dangerous
rescue StandardError => e
puts Backtrace.new(e)
end

Resources