ruby regex named and group - ruby

I am trying to use a named group in a regex but it doesn't work:
module Parser
def fill(line, pattern)
if /\s#{pattern}\:\s*(\w+)\s*\;/ =~ line
puts Regexp.last_match[1]
#self.send("#{pattern}=", value)
end
if /\s#{pattern}\:\s*(?<value>\w+)\s*\;/ =~ line
puts value
#self.send("#{pattern}=", value)
end
end
end
As you can see I first test my regex then I try to use the same regex with a named group.
class Test
attr_accessor :name, :type, :visible
include Parser #add instance method (use extend if we need class method)
def initialize(name)
#name = name
#type = "image"
#visible = true
end
end
t = Test.new("toto")
s='desciption{ name: "toto.test"; type: RECT; mouse_events: 0;'
puts t.type
t.fill(s, "type")
puts t.type
When I execute this, the first regex work but not the second with the named group.
Here is the output:
./ruby_mixin_test.rb
image
RECT
./ruby_mixin_test.rb:11:in `fill': undefined local variable or method `value' for
#<Test:0x00000001a572c8> (NameError)
from ./ruby_mixin_test.rb:34:in `<main>'

If =~ is used with a regexp literal with named captures, captured strings (or nil) is assigned to local variables named by the capture names.
/(?<lhs>\w+)\s*=\s*(?<rhs>\w+)/ =~ " x = y "
p lhs #=> "x"
p rhs #=> "y"
But - A regexp interpolation, #{}, also disables the assignment.
rhs_pat = /(?<rhs>\w+)/
/(?<lhs>\w+)\s*=\s*#{rhs_pat}/ =~ "x = y"
lhs # undefined local variable
In your case from the below code :
if /\s#{pattern}\:\s*(?<value>\w+)\s*\;/ =~ line
puts value
#self.send("#{pattern}=", value)
end
Look at the line below, you use interpolation
/\s#{pattern}\:\s*(?<value>\w+)\s*\;/ =~ line
~~^
Thus local variable assignment didn't happen and you got the error as you reported undefined local variable or method 'value'.

You have not defined value in the module
if /\s#{pattern}\:\s*(?<value>\w+)\s*\;/ =~ line
puts value # This is not defined anywhere
[..]

Related

Get line number of beginning and end of Ruby method given a ruby file

How can I find the line of the beginning and end of a Ruby method given a ruby file?
Say for example:
1 class Home
2 def initialize(color)
3 #color = color
4 end
5 end
Given the file home.rb and the method name initialize I would like to receive (2,4) which are the beginning and end lines.
Finding the end is tricky. The best way I can think of is to use the parser gem. Basically you'll parse the Ruby code into an AST, then recursively traverse its nodes until you find a node with type :def whose first child is :initialize:
require "parser/current"
def recursive_find(node, &block)
return node if block.call(node)
return nil unless node.respond_to?(:children) && !node.children.empty?
node.children.each do |child_node|
found = recursive_find(child_node, &block)
return found if found
end
nil
end
src = <<END
class Home
def initialize(color)
#color = color
end
end
END
ast = Parser::CurrentRuby.parse(src)
found = recursive_find(ast) do |node|
node.respond_to?(:type) && node.type == :def && node.children[0] == :initialize
end
puts "Start: #{found.loc.first_line}"
puts "End: #{found.loc.last_line}"
# => Start: 2
# End: 4
P.S. I would have recommended the Ripper module from the standard library, but as far as I can tell there's no way to get the end line out of it.
Ruby has a source_location method which gives you the file and the beginning line:
class Home
def initialize(color)
#color = color
end
end
p Home.new(1).method(:initialize).source_location
# => ["test2.rb", 2]
To find the end, perhaps look for the next def or EOF.
Ruby source is nothing but a text file. You can use linux commands to find the method line number
grep -nrw 'def initialize' home.rb | grep -oE '[0-9]+'
I have assumed that the file contains the definition of at most one initialize method (though generalizing the method to search for others would not be difficult) and that the definition of that method contains no syntax errors. The latter assumption is probably required for any method to extract the correct line range.
The only tricky part is finding the line containing end that is the last line of the definition of the initialize method. I've used Kernel#eval to locate that line. Naturally caution must be exercised whenever that method is to be executed, though here eval is merely attempting to compile (not execute) a method.
Code
def get_start_end_offsets(fname)
start = nil
str = ''
File.foreach(fname).with_index do |line, i|
if start.nil?
next unless line.lstrip.start_with?('def initialize')
start = i
str << line.lstrip.insert(4,'_')
else
str << line
if line.strip == "end"
begin
rv = eval(str)
rescue SyntaxError
nil
end
return [start, i] unless rv.nil?
end
end
end
nil
end
Example
Suppose we are searching a file created as follows1.
str = <<-_
class C
def self.feline
"cat"
end
def initialize(arr)
#row_sums = arr.map do |row|
row.reduce do |t,x|
t+x
end
end
end
def speak(sound)
puts sound
end
end
_
FName = 'temp'
File.write(FName, str)
#=> 203
We first search for the line that begins (after stripping leading spaces) "def initialize". That is the line at index 4. The end that completes the definition of that method is at index 10. We therefore expect the method to return [4, 10].
Let's see if that's what we get.
p get_start_end_offsets(FName)
#=> [4, 10]
Explanation
The variable start equals the index of the line beginning def initialize (after removing leading whitespace). start is initially nil and remains nil until the "def initialize" line is found. start is then set to the index of that line.
We now look for a line line such that line.strip #=> "end". This may or may not be the end that terminates the method. To determine if it is we eval a string that contains all lines from the one that begins def initialize to the line equal to end just found. If eval raises a SyntaxError exception that end does not terminate the method. That exception is rescued and nil is returned. eval will return :_initialize (which is truthy) if that end terminates the method. In that case the method returns [start, i], where i is the index of that line. nil is returned if no initialize method is found in the file.
I've converted "initialize" to "_initialize" to suppress the warning (eval):1: warning: redefining Object#initialize may cause infinite loop)
See both answers to this SO question to understand why SyntaxError is being rescued.
Compare indentation
If it is known that "def initialize..." is always indented the same amount as the line "end" that terminates the method definition (and no other lines "end" between the two are indented the same), we can use that fact to obtain the beginning and ending lines. There are many ways to do that; I will use Ruby's somewhat obscure flip-flop operator. This approach will tolerate syntax errors.
def get_start_end_offsets(fname)
indent = -1
lines = File.foreach(fname).with_index.select do |line, i|
cond1 = line.lstrip.start_with?('def initialize')
indent = line.size - line.lstrip.size if cond1
cond2 = line.strip == "end" && line.size - line.lstrip.size == indent
cond1 .. cond2 ? true : false
end
return nil if lines.nil?
lines.map(&:last).minmax
end
get_start_end_offsets(FName)
#=> [4, 10]
1 The file need not contain only code.

Ruby interpreted variables is_a?

I am looking to check a variabe for its type based on the value held in another variable but am struggling with it. I'm completely new to ruby but can anyone tell me how to have the value of a variable interpreted in the expression? My current code looks like:-
if variable.is_a?("#{variable_type}")
puts variable
end
Where variable could contain anything and variable_type contains the type of a variable like String or Fixnum. But currently this code gives me TypeError: Class or module required. Any thoughts?
Your code sends a String object to the #is_a? method and the #is_a method expects a Class.
For example, String vs. "String":
variable = "Hello!"
variable_type = String
"#{variable_type}" # => "String"
# your code:
if variable.is_a?("#{variable_type}")
puts variable
end
#is_a? expects the actual Class (String, Fixnum, etc') - as you can see in the documentation for #is_a?.
You can adjust your code in two ways:
pass the Class, without the string.
convert the string to the class using Module.const_get.
here is an example:
variable = "Hello!"
variable_type = String
"#{variable_type}" # => "String"
# passing the actual class:
if variable.is_a?(variable_type)
puts variable
end
# or,
# converting the string to a the type:
if variable.is_a?( Module.const_get( variable_type.to_s ) )
puts variable
end
Just a little example:
variable = 1
variable_type = String
puts variable if variable.is_a?(variable_type)
#=> nil
variable_type = Integer
puts variable if variable.is_a?(variable_type)
#=> 1
Or when your variable_type is a string:
variable_type = 'Integer'
puts variable if variable.is_a?(Object.const_get(variable_type))
#=> 1
TypeError: Class or module required
It means, that to use is_a? varibale_type should hold a class name (any).
Therefore if you hold anything else except for class name in variable_type it will give you this error.
a = :a
variable_type = Symbol
a if a.is_a? variable_type
# => :a
If variable type is a string, you will have to use Module#const_get:
variable_type = 'Symbol'
a if a.is_a? Object.const_get(variable_type)
# => :a

How to use if statements in a class in Ruby?

My if statement did not work and was wondering if I could please get some help. I would type in "FnPrint" and my if statement would not work.
puts "hi"
g = gets()
# Class for print command
x = "FnPrint"
class Fnprint
def Print
if x = g
puts "it worked"
else
puts "no"
end
end
end
Fnprint.new.Print
I kept getting this when I tried to run it:
lang.rb:9:in `Print': undefined local variable or method `g' for #<Fnprint:0x007f9379939040> (NameError)
from lang.rb:17:in `<main>'
You are doing many incorrect stuffs here
1) Incorrect comparison operator = instead of ==
2) Trying to access a vaiables g and x which are outside the scope of the class.
3) Your method name is a constant (In Ruby anything that starts with a capital letter is constant). The method name should be all downcase and seperated with _ in case of mutiword.
class FnPrint
def print(x)
g = gets.strip
if x == g
puts 'it worked'
else
puts 'no'
end
end
end
fn_print_object = FnPrint.new
fn_print_object.print('FnPrint')
Please use the following:
if x == g
when you do
if x = g
you simply tell ruby to assign the value of g to x.
You are also trying to access variables out of the Print method scope. Please consider refactoring your code as follow:
class FnPrint
def print(x)
if x == gets().strip
puts "it worked"
else
puts "no"
end
end
end
puts "hi"
FnPrint.new.print('FnPrint')
Couple points to understand:
String#strip remove leading and trailing whitespace.
method name should be lowercase
Class name should be camelcased (FnPrint, FooBar, Foo, ...)
print is an instance method of class FnPrint
FnPrint.new create a new instance of class FnPrint so you can call print on it
if x = g #=> is Assignment
if x == g #=> is Comparison
if x=g
Is actually an assignment and will return the value of g to the if statement.
Use the comparison operator ==
if x=g assigns g to x and then assesses whether this is truthy.
if x == g assesses whether x and g are equal.
You probably meant to write something like this:
puts 'hi'
#
class Fnprint
def print
x = 'FnPrint'
g = gets.strip
if x == g
puts 'it worked'
else
puts 'no'
end
end
end
Fnprint.new.print

Memorize gets.chomp

I'm trying to make a text editor in Ruby, but I don't know how to memorize an input with gets.chomp.
Here is my code so far:
outp =
def tor
text = gets.chomp
outp = "#{outp}" += "#{text}"
puts outp
end
while true
tor
end
Ordinary variables , like outp, in a method are only visible (AKA have scope) inside that method.
a = "aaa"
def x
puts a
end
x # =>error: undefined local variable or method `a' for main:Object
Why is that? For one thing, if you are writing a method and you need a counter, you can use a variable named i (or whatever) without worrying about other variables named i outside your method.
But... you want to interact with an outside variable in your method! This is one way:
#outp = "" # note the "", initializing #output to an empty string.
def tor
text = gets.chomp
#outp = #outp + text #not "#{#output}"+"#{text}", come on.
puts #outp
end
while true
tor
end
The # gives this variable a greater visisbility (scope).
This is another way: pass the variable as an argument. It is as saying to your method: "Here, work with this.".
output = ""
def tor(old_text)
old_text + gets.chomp
end
loop do #just another way of saying 'while true'
output = tor(output)
puts output
end

