Is there any way to recover from Psych::SyntaxError? - ruby

When there is a syntax error in an i18n locale YAML file, Psych::SyntaxError is raised. When this exception is encountered during Rails bootup (for example, when production is restarted), Rails crashes.
Is there any way to capture this exception and somehow recover from it without having Rails crash altogether?
Is there any way to check locale files for syntax errors before commit or deploy in an automated way?

I'm not sure if there's a way to recover from this error, but I've created a rake task that ensures that a given YAML file is syntactically valid (run via a pre-commit git hook for any changed YAML files):
namespace :yaml do
desc "Check YAML syntax for errors."
task :check_syntax do
require 'YAML'
require 'colorize'
puts "Checking YAML files..."
filenames = (ENV['FILENAMES'].split(',') || []).push(ENV['FILENAME']).uniq.compact
fails = 0
filenames.each do |file|
print "#{file}... "
begin
YAML.load_file(file)
rescue Psych::SyntaxError => e
fails += 1
print "Failed! ".red
print "[#{e.message.sub(/\A.*: /, '')}]"
puts
next
end
print "Success!".green
puts
end
puts
exit fails > 0 ? 1 : 0
end
end

Related

Ruby Rake FILE method doesn't work

It's first time I'm using rake, and I figure a problem with file dependencies.
To make a test, in Rakefile.rb I put this code:
task :ffile do
f1 = "config.yaml"
f2 = "Rakefile.rb"
if File.file? f1 then puts "## test file task on #{f1}" end
if File.file? f2 then puts "## test file task on #{f2}" end
file "#{f1}" => "#{f2}" do
puts "lol"
end
file "#{f2}" => "#{f1}" do
puts "lul"
end
file "#{f1}" do
puts "lil"
end
file "#{f2}" do
puts "lal"
end
end
I'm on Windows 10, and when run
rake ffile
the result is
Starting rake operations...
## test file task on config.yaml
## test file task on Rakefile.rb
that is file method do nothing in all four cases. I tried also to remove the quote (i.e. f1 instead "#{f1}" and so on in all file) but obtain the same result.
Clearly every time I save Rakefile.rb while testing, so I'm sure that should be trigger one of the file methos.
There's syntax error? It's troubleshoot with Windows 10?
Thanks
The file method is actually a DSL to define a file task https://github.com/ruby/rake/blob/b63f7d33d72782f646ef95da10300d273c9b8006/lib/rake/dsl_definition.rb#L75, which has to be invoked manually.
Rake::Task[f1].invoke
Rake::Task[f2].invoke
Since your dsl definitions have circular dependency: TOP => config.yaml => Rakefile.rb => config.yaml, adding the above code will raise an error.
But I think you could get the idea how to invoke the file tasks.
Thanks larrylv, I've understood!
I have two errors:
circular, as larrylv said
and I don't invoke the file task! (larrylv said)
So I report a simple example that works
f1 = "config.yaml"
f2 = "Rakefile.rb"
task :ffile => ["#{f1}"]
file "#{f1}" => ["#{f2}"] do
puts "lul"
end
With this, the command rake ffile works as aspected.
Thanks!

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.

RUBY (Errno::ENOENT), no such file or directory # dir_s_mkdir

I'm following "The Bastards Book of Ruby" and I am trying to build a webscraper using nokogiri but about a quarter of the way into it when I attempt to run the code it throws the error:
Crawler.rb:6:in `mkdir': No such file or directory # dir_s_mkdir - data-hold/nobel (Errno::ENOENT)
from Crawler.rb:6:in `<main>'
My code is as follows:
require 'rubygems'
require 'nokogiri'
require 'open-uri'
DATA_DIR = "data-hold/nobel"
Dir.mkdir(DATA_DIR) unless File.exists?(DATA_DIR)
BASE_WIKIPEDIA_URL = "http://en.wikipedia.org"
LIST_URL = "#{BASE_WIKIPEDIA_URL}/wiki/List_of_Nobel_laureates"
HEADERS_HASH = {"User-Agent" => "Ruby/#{RUBY_VERSION}"}
page = Nokogiri::HTML(open(LIST_URL))
rows = page.css('div.mw-content-ltr table.wikitable tr')
rows[1..-2].each do |row|
hrefs = row.css("td a").map{ |a|
a['href'] if a['href'] =~ /^\/wiki\//
}.compact.uniq
hrefs.each do |href|
remote_url = BASE_WIKIPEDIA_URL + href
local_fname = "#{DATA_DIR}/#{File.basename(href)}.html"
unless File.exists?(local_fname)
puts "Fetching #{remote_url}..."
begin
wiki_content = open(remote_url, HEADERS_HASH).read
rescue Exception=>e
puts "Error: #{e}"
sleep 5
else
File.open(local_fname, 'w'){|file| file.write(wiki_content)}
puts "\t...Success, saved to #{local_fname}"
ensure
sleep 1.0 + rand
end # done: begin/rescue
end # done: unless File.exists?
end # done: hrefs.each
end # done: rows.each
I have literally no idea why it is not creating a new directory to store the data in. I know I must be missing something extremely simple...
My best guess is that not only does "data-hold/nobel" not exist, "data-hold/" does not exist either. Since mkdir does not recursively create all parent directories of the directory you want to create, an error is thrown.
To fix this, you could use FileUtils.mkdir_p, which does create all parent directories.
Be sure to include fileutils before using mkdir_p.
Bundled the requirements into a method with the proper debug message. Works as expected.
$:~/rubyterminals/file_tansfer$ cat mkdir_mthod.rb
#!/usr/bin/env ruby
require 'fileutils'
def run
my_dir="/home/rubyterminals/file_tansfer/new_dir"
create_a_directory(my_dir)
end
def create_a_directory(dir_name)
if dir_name
# dir_name was specified, ensure it is created and writable.
unless File.exist?(dir_name)
begin
FileUtils.mkdir_p(dir_name)
puts "just made the following dir #{dir_name}"
rescue Errno::EACCES => e
abort "Failed to create #{dir_name}: #{e.message}"
end
end
end
end
run
tested it :
-SVE1411EGXB:~/rubyterminals/file_tansfer$ ./mkdir_mthod.rb
just made the following dir /home/rubyterminals/file_tansfer/new_dir
Hope this help.

