How to make Ruby partially parse the source code? - ruby

I am trying to write a script in Ruby which interactively generate some input data for a program. The idea is use QtRuby when it exists, otherwise console is used. What I tried is to
begin
require "Qt4"
rescue LoadError => load_err
puts "Qt not found, using console"
end
class ConsoleDefine
# console code
end
class QtDefine < Qt::Widget
# GUI code
end
but the interpreter refused my code when Qt4 does not exist. is there a way to deal it similar to C++, like:
#ifdef QT4
class qt4gui
{
// some code
};
#else
class qt4gui
{
// dummy
};
#endif // Qt4

Use require to your advantage:
begin
require "Qt4"
require "my_lib/qt4"
rescue LoadError => load_err
puts "Qt not found, using console"
require "my_lib/console"
end
Create the two files:
# my_lib/console.rb
class ConsoleDefine
# console code
end
# my_lib/qt4.rb
class QtDefine < Qt::Widget
# GUI code
end

As #pst said, you don't need a preprocessor in Ruby, since it is dynamic. So:
begin
require "Qt4"
class QtDefine < Qt::Widget
# GUI code
end
rescue LoadError => load_err
puts "Qt not found, using console"
class ConsoleDefine
# console code
end
end

Related

How to use rescue when a LoadError is raised

I'm trying to implement a file dialog using Tk. This aspect has worked but my error checking isn't working.
Since this file dialog can only take certain extensions I made it raise a LoadError, but I also don't want the program to stop, I want it to reopen to allow the user to pick another file.
Each way I've tried has only ended in an infinite loop or a LoadError stopping the program.
My code is:
module FileExplorer
require 'tk'
require 'tkextlib/tile'
def self.fileDialog
TkClipboard.append(Tk.getOpenFile)
f = TkClipboard.get
begin
unless extenstionCheck(f)
raise LoadError, 'Please select a valid file type'
end
rescue LoadError
fileDialog
end
end
def self.extenstionCheck(file)
filetypes = ['.xlsx', '.xls', '.csv', '.xml']
type = File.extname(file)
true if filetypes.include?(file)
end
end
There's no need to use TkClipboard, nor to use an exception.
Did misspelling the word, 'extension' blind you to your nearby error of checking whether filetypes included file, instead of type?
Your program, minimally changed as follows, works for me:
module FileExplorer
require 'tk'
require 'tkextlib/tile'
def self.fileDialog
while true
f = Tk.getOpenFile
break if extension_okay?(f)
Tk.messageBox message: 'Please select a valid file type!', detail: "Selection was: #{f}"
end
f
end
def self.extension_okay?(file)
filetypes = ['.xlsx', '.xls', '.csv', '.xml']
type = File.extname(file)
filetypes.include?(type)
end
end
p FileExplorer.fileDialog
This is completely inappropriate (and unnecessary) use of LoadError.
Raised when a file required (a Ruby script, extension library, …)
fails to load.
Its a low level error that does not inherit from StandardError and is tied to Kernel#require.
Instead declare your own exceptions in your own namespace:
module FileExplorer
require 'tk'
require 'tkextlib/tile'
FileTypeError = Class.new(::StandardError)
def self.fileDialog
TkClipboard.append(Tk.getOpenFile)
f = TkClipboard.get
begin
unless extenstionCheck(f)
raise FileTypeError, 'Please select a valid file type'
end
rescue FileTypeError
fileDialog
end
end
def self.extenstionCheck(file)
filetypes = ['.xlsx', '.xls', '.csv', '.xml']
type = File.extname(file)
true if filetypes.include?(file)
end
end

OS specific tests with ruby

I've the following Rakefile
require 'bundler/gem_tasks'
require 'rake/testtask'
Rake::TestTask.new do |task|
task.libs << %w(test lib)
task.pattern = 'test/**/*_test.rb'
end
task default: :test
And test are defined with minitest:
require 'test_helper'
require 'certmanager'
class CertificateTest < Minitest::Test
context 'settings' do
should 'retrieve settings' do
assert_equal 'test123', Certmanager::Settings.key_passphrase
end
end
end
Now there is also some OS specific code like FileUtils.ln_s on Linux and FileUtils.cp on Windows. This code requires different tests. In this case e.g. assert_equal filepath, File.readlink(linkpath) (Linux) vs. assert File.exist?(filepath) (windows)
What is the best practice to differentiate between OS Types?
Do I have t write the tests in a way that Linux can test windows specific code and vice versa?
Is it possible to differentiate "inline" in an test?
Is it necessary to have two different testsets and decide in the Rakefile which set needs to be executed? Is it even possible int the Rakefile?
In order to be able to run every test regardless of the current OS, i wrote a module TestHelper. This module contains severall methods to
pretend a windows or posix environment for the test
setup stubs for methods that are not implemented (or work in a different way) on the actual OS - currently I've only found those methods on windows
check if the actual OS is windows or not (if it is necessary to know)
In the test it is possible to simply use TestHelper.setup_windows_env or TestHelper.setup_posix_env whenever it has to be OS specific.
require 'rbconfig'
require 'fileutils'
module TestHelper
extend self
def setup_windows_env
is_windows?
RbConfig::CONFIG.stubs(:[]).with('host_os').returns('windows')
ENV.stubs(:[]).with('OS').returns('Windows_NT')
end
def setup_stubs_for_windows_in_posix_env
class << FileUtils
def ln_s(src, dest, options = {})
File.open(dest, 'w') { |file| file.write src }
return 0
end
end
class << File
def readlink(file_name)
return File.read(file_name)
end
end
end
def setup_posix_env
setup_stubs_for_windows_in_posix_env if is_windows?
RbConfig::CONFIG.stubs(:[]).with('host_os').returns('darwin')
ENV.stubs(:[]).with('OS').returns('OSX')
end
def is_windows?
#is_windows ||= ApplicationToTest::Util::OS.is_windows?
end
end
For completeness ApplicationToTest::Util::OS.is_windows?:
require 'rbconfig'
module ApplicationToTest
module Util
module OS
def self.is_windows?
begin
env_os = ENV['OS']
rescue NameError
return false
end
if RbConfig::CONFIG['host_os'] =~ /^mingw2$|^mingw$|^mswin$|^windows$/
true
elsif env_os == 'Windows_NT'
true
else
false
end
end
end
end
end

