TOPLEVEL_BINDING difference in irb and script - ruby

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.

Related

How to change to color of the Command Prompt [duplicate]

Previously I was using Ruby 1.8 and my irb command prompt used to look like this:
Air ~: irb
>> a = 1
=> 1
>> b = 2
=> 2
>> a + b
=> 3
I installed rvm (and Ruby 1.9.2) and now my irb command prompt looks like this:
Air ~: irb
ruby-1.9.2-p180 :001 > a = 1
=> 1
ruby-1.9.2-p180 :002 > b = 2
=> 2
ruby-1.9.2-p180 :003 > a + b
=> 3
Is there a way to remove the ruby-1.9.2-p180 :001 from the command line?
The irb man page has a section on "Customizing prompt". Here's mine for example:
IRB.conf[:PROMPT][:CUSTOM] = {
:PROMPT_I => ">> ",
:PROMPT_S => "%l>> ",
:PROMPT_C => ".. ",
:PROMPT_N => ".. ",
:RETURN => "=> %s\n"
}
IRB.conf[:PROMPT_MODE] = :CUSTOM
IRB.conf[:AUTO_INDENT] = true
To use this, add it to your ~/.irbrc file (creating it if it doesn't exist.)
In your ~/.irbrc, simply add
IRB.conf[:PROMPT_MODE] = :SIMPLE
When you would usually run the irb command, try running irb --simple-prompt instead. That greatly shortens the prompt and makes it easier to understand.
irb --simple-prompt
saw this in Lynda.com
To avoid giving the prompt you wish on the command line all the time, you can configure the prompt via the ~/.irbrc config file:
$ echo "IRB.conf[:PROMPT_MODE] = :DEFAULT" > ~/.irbrc
$ irb
irb(main):001:0> quit
$ echo "IRB.conf[:PROMPT_MODE] = :SIMPLE" > ~/.irbrc
$ irb
>> quit
$
Whoever want to add a prompt timestamp, this isn't possible yet (check "special strings" section), so I implemented it in a monkey-patchy way:
module IrbTimePrompt
def prompt(prompt, ltype, indent, line_no)
# I used %T as time format, but you could use whatever you want to.
# Check https://apidock.com/ruby/Time/strftime for more options
p = prompt.dup.gsub(/%t/, Time.new.strftime('%T'))
super(p, ltype, indent, line_no)
end
end
module IRB
class Irb
prepend IrbTimePrompt
end
end
Now, add this to your lib/ project folder (in case is a Rails project, ensure lib/ is part of config.autoload_paths in config/application.rb) or in a more aggresive way (not recommended), look for lib/irb.rb file in your local ruby instance and in def prompt method, add a new when condition to the method, like:
when "t"
Time.now.strftime('%-d-%-m %T%Z')
then in your .irbrc file (it could be located in your home folder or root project folder) you could modify your prompt. I'm adding my current prompt, but please adjust it to your needs:
def rails_prompt
# This is my base prompt, displaying line number and time
def_prompt = '[%01n][%t]'
# Maybe you're only running as `irb` an not `rails console`, so check first
# if rails is available
if defined? Rails
app_env = Rails.env[0...4]
if Rails.env.production?
puts "\n\e[1m\e[41mWARNING: YOU ARE USING RAILS CONSOLE IN PRODUCTION!\n" \
"Changing data can cause serious data loss.\n" \
"Make sure you know what you're doing.\e[0m\e[22m\n\n"
app_env = "\e[31m#{app_env}\e[0m" # red
else
app_env = "\e[32m#{app_env}\e[0m" # green
end
def_prompt << "(\e[1m#{app_env}\e[22m)" # bold
end
IRB.conf[:PROMPT] ||= {}
IRB.conf[:PROMPT][:WITH_TIME] = {
PROMPT_I: "#{def_prompt}> ",
PROMPT_N: "#{def_prompt}| ",
PROMPT_C: "#{def_prompt}| ",
PROMPT_S: "#{def_prompt}%l ",
RETURN: "=> %s\n",
AUTO_INDENT: true,
}
IRB.conf[:PROMPT_MODE] = :WITH_TIME
end
rails_prompt
Then start irb or rails console and check the awesomeness:
[1][13:01:15](deve)> 'say hello to your new prompt'
=> "say hello to your new prompt"
[2][13:01:23](deve)>
See this note about IRB prompt in RVM.
Note that you can create a .irbrc file in your home folder for various settings for IRB.
For example, see "Configuring the Prompt" in this document
You can also puts IRB.conf[:PROMPT_MODE] or puts IRB.conf to see all the various settings currently in effect. For example, the :PROMPT_MODE is probably set to "RVM" in your case.

Mistaken call to a hash table results in strange ruby irb prompt

I'm running the Ruby irb on a DOS environment.
I've defined a dictionary.
irb(main):001:0> stuff = {'name'=> 'Zed', 'age'=>36, 'height'=>6*12+2}
I've made a mistake in calling it
irb(main):004:0> puts stuff['age]
the ruby prompt changes to an apostrophe ' instead of the usual >
irb(main):006:1'
irb(main):007:1'
IRB doesn't work anymore.
What has happened here and how do I get the shell to function again without quitting the program?
It is waiting for the closing ',that you missed here puts stuff['age]. Use Ctrl+c to get the prompt back,that you are expecting.
See below:
2.0.0p0 :001 > stuff = {'name'=> 'Zed', 'age'=>36, 'height'=>6*12+2}
=> {"name"=>"Zed", "age"=>36, "height"=>74}
2.0.0p0 :002 > puts stuff['age]
2.0.0p0 :003'> ^C
2.0.0p0 :003 >

Creating a thread-safe temporary file name

When using Tempfile Ruby is creating a file with a thread-safe and inter-process-safe name. I only need a file name in that way.
I was wondering if there is a more straight forward approach way than:
t = Tempfile.new(['fleischwurst', '.png'])
temp_path = t.path
t.close
t.unlink
Dir::Tmpname.create
You could use Dir::Tmpname.create. It figures out what temporary directory to use (unless you pass it a directory). It's a little ugly to use given that it expects a block:
require 'tmpdir'
# => true
Dir::Tmpname.create(['prefix-', '.ext']) {}
# => "/tmp/prefix-20190827-1-87n9iu.ext"
Dir::Tmpname.create(['prefix-', '.ext'], '/my/custom/directory') {}
# => "/my/custom/directory/prefix-20190827-1-11x2u0h.ext"
The block is there for code to test if the file exists and raise an Errno::EEXIST so that a new name can be generated with incrementing value appended on the end.
The Rails Solution
The solution implemented by Ruby on Rails is short and similar to the solution originally implemented in Ruby:
require 'tmpdir'
# => true
File.join(Dir.tmpdir, "YOUR_PREFIX-#{Time.now.strftime("%Y%m%d")}-#{$$}-#{rand(0x100000000).to_s(36)}-YOUR_SUFFIX")
=> "/tmp/YOUR_PREFIX-20190827-1-wyouwg-YOUR_SUFFIX"
File.join(Dir.tmpdir, "YOUR_PREFIX-#{Time.now.strftime("%Y%m%d")}-#{$$}-#{rand(0x100000000).to_s(36)}-YOUR_SUFFIX")
=> "/tmp/YOUR_PREFIX-20190827-1-140far-YOUR_SUFFIX"
Dir::Tmpname.make_tmpname (Ruby 2.5.0 and earlier)
Dir::Tmpname.make_tmpname was removed in Ruby 2.5.0. Prior to Ruby 2.4.4 it could accept a directory path as a prefix, but as of Ruby 2.4.4, directory separators are removed.
Digging in tempfile.rb you'll notice that Tempfile includes Dir::Tmpname. Inside you'll find make_tmpname which does what you ask for.
require 'tmpdir'
# => true
File.join(Dir.tmpdir, Dir::Tmpname.make_tmpname("prefix-", nil))
# => "/tmp/prefix-20190827-1-dfhvld"
File.join(Dir.tmpdir, Dir::Tmpname.make_tmpname(["prefix-", ".ext"], nil))
# => "/tmp/prefix-20190827-1-19zjck1.ext"
File.join(Dir.tmpdir, Dir::Tmpname.make_tmpname(["prefix-", ".ext"], "suffix"))
# => "/tmp/prefix-20190827-1-f5ipo7-suffix.ext"
Since Dir::Tmpname.make_tmpname was removed in Ruby 2.5.0, this one falls back to using SecureRandom:
require "tmpdir"
def generate_temp_filename(ext=".png")
filename = begin
Dir::Tmpname.make_tmpname(["x", ext], nil)
rescue NoMethodError
require "securerandom"
"#{SecureRandom.urlsafe_base64}#{ext}"
end
File.join(Dir.tmpdir, filename)
end
Since you only need the filename, what about using the SecureRandom for that:
require 'securerandom'
filename = "#{SecureRandom.hex(6)}.png" #=> "0f04dd94addf.png"
You can also use SecureRandom.alphanumeric
I found the Dir:Tmpname solution did not work for me. When evaluating this:
Dir::Tmpname.make_tmpname "/tmp/blob", nil
Under MRI Ruby 1.9.3p194 I get:
uninitialized constant Dir::Tmpname (NameError)
Under JRuby 1.7.5 (1.9.3p393) I get:
NameError: uninitialized constant Dir::Tmpname
You might try something like this:
def temp_name(file_name='', ext='', dir=nil)
id = Thread.current.hash * Time.now.to_i % 2**32
name = "%s%d.%s" % [file_name, id, ext]
dir ? File.join(dir, name) : name
end

How to format irb command prompt

Previously I was using Ruby 1.8 and my irb command prompt used to look like this:
Air ~: irb
>> a = 1
=> 1
>> b = 2
=> 2
>> a + b
=> 3
I installed rvm (and Ruby 1.9.2) and now my irb command prompt looks like this:
Air ~: irb
ruby-1.9.2-p180 :001 > a = 1
=> 1
ruby-1.9.2-p180 :002 > b = 2
=> 2
ruby-1.9.2-p180 :003 > a + b
=> 3
Is there a way to remove the ruby-1.9.2-p180 :001 from the command line?
The irb man page has a section on "Customizing prompt". Here's mine for example:
IRB.conf[:PROMPT][:CUSTOM] = {
:PROMPT_I => ">> ",
:PROMPT_S => "%l>> ",
:PROMPT_C => ".. ",
:PROMPT_N => ".. ",
:RETURN => "=> %s\n"
}
IRB.conf[:PROMPT_MODE] = :CUSTOM
IRB.conf[:AUTO_INDENT] = true
To use this, add it to your ~/.irbrc file (creating it if it doesn't exist.)
In your ~/.irbrc, simply add
IRB.conf[:PROMPT_MODE] = :SIMPLE
When you would usually run the irb command, try running irb --simple-prompt instead. That greatly shortens the prompt and makes it easier to understand.
irb --simple-prompt
saw this in Lynda.com
To avoid giving the prompt you wish on the command line all the time, you can configure the prompt via the ~/.irbrc config file:
$ echo "IRB.conf[:PROMPT_MODE] = :DEFAULT" > ~/.irbrc
$ irb
irb(main):001:0> quit
$ echo "IRB.conf[:PROMPT_MODE] = :SIMPLE" > ~/.irbrc
$ irb
>> quit
$
Whoever want to add a prompt timestamp, this isn't possible yet (check "special strings" section), so I implemented it in a monkey-patchy way:
module IrbTimePrompt
def prompt(prompt, ltype, indent, line_no)
# I used %T as time format, but you could use whatever you want to.
# Check https://apidock.com/ruby/Time/strftime for more options
p = prompt.dup.gsub(/%t/, Time.new.strftime('%T'))
super(p, ltype, indent, line_no)
end
end
module IRB
class Irb
prepend IrbTimePrompt
end
end
Now, add this to your lib/ project folder (in case is a Rails project, ensure lib/ is part of config.autoload_paths in config/application.rb) or in a more aggresive way (not recommended), look for lib/irb.rb file in your local ruby instance and in def prompt method, add a new when condition to the method, like:
when "t"
Time.now.strftime('%-d-%-m %T%Z')
then in your .irbrc file (it could be located in your home folder or root project folder) you could modify your prompt. I'm adding my current prompt, but please adjust it to your needs:
def rails_prompt
# This is my base prompt, displaying line number and time
def_prompt = '[%01n][%t]'
# Maybe you're only running as `irb` an not `rails console`, so check first
# if rails is available
if defined? Rails
app_env = Rails.env[0...4]
if Rails.env.production?
puts "\n\e[1m\e[41mWARNING: YOU ARE USING RAILS CONSOLE IN PRODUCTION!\n" \
"Changing data can cause serious data loss.\n" \
"Make sure you know what you're doing.\e[0m\e[22m\n\n"
app_env = "\e[31m#{app_env}\e[0m" # red
else
app_env = "\e[32m#{app_env}\e[0m" # green
end
def_prompt << "(\e[1m#{app_env}\e[22m)" # bold
end
IRB.conf[:PROMPT] ||= {}
IRB.conf[:PROMPT][:WITH_TIME] = {
PROMPT_I: "#{def_prompt}> ",
PROMPT_N: "#{def_prompt}| ",
PROMPT_C: "#{def_prompt}| ",
PROMPT_S: "#{def_prompt}%l ",
RETURN: "=> %s\n",
AUTO_INDENT: true,
}
IRB.conf[:PROMPT_MODE] = :WITH_TIME
end
rails_prompt
Then start irb or rails console and check the awesomeness:
[1][13:01:15](deve)> 'say hello to your new prompt'
=> "say hello to your new prompt"
[2][13:01:23](deve)>
See this note about IRB prompt in RVM.
Note that you can create a .irbrc file in your home folder for various settings for IRB.
For example, see "Configuring the Prompt" in this document
You can also puts IRB.conf[:PROMPT_MODE] or puts IRB.conf to see all the various settings currently in effect. For example, the :PROMPT_MODE is probably set to "RVM" in your case.

How to extract the code from a Proc object?

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.

Resources