run external program in Ruby IO.popen : rescue not working

I'm using the Tika jar to extract metadata from Microsoft Word doc files but in the case Tika encounters a problem my rescue is not catching the error, instead the scripts exits. I'm on windows 7 with MRI Ruby 1.9.3
I could adapt the doc file but I want to avoid having this problem with future files.
How can I capture this error ?
JARPATH = "jar/tika-app-1.6.jar"
def metadata
return #metadata if defined? #metadata
switch = '-m -j'
begin
command = %Q{java -Djava.awt.headless=true -jar #{JARPATH} #{switch} "#{#path}"}
output = IO.popen(command+" 2>&1") do |io|
io.read
end
if output.respond_to?(:to_str)
#metadata = JSON.parse(output)
else
#metadata = nil
end
rescue => e
puts e
puts e.backtrace
end
end
This is the output I get
c:/Ruby193/lib/ruby/gems/1.9.1/gems/json-1.8.2/lib/json/common.rb:155:in `parse': 757: unexpected token at 'Exception in thread "main" org.apache.tika.exception.TikaException: TIKA-198: Illegal IOExce
ption from org.apache.tika.parser.microsoft.OfficeParser#1006d75 (JSON::ParserError)
at org.apache.tika.parser.CompositeParser.parse(CompositeParser.java:250)
at org.apache.tika.parser.CompositeParser.parse(CompositeParser.java:244)
at org.apache.tika.parser.AutoDetectParser.parse(AutoDetectParser.java:121)
at org.apache.tika.cli.TikaCLI$OutputType.process(TikaCLI.java:143)
at org.apache.tika.cli.TikaCLI.process(TikaCLI.java:422)
at org.apache.tika.cli.TikaCLI.main(TikaCLI.java:113)
Caused by: java.io.IOException: Invalid header signature; read 0x04090000002DA5DB, expected 0xE11AB1A1E011CFD0 - Your file appears not to be a valid OLE2 document
at org.apache.poi.poifs.storage.HeaderBlock.<init>(HeaderBlock.java:140)
at org.apache.poi.poifs.storage.HeaderBlock.<init>(HeaderBlock.java:115)
at org.apache.poi.poifs.filesystem.NPOIFSFileSystem.<init>(NPOIFSFileSystem.java:204)
at org.apache.poi.poifs.filesystem.NPOIFSFileSystem.<init>(NPOIFSFileSystem.java:163)
at org.apache.tika.parser.microsoft.OfficeParser.parse(OfficeParser.java:162)
at org.apache.tika.parser.CompositeParser.parse(CompositeParser.java:244)
... 5 more
'
from c:/Ruby193/lib/ruby/gems/1.9.1/gems/json-1.8.2/lib/json/common.rb:155:in `parse'
from C:/Users/.../tika.rb:37:in `metadata'
from C:/Users/.../index_helpers.rb:55:in `index_doc'
from index.rb:39:in `block in <main>'
from index.rb:20:in `each'
from index.rb:20:in `each_with_index'
from index.rb:20:in `<main>'
After your call to IO.popen you are passing the output from the child program to JSON.parse, regardless of whether it is valid. The exception you see is the json parser trying to parse the Java exception method, which is captured because you redirect stderr with 2>&1.
You need to check that the child process completed successfully before continuing. The simplest way is probably to use the $? special variable, which indicates the status of the last executed child process, after the call to popen. This variable is an instance if Process::Status. You could do something like this:
output = IO.popen(command+" 2>&1") do |io|
io.read
end
unless $?.success?
# Handle the error however you feel is best, e.g.
puts "Tika had an error, the message was:\n#{output}"
raise "Tika error"
end
For more control you could look at the Open3 module in the standard library. Since Tika is a Java program, another possibility might be to look into using JRuby and call it directly.

Catch errors in vim/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

Resources