How do I execute Ruby files from another Ruby file?
class Launch
def get_program
begin
files = ["sum_of_digits", "compressed_sequence",
"shortest_repetition"]
(0...files.length).each_with_index do |index|
puts "#{index} . #{ files[index]}"
end
begin
puts "Enter program number to execute: "
puts program_number = gets.chomp.to_i
puts "loading program #{files[program_number]}"
begin
load(`ruby #{files[program_number]}.rb
#{files[program_number]}.txt`)
rescue
puts "loading error"
end
puts "do you want to continue Y/N"
answer = gets.chomp
end until answer == 'N'
rescue
puts "the file cannot be loaded ,it may be moved or not exist "
end
end
end
launch = Launch.new
launch.get_program
launch = Launch.new
launch.get_program
While executing, I am getting the output, but for only one program, and the loop is terminating. I want to execute files in a loop until the user enters "N".
In general your code isn't written in the Ruby way. This is untested but it looks about right:
class Launch
FILES = ['sum_of_digits', 'compressed_sequence', 'shortest_repetition']
def get_program
FILES.each_with_index do |fname, i|
puts "#{i} . #{fname}"
end
loop do
puts "Enter program number to execute: "
program_number = gets.to_i
file_to_load = FILES[program_number]
puts "loading program #{file_to_load}"
begin
system("ruby #{file_to_load}.rb #{file_to_load}.txt")
rescue => e
puts "loading error: #{e}"
puts "'#{file_to_load}' cannot be loaded, it may have been moved or not exist."
end
puts 'Do you want to continue Y/N'
break if gets.chomp.strip.upcase == 'N'
end
end
end
launch = Launch.new
launch.get_program
Some things to study:
block and end are used to start exception handling, not to define control loops. Well, they can, but there are better, more idiomatic, ways. loop is recommended by Matz.
You used load but I don't think that's really what you'd want to do. Instead, you should tell the OS to load and run the code in a sub-shell using system, not in the context of your currently running code.
Instead of using a bare rescue, your code should at least capture the exception using rescue => e so you can output what occurred. In "real life", AKA, production, you should be even more discerning and capture only the exceptions you expect, but that's a different discussion.
When using a begin/rescue/end, try to keep them as small as possible, at least until you're more familiar with how they work. rescue is a great way to shoot yourself in the foot, and debugging raised exceptions that could be generated by many lines of code can be a pain.
In general, when you have a list of things that's likely to change, or any variable that's more likely to change than the rest of the code, put that definition at the top of the script, or the top of the class or module definition, then reference it as a constant. That helps avoid magical dust being sprinkled through the code that has to be searched for if you want to add or delete things. Like files. Or magical dust.
Related
I want to recall the case until user writes a or b. I do not want to use "case"
particularly.
I just want to get input from user but not geting something else. If he writes something else, he should need to write until he writes a or b.
str = gets.chomp.to_s
case str
when "a"
print "nice a"
when "b"
puts "nice b"
else
puts "please do it again"
end
class person
attr_accessor :name , :surname #and other attributes
end
#There will be a method here and it will run when the program is opened.
#The method will create the first object as soon as the program is opened.
#The new object that the user will enter will actually be the 2nd object.
puts "What do you want to do?
add
list
out"
process = gets.chomp.to_s
case process
when "add"
#in here user will add new objects of my class
when "list"
#in here user will show my objects
when "out"
puts "Have a nice day"
else
puts "please do it again"
end
In fact, if you look at it, many actions will be taken as a result of the user entering the correct input. what I want to tell is more detailed in this example. According to the input of the user, there will be actions such as calling methods, adding objects, etc.
I wrote most of the code on my computer. But still I couldn't solve my first problem.
Use Kernel#loop
There are a lot of ways to solve this problem, but let's start with a simple Kernel#loop wrapper around your existing code, as that's probably the easiest path forward for you.
loop do
str = gets.chomp.to_s
case str
when "a"
print "nice a"
when "b"
puts "nice b"
else
puts "please do it again"
# restart your loop when not "a" or "b"
next
end
# exit the loop if else clause wasn't triggered
break
end
Use until Control Expression
The loop construct above is pretty straightforward, but it requires you to think about where you need next and break statements for flow control. My own instinct would be to simply call a block until it's truthy. For example, the core logic could be shortened to:
str = nil; until str =~ /a|b/i do str = gets.chomp end; p str
This is a lot shorter, but it's not particularly user-friendly. To leverage this approach while making the solution more communicative and error-resistant, I'd refactor the original code this way:
# enable single-character input from console
require 'io/console'
# make sure you don't already have a value,
# especially in a REPL like irb
str = nil
until str =~ /a|b/ do
printf "\nLetter (a, b): "
str = STDIN.getch.downcase
end
puts "\nYou entered: #{str}"
While not much shorter than your original code, it handles more edge cases and avoids branching. It also seems less cluttered to me, but that's more a question of style. This approach and its semantic intent also seem more readable to me, but your mileage may legitimately vary.
See Also
IO::Console
Control Expressions
"I just want to do something until something else happens" is when you use some sort of while loop.
You can do this:
while true
str = gets.chomp
break unless str == 'a' || str == 'b'
puts "please do it again"
end
You can also use loop do:
loop do
str = gets.chomp
break unless ['a', 'b'].include?(str)
puts "please do it again"
end
puts "Nice #{str}."
Rubyists tend to prefer loop do over while true. They do pretty much the same thing.
One more thing. There's a simpler way to write out arrays of strings:
loop do
str = gets.chomp
break unless %w(a b).include?(str)
puts "please do it again"
end
puts "Nice #{str}."
It doesn't look a whole lot simpler, but if you have, say, 10 strings, it's definitely quicker to type in when you don't have to use all those quotation marks.
As your intuition was telling you, you don't need to use the case statement at all. Like trying to kill a flea with a sledgehammer. The most concise way to do your check is to check whether the input character is included in an array of the desired characters.
Basically in my search for code which will loop, and terminate upon user input, i managed to find code here, and after some alteration, produced this:
#desired destination method, however loop persists!!
def desired_method
print "method entered"
end
Thread.new do
while line = STDIN.gets
break if line.chomp == "" # code detects user input
end
desired_method
end
# program will loop here until user presses enter
loop do
puts "foo"
sleep 1
end
This code is brilliant, and will enter the method 'desired_method' when i hit enter, however the loop persists!! printing 'foo' perpetually after "method entered"!!. I have done some research prior to posting this question on how to kill threads, which i believe may hold the answer. My attempts included naming the thread and using the 'thread.exit' function to kill it, however these techniques have remained unsuccessful.
Can anyone illustrate how i might enter the 'desired_method' method without the persisting "foo" print?
Thanks in advance, and greatly appreciated.
An easy solution here is to use semaphore, signalling between threads with a variable access to both places:
# This will be out stop flag, for signalling between threads.
#time_to_stop = false
def desired_method
print "method entered"
# Here we want the loop in the other thread to stop.
#time_to_stop = true
end
Thread.new do
while line = STDIN.gets
break if line.chomp == "" # code detects user input
end
desired_method
end
# program will loop here until user presses enter
loop do
puts "foo"
sleep 1
# only continue if the stop flag is not set.
break if #time_to_stop
end
Hope this helps.
I'm running a script with an API that often times out. I'm using begin/rescue blocks to get it to redo when this happens, but want to log what is happening to the command line before I run the redo command.
begin
#...api query...
rescue ErrorClass
puts("retrying #{id}") && redo
end
Unfortunately the above script doesn't work. Only the first command is run.
I would like to force the rescue block to run multiple lines of code like so:
begin
# api query
rescue ErrorClass do ###or:# rescue ErrorClass do |e|
puts "retrying #{id}"
redo
end
but those don't work either.
I've had luck creating a separate method to run like so:
def example
id = 34314
begin
5/0
rescue ZeroDivisionError
eval(handle_zerodiv_error(id))
end
end
def handle_zerodiv_error(id)
puts "retrying #{id}"
"redo"
end
...that actually works. But it requires too many lines of code in my opinion and it uses eval which is not kosher by any means according to my mentor(s).
You are unnecessarily complicating things by using && or do. The && version does not work because puts returns nil, so by shortcut evaluation of &&, the part to follow is not evaluated. If you use || or ; instead, then it will work:
begin
...
rescue ErrorClass
puts("retrying #{id}") || redo
end
begin
...
rescue ErrorClass
puts("retrying #{id}"); redo
end
but even this is not necessary. You somehow seem to believe that you need a block within rescue to write multiple lines, but that does not make sense because you are not using a block with single line. There is no Ruby construction that requires a block only when you have multiple lines. So, just put them in multiple lines:
begin
...
rescue ErrorClass
puts("retrying #{id}")
redo
end
There is a retry built in. This example is from "The Ruby Programming Language" pg 162.
require "open-uri"
tries = 0
begin
tries +=1
open("http://www.example.com/"){|f| puts f.readlines}
rescue OpenURI::HTTPError => e
puts e.message
if (tries < 4)
sleep (2**tries) # wait for 2, 4 or 8 seconds
retry # and try again
end
end
I am trying to make a number guessing game in Ruby but the program exits after I type in yes when I want to play again. I tried using the catch and throw but it would not work. Could I please get some help.
Here is my code.
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
until $g==$a
puts "Guess the number between 0-10."
$g=gets.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets()
if $s=="yes"
$c=true
end
if $c==true
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
Game.new.Play
Edit: Here's my new code after trying suggestions:
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
while $s=="yes"
until $g==$a
puts "Guess the number between 0-10."
$g=gets.chomp.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets.chomp
if $s=="yes"
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
end
Game.new.Play
Your first problem is here:
$s=gets()
if $s=="yes"
$c=true
end
The gets method will read the next line including the new line character '\n', and you compare it to only "yes":
> gets
=> "yes\n"
The idiomatic way to fix this in Ruby is the chomp method:
> gets.chomp
=> "yes"
That said, your code has two other deficiencies.
You may come from a language such as PHP, Perl, or even just Bash scripting, but Ruby doesn't require the dollar sign before variables. Using a $ gives a variable global scope, which is likely not what you want. In fact, you almost never want a variable to have global scope.
Ruby uses three types of symbol prefixes to indicate scope - # for instance, ## for class, and $ for global. However the most common type of variable is just local which doesn't need any prefix, and what I would suggest for your code.
I have always been told that it is very bad practice to use exceptions for control structure. Your code would be better served with a while/break structure.
When you do gets(), it retrieves the full line with a '\n' in the end. You need to trim the new line character by using:
$g=gets.chomp.to_i
Same for other gets
Based on your updated code (where you fixed the newline problem shown by others), your new problem is that you have wrapped all your game inside while $s=="true". The very first time your code is run, $s is nil (it has never been set), and so you never get to play. If you used local variables instead of global variables (s instead of $s) this would have become more obvious, because the code would not even have run.
Here's one working way that I would re-write your game.
class Game
def play
keep_playing = true
while keep_playing
answer = rand(11) # Make a new answer each time
puts answer if $DEBUG # we don't normally let the user cheat
loop do # keep going until I break from the loop
puts "Guess the number between 0-10."
guess = gets.to_i # no need for chomp here
if guess>answer
puts "The number you guessed is too high."
elsif guess<answer
puts "The number you guessed is too low."
else
puts "Correct you won!!!",
"Would you like to play again?"
keep_playing = gets.chomp.downcase=="yes"
break
end
end
end
end
end
Game.new.play
I know this doesn't really answer your question about why your code isn't working, but after seeing the code you posted I just had to refactor it. Here you go:
class Game
def initialize
#answer = rand(11)
end
def play
loop do
guess = get_guess
display_feedback guess
break if guess == #answer
end
end
def self.play_loop
loop do
Game.new.play
break unless play_again?
end
end
private
def get_guess
puts "Guess the number between 0-10."
return gets.chomp.to_i
end
def display_feedback(guess)
if guess > #answer
puts "The number you guessed is too high."
elsif guess < #answer
puts "The number you guessed is too low."
elsif guess == #answer
puts "Correct you won!!!"
end
end
def self.play_again?
puts "Would you like to play again?"
return gets.chomp == "yes"
end
end
Game.play_loop
This is a snippet from a larger chunk of code:
print "> "
$next_move = gets.chomp
case $next_move.include?
when "instructions"
puts "$next_move is instructions"
else
puts "$next_move is NOT instructions"
end
Everytime I run it in the terminal, whether I'm using ruby 1.8.7, 1.9.3, or 2.0.0, I get the following error:
test.rb:4:in `include?': wrong number of arguments (0 for 1) (ArgumentError)
from test.rb:4
This code worked last night on a different computer.
Isn't include? checking the contents of that global variable? What other argument should I be passing to it?
I'm kinda stumped here, especially since all I did was move the code from one computer to another.
http://www.ruby-doc.org/core-1.9.3/String.html#method-i-include-3F
Returns true if str contains the given string or character.
That means it requires exactly 1 argument, so no wonder it throws ArgumentError when called without arguments.
So the code should be:
if $next_move.include? 'instructions'
puts '$next_move is instructions'
else
puts '$next move is NOT instructions'
end
Something had to have changed between the two computers you tested this on. If you are wanting to use this as a case statement, you probably had something along the lines of:
next_move = 'instructions'
case next_move
when "instructions"
puts "$next_move is instructions"
else
puts "$next_move is NOT instructions"
end
This specifically tests if next_move IS instructions. As an if/else statement:
if next_move.include? 'instructions'
puts "$next_move is instructions"
else
puts "$next_move is NOT instructions"
end
See eval.in for more information.