Spec Testing a Ruby CLI

I am trying to test the first ruby CLI i've written (n00b alert) and need some help. All my code is within 1 file, this includes a Class, OptionParser and some basic class execution methods. Here's an idea of what that looks like
The *rb. file
require 'optparse'
require 'fileutils'
class Foo
attr_accessor :arg, :opt
def initialize(p={})
#opt = p[:opt] || false
end
def do_something(arg)
#arg = arg
end
#more methods...
end
# Options
#options={}
#opt_parser = OptionParser.new do |opt|
opt.banner = "<{ FooBar }>"
opt.separator "------------"
opt.on("-o", "--opt", "An Option" do
#options[:opt] = true
end
end
#opt_parser.parse!
#CLI Execution
#foo = Foo.new(#options)
#foo.do_something(ARGV[0])
So here is the problem, i know would like to run some rspec tests rspec spec/ that i've wrote for the class, however the lines outside the class get executed of course and im left with an ARGV error.
What im looking for
Is there a better way to organize my code so i can test all the pieces, or how could i write a test to accommodate this file, Any and all suggestions would be greatly appreciated.
One posible solution is to wrap your option parsing code with a conditional that checks if the file is being run directly or loaded by some other file.
if __FILE__ == $0
# option parsing code
end
If you do that then all the code inside the if __FILE__ == $0 will not run with your test, but the rest of the code will run normally.

Embed RSpec test in a Ruby class

I often build little single-purpose Ruby scripts like this:
#!/usr/bin/env ruby
class Widget
def end_data
DATA.read
end
def render_data source_data
source_data.upcase
end
end
w = Widget.new
puts w.render_data(w.end_data)
__END__
data set to work on.
I'd like to include RSpec tests directly inside the file while I'm working on it. Something like this (which doesn't work but illustrates what I'm trying to do):
#!/usr/bin/env ruby
class Widget
def end_data
DATA.read
end
def render_data source_data
source_data.upcase
end
def self_test
# This doesn't work but shows what I'm trying to
# accomplish. The goal is to have RSpec run these type
# of test when self_test is called.
describe "Widget" do
it "should render data properly" do
#w = Widget.new
expect(#w.render_data('test string')).to eq 'TEST STRING'
end
end
end
end
w = Widget.new
w.self_test
__END__
data set to work on.
I understand this is not the normal way to work with RSpec and isn't appropriate in most cases. That said, there are times when it would be nice. So, I'd like to know, is it possible?
There are two things. First off rspec by default won't pollute the global namespace with methods like describe and so on. The second thing is that you need to tell rspec to run the specs after they've been declared.
If you change your self_test method to be
RSpec.describe "Widget" do
it "should render data properly" do
#w = Widget.new
expect(#w.render_data('test string')).to eq 'TEST STRING'
end
end
RSpec::Core::Runner.invoke
(having of course done require 'rspec' then that will run your specs).
The invoke methods exits the process after running the specs. If you don't want to do that, or need more control over where output goes etc. you might want to drop down to the run method which allows you to control these things.

cattr_accessor outside of rails

I'm trying to use the google_search ruby library (code follows) but it complains that 'cattr_accessor is an undefined method' - any ideas why this might be or how I could fix it?
require 'rubygems'
require 'google_search'
GoogleSearch.web :q => "pink floyd"
cattr_accessor seems to be a Rails extension that acts like attr_accessor, but is accessible on both the class and its instances.
If you want to copy the source of the cattr_accessor method, check out this documentation:
# File vendor/rails/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb, line 46
def cattr_accessor(*syms)
cattr_reader(*syms)
cattr_writer(*syms)
end
# File vendor/rails/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb, line 4
def cattr_reader(*syms)
syms.flatten.each do |sym|
next if sym.is_a?(Hash)
class_eval("unless defined? ##\#{sym}\n##\#{sym} = nil\nend\n\ndef self.\#{sym}\n##\#{sym}\nend\n\ndef \#{sym}\n##\#{sym}\nend\n", __FILE__, __LINE__)
end
end
# File vendor/rails/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb, line 24
def cattr_writer(*syms)
options = syms.extract_options!
syms.flatten.each do |sym|
class_eval("unless defined? ##\#{sym}\n##\#{sym} = nil\nend\n\ndef self.\#{sym}=(obj)\n##\#{sym} = obj\nend\n\n\#{\"\ndef \#{sym}=(obj)\n##\#{sym} = obj\nend\n\" unless options[:instance_writer] == false }\n", __FILE__, __LINE__)
end
end
You can get this functionality by including the Ruby Facets gem. Reference the source here:
https://github.com/rubyworks/facets/blob/master/lib/core/facets/cattr.rb
You generally don't need to require all code from the gem. You can selectively require what you want. There are quite a few useful extensions in the gem though.

Resources