Putting a variable name = value format in Ruby

I would like to add some debugs for my simple ruby functions and I wrote a function as below,
def debug(&block)
varname = block.call.to_s
puts "#{varname} = #{eval(varname,block)}"
end
debug {:x} #prints x = 5
debug {:y} #prints y = 5
I understand that eval is evil. So I have two questions.
Is there any way to write that debug method without using eval? If NO is there a preferred way to do this?
Is there any way to pass a list of arguments to this method? I would ideally prefer debug {:x, :y. :anynumOfvariables}. I could not quite figure out how to factor that into the debug method (i.e, to take a list of arguments)
Just use arrays. You can use the Array method to ensure that you will always have an array, even if someone passes in only a single value:
def debug(&block)
Array(block[]).each do |var| puts "#{var} = #{eval var.to_s, block}" end
end
x, y = 3, 5
debug {:x} # => "x = 3"
debug {[:x, :y]} # => "x = 3" "y = 5"
BTW: passing a block as the binding no longer works in Ruby 1.9. (Despite the fact that the documentation says it does work.) You have to explicitly call Proc#binding to get a Binding object for that Proc:
def debug(&block)
Array(block.()).flatten.each do |var|
puts "#{var} = #{eval var.to_s, block.binding}"
end
end
Fortunately, this already works in Ruby 1.8, so you can futureproof your code by including it.
An alternative would be to forgo the block altogether. I mean, you already force the user of the debug to use the unfamiliar idiom of passing arguments in the block instead of in parentheses. Why not force them to just pass the binding instead?
def debug(*vars, bnd)
vars.each do |var|
puts "#{var} = #{eval var.to_s, bnd}"
end
end
x, y = 3, 5
debug :x, binding # => "x = 3"
debug :x, :y, binding # => "x = 3" "y = 5"
This has the added flexibility that they can actually pass a different binding than the one at the callsite, e.g. if they want to actually debug a piece of code in a different piece of the application.
BTW: here's some fun with Ruby 1.9.2's parameter introspection (Proc#parameters):
def debug(&block)
block.parameters.map(&:last).each do |var|
puts "#{var} = #{eval var.to_s, block.binding}"
end
end
x, y = 3, 5
debug {|x|} # => "x = 3"
debug {|x, y|} # => "x = 3" "y = 5"
And how I must use those methods to get names and values of variables from each loop, to place them as keys and values of nested hash?
The *attributes array is passed as an method's parameter.
I want to iterate through it, and every variable's name use as a key to nested hash and variable's value as value.
I got this:
def get_varname(&block)
varname = block.call.to_s
return varname
end
def create_instance_hash(env_attrs_obj, *attributes)
if not env_attrs_obj.has_key?("instances")
env_attrs_obj["instances"] = ""
end
attributes.each do |attr|
attr_name = get_varname{:attr}
env_attrs_obj["instances"][attr_name] = attr
end
end
instance_nat_ip_pub = "ip_pub_addr"
instance_nat_ip_prv = "ip_addr"
instance_nat_ami = "AMI_NAME"
instance_nat_aws_ssh_key_id = "AWS_SSH_KEY_ID"
instance_nat_id = "instance_id"
env_hash = {}
create_instance_hash(env_hash, *[instance_nat_ip_pub, instance_nat_ip_prv, instance_nat_ami, instance_nat_aws_ssh_key_id, instance_nat_id])
But:
def attrs_each_test(*attributes)
attributes.each do |attr|
puts get_varname{:attr}
end
end
And it outputs:
attr
attr
attr
attr
attr
The error when I run this create_instance_hash:
Line 12:in `[]=': string not matched (IndexError)
from t.rb:12:in `create_instance_hash'
from t.rb:10:in `each'
from t.rb:10:in `create_instance_hash'
from t.rb:24
How I can correct this error and achieve my goal?

Resources