How to extract the code from a Proc object? - ruby

Given a Proc object, is it possible to look at the code inside it?
For example:
p = Proc.new{test = 0}
What I need is for some way to get the string "test = 0" from a Proc object that has already been created.

You can use the ruby2ruby library:
>> # tested with 1.8.7
>> require "parse_tree"
=> true
>> require "ruby2ruby"
=> true
>> require "parse_tree_extensions"
=> true
>> p = Proc.new{test = 0}
>> p.to_ruby
=> "proc { test = 0 }"
You can also turn this string representation of the proc back to ruby and call it:
>> eval(p.to_ruby).call
0
More about ruby2ruby in this video: Hacking with ruby2ruby.

In case you're using Ruby 1.9, you can use the sourcify gem
$ irb
ruby-1.9.2-p0 > require 'sourcify'
=> true
ruby-1.9.2-p0 > p = Proc.new{test = 0}
=> #<Proc:0xa4b166c#(irb):2>
ruby-1.9.2-p0 > p.to_source
=> "proc { test = 0 }"

Use proc.source_location to get the location of the source file that defines the proc.
It also returns the line number of the definition.
You can use those values to locate the location of the proc source.

I think you could use ParseTree for this, it also seems that support for Ruby 1.9.2 is getting close.

Related

TOPLEVEL_BINDING difference in irb and script

If i put the following code:
a = 42
p TOPLEVEL_BINDING.local_variable_defined?(:a)
in a file "rubyScratch.rb" and ruby it using
ruby rubyScratch.rb
I get
true
However in irb I get
2.3.1 :001 > a = 42
=> 42
2.3.1 :002 > TOPLEVEL_BINDING.local_variable_defined?(:a)
=> false
Why is there this difference?
This is because the irb command (on my system, anyways) runs a small ruby script that looks like this:
#!/usr/bin/env ruby
#
# irb.rb - interactive ruby
# $Release Version: 0.9.6 $
# $Revision: 40560 $
# by Keiju ISHITSUKA(keiju#ruby-lang.org)
#
require "irb"
IRB.start(__FILE__)
So, the TOPLEVEL_BINDING is this script and not your IRB context.
While looking for some more information, I ran across this short article which states:
It is, as its name suggest, the Binding of your script's main scope:
a = 42
p binding.local_variable_defined?(:a) # => true
p TOPLEVEL_BINDING.local_variable_defined?(:a) # => true
def example_method
p binding.local_variable_defined?(:a) # => false
p TOPLEVEL_BINDING.local_variable_defined?(:a) # => true
end
example_method
To summarize, the TOPLEVEL_BINDING is the binding for the first script in the current context that was run by the Ruby VM. When running IRB, that script is the one that starts the IRB session.

Accessing i3-wm JSON response obj from i3ipc-glib through gir_ffi gem

