Printing the source code of a Ruby block - ruby

I have a method that takes a block.
Obviously I don't know what is going to be passed in and for bizarre reasons that I won't go into here I want to print the contents of the block.
Is there a way to do this?

You can do this with Ruby2Ruby which implements a to_ruby method.
require 'rubygems'
require 'parse_tree'
require 'parse_tree_extensions'
require 'ruby2ruby'
def meth &block
puts block.to_ruby
end
meth { some code }
will output:
"proc { some(code) }"
I would also check out this awesome talk by Chris Wanstrath of Github http://goruco2008.confreaks.com/03_wanstrath.html He shows some interesting ruby2ruby and parsetree usage examples.

In Ruby 1.9+ (tested with 2.1.2), you can use https://github.com/banister/method_source
Print out the source via block#source:
#! /usr/bin/ruby
require 'rubygems'
require 'method_source'
def wait &block
puts "Running the following code: #{block.source}"
puts "Result: #{yield}"
puts "Done"
end
def run!
x = 6
wait { x == 5 }
wait { x == 6 }
end
run!
Note that in order for the source to be read you need to use a file and execute the file (testing it out from irb will result in the following error: MethodSource::SourceNotFoundError: Could not load source for : No such file or directory # rb_sysopen - (irb)

Building on Evangenieur's answer, here's Corban's answer if you had Ruby 1.9:
# Works with Ruby 1.9
require 'sourcify'
def meth &block
# Note it's to_source, not to_ruby
puts block.to_source
end
meth { some code }
My company uses this to display the Ruby code used to make carbon calculations... we used ParseTree with Ruby 1.8 and now sourcify with Ruby 1.9.

In Ruby 1.9, you can try this gem which extract the code from source file.
https://github.com/ngty/sourcify

In Ruby 2.5 the following works
puts block.source

In ruby 2.7, using the method_source gem (pry depends on it)
Set.instance_method(:merge).source.display
# =>
def merge(enum)
if enum.instance_of?(self.class)
#hash.update(enum.instance_variable_get(:#hash))
else
do_with_enum(enum) { |o| add(o) }
end
self
end
The repo says it works for procs, but I haven't tested it.

Related

Upgade to Ruby 3.1 breaks code when using CSV class from standard library

I'm upgrading a Project written for JRuby 1.7 (corresponding on the language level to Ruby 1.9) to JRuby 9.4 (corresponding to Ruby 3.1.0). In this code, we have
require 'csv'
....
CSV.parse(string, csv_options) { .... }
where string is of class String and csv_optionsis of class Hash. This statement produces, when run under the new Ruby version, the error
ArgumentError:
wrong number of arguments (given 2, expected 1)
I found in the Ruby docs the following difference in the definition of parse:
Old version:
def parse(str, options={}, &block)
New version
def parse(str, **options, &block)
I understand that in the new Ruby, I would have to invoke parse as
CSV.parse(string, **csv_options) {....}
However, I would like to keep the code compatible for both versions, at least for some transition period, but the old JRuby does not understand **variable (I would get a syntax error, unexpected tPOW).
Is there a way to write the invocation of CSV.parse in such a way, that it preserves the original semantics and can run under Ruby 1.9 and Ruby 3.1? Currently the best solution for this problem which I can think of, is to write something like turning the block argument into a proc and writing
if RUBY_VERSION < '2'
CSV.parse(string, csv_options, &myproc)
else
# Without the eval, the compiler would complain about
# the ** when compiled with Ruby 1.9
eval "CSV.parse(string, **csv_options, &myproc)"
end
which looks pretty awful.
Not sure exactly what you are passing as csv_options but all versions can handle this using an a combination of implicit Hash/kwargs. e.g.
CSV.parse(string, col_sep: '|', write_headers: false, quote_empty: true) { ... }
If this is not an option then you going to need to patch the CSV class to make it backwards compatible e.g.
csv_shim.rb
# Shim CSV::parse to mimic old method signature
# while supporting Ruby 3 kwargs argument passing
module CSVShim
def parse(string, options={}, &block)
super(string, **options, &block)
end
end
CSV.singleton_class.prepend(CSVShim)
Then you can modify as:
require 'csv'
require 'csv_shim.rb' if RUBY_VERSION > '2.6.0'
#...
CSV.parse(string, csv_options) { .... }

Ruby Script: undefined method `symbolize_keys' error loading YAML files

I have a ruby script for yaml merging as follows
#!/usr/bin/env ruby
require 'yaml'
raise "wrong number of parameters" unless ARGV.length == 2
y1 = YAML.load_file(ARGV[0]).symbolize_keys
y2 = YAML.load_file(ARGV[1]).symbolize_keys
puts y1.merge!(y2).to_yaml
when I execute it:
./test.rb ./src/api/config/config1.yml ./src/api/config/config2.yml
I've got the following error:
./test.rb:5:in `<main>': undefined method `symbolize_keys' for {"root"=>{"cloud.n2"=>{"accessKey"=>"I5VAJUYNR4AAKIZDH777"}}}:Hash (NoMethodError)
Hash#symbolize_keys method comes from activesupport gem (activesupport/lib/active_support/core_ext/hash/keys.rb).
In order to use it, you need to add the following line to your script:
require "active_support"
While the other answers/comments are correct it seems like overkill to require all of ActiveSupport for this. Instead either use:
require 'active_support/core_ext/hash/keys'
Or if you have control over the yml files then just make the keys symbols there and avoid any transformation. For Example
require 'yaml'
yml = <<YML
:root:
:cloud.n2:
:accessKey: "I5VAJUYNR4AAKIZDH777"
YML
YAML.load(yml)
#=> {:root=>{:"cloud.n2"=>{:accessKey=>"I5VAJUYNR4AAKIZDH777"}}}
This does not really the answer your question, but Ruby 2.5.0 introduced Hash#transform_keys (release notes) which also can be used to symbolize keys and is in core Ruby.
{'a' => 1, 'b' => 2}.transform_keys(&:to_sym)
#=> {:a=>1, :b=>2}
There is also a bang version which mutates the hash instead of creating a new one.
As other have already noted, symbolize_keys is an ActiveSupport method. If you are not using ActiveSupport, and/or on a pre-2.5 version of Ruby that does not include transform_keys, you could define it yourself.
class Hash
def transform_keys
return enum_for(:transform_keys) unless block_given?
result = self.class.new
each_key do |key|
result[yield(key)] = self[key]
end
result
end
def transform_keys!
return enum_for(:transform_keys!) unless block_given?
keys.each do |key|
self[yield(key)] = delete(key)
end
self
end
def symbolize_keys
transform_keys{ |key| key.to_sym rescue key }
end
def symbolize_keys!
transform_keys!{ |key| key.to_sym rescue key }
end
end
This is not to say that there are not likely other dependencies on Rails or ActiveSupport that will be required for your script.

Mocha expect failing with Thor 0.19.1

After upgrading Thor from 0.18.1 to 0.19.1, I'm seeing a weird failure of Mocha (using test-unit)
/gems/2.1.0/gems/test-unit-2.5.5/lib/test/unit/ui/console/testrunner.rb:395:
in `output': unexpected invocation: #<IO:0x7fd6c986ab58>.puts() (Mocha::ExpectationError)
unsatisfied expectations:
- expected exactly once, not yet invoked: #<IO:0x7fd6c986ab58>.puts('1.0.0')
from rbenv/versions/2.1.1/lib/ruby/gems/2.1.0/gems/test-unit-2.5.5/lib/test/unit/ui/console/testrunner.rb:389:in `nl'
Code:
def version
say VERSION
end
Test:
def test_should_print_version
$stdout.expects(:puts).with(VERSION)
App::CLI.start %W(version)
end
Interestingly, $stdout.expects(:print).with(VERSION + "\n") works without issues. I'm using ruby 2.1.1p76
It seems that the first exception is due to puts being called and the second is due to it not being called. Should I be using expect in a different way?
I worked around the issue by redirecting $stdout to an instance of StringIO as suggested here
require 'stringio'
module Kernel
def capture_stdout
out = StringIO.new
$stdout = out
yield
return out
ensure
$stdout = STDOUT
end
end
Updated test:
def test_should_print_version
#$stdout.expects(:puts).with(VERSION)
out = capture_stdout { App::CLI.start %W(version) }
assert_match VERSION, out.string
end

How does Rubygem require all gems? [duplicate]

This question already has an answer here:
How can I debug require in Ruby 1.9
(1 answer)
Closed 8 years ago.
I got interested in Rubygem, and started to explore how does it works, and found out that after 1.9, Rubygem's require became THE require.
With the code below:
require 'debugger'
debugger
require 'thor'
I started to n and l and s, but got stuck at:
# specification.rb:263
def self._all # :nodoc:
unless defined?(##all) && ##all then
specs = {}
self.dirs.each { |dir|
Dir[File.join(dir, "*.gemspec")].each { |path|
spec = Gem::Specification.load path.untaint
# #load returns nil if the spec is bad, so we just ignore
# it at this stage
specs[spec.full_name] ||= spec if spec
}
}
##all = specs.values
_resort!
end
##all
end
It seems that before stepping into the method above, ##all has already be been prepared. Then I set break-points everywhere ##all =, but none of the break-points are reached.
What am i missing???
EDIT:
Look at my question again. See require 'debugger'? I feel like a fool.
Now the question is "How can I debug require"?
CLOSED:
Plz see this great answer:https://stackoverflow.com/a/16069229/342366
Thx again for this greet answer:https://stackoverflow.com/a/16069229/342366, i did a little debugging myself.
For some one google to this question(and lazy to debug), i decide to post the answer to "How does Rubygem require all gems?"
There are the key-steps below:
load all ".gemspec" in "Ruby193\lib\ruby\gems\1.9.1\specifications"
Dir[File.join(dir, "*.gemspec")].each { |path|
spec = Gem::Specification.load path.untaint
# #load returns nil if the spec is bad, so we just ignore
# it at this stage
specs[spec.full_name] ||= spec if spec
}
sort the gems by version desc
##all.sort! { |a, b|
names = a.name <=> b.name
next names if names.nonzero?
b.version <=> a.version
}
got the first gem (the higher version)
self.find { |spec|
spec.contains_requirable_file? path
}
activate the gem and all dependencies~ Everyone's happy.

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