I am looking to control my i3 window manager through a ruby script. The existing gem is no longer maintained, and it is recommended that you use the i3ipc-glib C library in conjunction with gir_ffi to replicate its function. (The gir_ffi gem "creates bindings for GObject-based libraries at runtime based on introspection data provided by the GObject Introspection Repository (GIR) system").
I can successfully use it to send commands thusly:
require 'gir_ffi'
namespace = 'i3ipc'
GirFFI.setup namespace
i3 = I3ipc::Connection.new(nil)
i3.command 'focus right'
I do not however receive the JSON reply outlined in the i3 IPC documentation. Instead I get a GLib::Slist (doc) object (or in some cases I3ipc::Con).
2.1.2 :108 > command = i3.command('focus left')
=> #<GLib::SList:0x000000019f70d0 #struct=#<GLib::SList::Struct:0x000000019f7058>, #element_type=I3ipc::CommandReply>
2.1.2 :111 > command.class
=> GLib::SList
2.1.2 :112 > command.class.superclass
=> GirFFI::StructBase
2.1.2 :094 > workspaces = i3.get_workspaces
=> #<GLib::SList:0x00000001ab6ae8 #struct=#<GLib::SList::Struct:0x00000001ab6ac0>, #element_type=I3ipc::WorkspaceReply>
2.1.2 :095 > workspaces.class
=> GLib::SList
2.1.2 :035 > tree = i3.get_tree
=> #<I3ipc::Con:0x000000013c4a78 #struct=#<I3ipc::Con::Struct:0x000000013c51f8>>
2.1.2 :036 > tree.class
=> I3ipc::Con
2.1.2 :037 > tree.class.superclass
=> GObject::Object
2.1.2 :038 > tree.class.superclass.superclass
=> GirFFI::ObjectBase
How can I access the original JSON response?
You may try an improved i3ipc gem:
>> require 'i3ipc' #=> true
>> i3 = I3Ipc::Connection.new
=> #<I3Ipc::Connection:0x000000009fc068 #protocol=# <I3Ipc::Protocol:0x000000009fc040 #socketpath="/run/user/1000/i3/ipc-socket.1043", #socket=#<UNIXSocket:fd 9>>>
>> command = i3.command('focus left') #=> [#<I3Ipc::Reply:0x007fbf90145940 #data={:success=>true}>]
>> command[0].to_h #=> {:success=>true}
>> workspaces = i3.workspaces
=> [#<I3Ipc::Reply:0x007fbf900e77f0 #data={:num=>1, :name=>"1 Browse", :visible=>true, :focused=>false, :rect=>#<I3Ipc::Reply:0x007fbf900ffd78 #data={:x=>1366, :y=>20, :width=>1920, :height=>1060}>, :output=>"VGA1", :urgent=>false}>, #<I3Ipc::Reply:0x007fbf900e6e68 #data={:num=>0, :name=>"0 Term", :visible=>true, :focused=>true, :rect=>#<I3Ipc::Reply:0x007fbf900e7458 #data={:x=>0, :y=>20, :width=>1366, :height=>748}>, :output=>"LVDS1", :urgent=>false}>]
>> workspaces[0].to_h #=> {:num=>1, :name=>"1 Browse", :visible=>true, :focused=>false, :rect=>{:x=>1366, :y=>20, :width=>1920, :height=>1060}, :output=>"VGA1", :urgent=>false}
>> workspaces[0].visible
>> workspaces[0].name #=> "1 Browse"
This is not actually a json, but a parsed hash. But there is a way to get an original string response with low level class:
>> protocol = I3Ipc::Protocol.new #=> #<I3Ipc::Protocol:0x007fbf9c08aee0 #socketpath="/run/user/1000/i3/ipc-socket.1043">
>> protocol.connect #=> #<UNIXSocket:fd 10>
# 1 means get_workspaces message
>> protocol.send(1) #=> 14
>> response = protocol.receive(1)
=> "[{\"num\":1,\"name\":\"1 Browse\",\"visible\":true,\"focused\":false,\"rect\":{\"x\":1366,\"y\":20,\"width\":1920,\"height\":1060},\"output\":\"VGA1\",\"urgent\":false},{\"num\":0,\"name\":\"0 Term\",\"visible\":true,\"focused\":true,\"rect\":{\"x\":0,\"y\":20,\"width\":1366,\"height\":748},\"output\":\"LVDS1\",\"urgent\":false}]"

What happens to the object I assign to $stdout in Ruby? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
EDIT: Don't bother reading this question, I just can't delete it. It's based on broken code and there's (almost) nothing to learn here.
I am redirecting console output in my Ruby program and although it works perfectly there is one thing I'm curious about:
Here's my code
capture = StringIO.new
$stdout = capture
puts "Hello World"
It looks like even though I'm assigning my capture object to $stdout, $stdout contains a new and different object after the assignment, but at least the type is correct.
In other words:
$stdout.to_s # => #<IO:0x2584b30>
capture = StringIO.new
$stdout = capture
$stdout.to_s # => #<StringIO:0x4fda948>
capture.to_s # => #<StringIO:0x4e3b220>
Subsequently $stdout.string contains "Hello World", but capture.string is empty.
Is there something happening behind the scenes or am I missing something here?
EDIT: This might be specific to certain versions only. I'm using Ruby 2.0.0-p247 on Windows 8.1
It works as expected.
>> capture = StringIO.new
=> #<StringIO:0x00000001ea8c00>
>> $stdout = capture
>> $stdout.to_s
>> capture.to_s
Above two line does not print anything because $stdout is now disconnected from terminal.
So I used $stderr.puts in following lines (can also use STDOUT.puts as Stefan commented):
>> $stderr.puts $stdout.to_s
#<StringIO:0x00000001ea8c00>
>> $stderr.puts capture.to_s
#<StringIO:0x00000001ea8c00>
$stdout.to_s, capture.to_s give me same result.
I used ruby 1.9.3. (Same for 2.0.0)
Are you sure there is no other manipulation of $stdout or capturehappening in between?
For me, output looks different. Both capture and $stdout are the same object and subsequently answer to string with the same response (ruby 1.9.2):
require 'stringio'
$stdout.to_s # => #<IO:0x2584b30>
capture = StringIO.new
$stdout = capture
puts $stdout.to_s # => #<StringIO:0x89a38c0>
puts capture.to_s # => #<StringIO:0x89a38c0>
puts "redirected"
$stderr.puts $stdout.string # => '#<StringIO:0x89a38c0>\n#<StringIO:0x89a38c0>\nredirected'
$stderr.puts capture.string # => '#<StringIO:0x89a38c0>\n#<StringIO:0x89a38c0>\nredirected'
Although this question was the result of overlooking a change to the value of $stdout, Ruby does have the ability to override assignment to global vars in this way, at least in the C api, using hooked variables.
$stdout actually does make use of this to check whether the new value is appropriate (it checks whether the new value responds to write) and raises an exception if it doesn’t.
If you really wanted (you don’t) you could create an extension that defines a global variable that automatically stores a different object than the value assigned, perhaps by called dup on it and using that instead:
#include "ruby.h"
VALUE foo;
static void foo_setter(VALUE val, ID id, VALUE *var){
VALUE dup_val = rb_funcall(val, rb_intern("dup"), 0);
*var = dup_val;
}
void Init_hooked() {
rb_define_hooked_variable("$foo", &foo, 0, foo_setter);
}
You could then use it like:
2.0.0-p247 :001 > require './ext/hooked'
=> true
2.0.0-p247 :002 > s = Object.new
=> #<Object:0x00000100b20560>
2.0.0-p247 :003 > $foo = s
=> #<Object:0x00000100b20560>
2.0.0-p247 :004 > s.to_s
=> "#<Object:0x00000100b20560>"
2.0.0-p247 :005 > $foo.to_s
=> "#<Object:0x00000100b3bea0>"
2.0.0-p247 :006 > s == $foo
=> false
Of course this is very similar to simply creating a setter method in a class that dups the vale and stores that, which you can do in plain Ruby:
def foo=(new_foo)
#foo = new_foo.dup
end
Since using global variables is generally bad design, it seems reasonable that this isn’t possible in Ruby for globals.

Convert Ruby object into HTML string with awesome_print

I'm trying to prettify an Ruby object using awesome_print so I can place this string inside an email and send it off. So in terms of code, (I know this is wrong), but here's what I'm trying to achieve:
my_str = (ap error.object).to_str
# Do something with my_str, like stick it in a <pre> tag inside an html email.
How do I convert the output from ap to string? Reason I'm asking is as I noticed, ap seems to only return the object.
It doesn't seem to be documented in the README.md, but if you look at the Kernel modifications the library makes here: https://github.com/michaeldv/awesome_print/blob/master/lib/awesome_print/core_ext/kernel.rb
You can see that in addition to the ap method, the awesome_print gem also adds an ai method to all objects.
1.9.3p392 :001 > require 'awesome_print'
=> true
1.9.3p392 :002 > test = {a: "b"}
=> {:a=>"b"}
1.9.3p392 :003 > ap test
{
:a => "b"
}
1.9.3p392 :006 > test.ai
=> "{\n :a\e[0;37m => \e[0m\e[0;33m\"b\"\e[0m\n}"
1.9.3p392 :007 > test.ai(html:true)
=> "<pre>{\n <pre>:a</pre><kbd style=\"color:slategray\"> => </kbd><pre><kbd style=\"color:brown\">"b"</kbd></pre>\n}</pre>"
That said, the output formatting might not be that useful (the html version adds a ton of whitespace, and the non-html version has the weird terminal coloring characters), and being an undocumented feature, it's liable to break without warning in a minor version update.
The other thing worth noting in the kernel.rb above is that ap and ai have aliases: awesome_print and awesome_inspect.
awesomeprint is meant for printing ASCII colors and stuff, not HTML. What I'd use is pygments gem:
# gem install pygments.rb
require 'pygments'
str = <<EOT
# This is an awesome comment on my rb script
a = 2
puts a
hsh = {asdf: 1, qwer: 2, uiop: 3}
EOT
Pygments.highlight str
https://github.com/tmm1/pygments.rb

ruby string splitting problem

i have this string:
"asdasda=asdaskdmasd&asmda=asdasmda&ACK=Success&asdmas=asdakmsd&asmda=adasda"
i want to get the value after between the ACK and the & symbol, the value between the ACK and the & symbol can be changed...
thanks
i want the solution in ruby.
require "cgi"
query_string = "asdasda=asdaskdmasd&asmda=asdasmda&ACK=Success&asmda=asdakmsd"
parsed_query_string = CGI.parse(query_string)
#=> { "asdasda" => ["asdaskdmasd"],
# "asmda" => ["asdasmda", "asdakmsd"],
# "ACK" => ["Success"] }
parsed_query_string["ACK"].first
#=> "Success"
If you also want to reconstruct the query string (especially together with the rest of a URL), I would recommend looking into the addressable gem.
require "addressable/uri"
# Note the leading '?'
query_string = "?asdasda=asdaskdmasd&asmda=asdasmda&ACK=Success&asmda=asdakmsd"
parsed_uri = Addressable::URI.parse(query_string)
parsed_uri.query_values["ACK"]
#=> "Success"
parsed_uri.query_values = parsed_uri.query_values.merge("ACK" => "Changed")
parsed_uri.to_s
#=> "?ACK=Changed&asdasda=asdaskdmasd&asmda=asdakmsd"
# Note how the order has changed and the duplicate key has been removed due to
# Addressable's built-in normalisation.
"asdasda=asdaskdmasd&asmda=asdasmda&ACK=Success&asdmas=asdakmsd&asmda=adasda"[/ACK=([^&]*)&/]
$1 # => 'Success'
A quick approach:
s = "asdasda=asdaskdmasd&asmda=asdasmda&ACK=Success&asdmas=asdakmsd&asmda=adasda"
s.gsub(/ACK[=\w]+&/,"ACK[changedValue]&")
#=> asdasda=asdaskdmasd&asmda=asdasmda&ACK[changedValue]&asdmas=asdakmsd&asmda=adasda
s = "asdasda=asdaskdmasd&asmda=asdasmda&ACK=Success&asdmas=asdakmsd&asmda=adasda"
m = s.match /.*ACK=(.*?)&/
puts m[1]
and just for fun without regexp:
Hash[s.split("&").map{|p| p.split("=")}]["ACK"]

Resources