Separating console message from the code - ruby

I'm trying to make command line apps. The puts line makes the code looks messy. For example, I have help command that has several puts
def help()
puts "Welcome to my app"
puts "..."
puts "..."
puts "..."
puts "..."
end
If I combine the puts into one, the output will include the trailing space
def help()
puts "Welcome to my app
...
..."
end
# The output in the console will be like:
# Welcome to my app
# ...
# ...
What's the best way to separate the message from the code? I can only think of using variable to store the message, but I believe there is a better, tidier way like markdown or using txt.

For what you are asking, I think you are looking for the OptParser library in STDLIB.
It allows you to build command line options for doing things like usage and command line reporting for the user.
However, you can do this in your help method:
def help
<<-EOS.lines.each {|line| line.strip!}
Welcome to my app
...
...
EOS
end
puts help
puts "Thank you for using my app!"
This will display like this.
Welcome to my app
...
...
Thank you for using my app!
Update: I changed the EOF delimiter to EOS for End of String.

def help
puts \
"Welcome to my app"\
"..."\
"..."\
"..."\
"..."\
"..."
end

In your specific example, You can do within the help function
puts "Welcome to my app", "...\n"*3
If you have a lots of such static messages, you can try using a hash somewhere at the beginning
messages = {"welcome" => "Welcome to my app\n" + "...\n"*3,
"thanks" => "Thank you for the action"}
Then you can access them as
puts messages["welcome"]

Related

end to end test of a ruby console app

I have a ruby console app that you run with an argument, then once running outputs some text to the screen, asks for some more user input and then outputs some more text to the screen. I want to do an end to end test on this app and I don't know how. If I were writing an end to end test for an REST API, I would just hit the public endpoint, follow the links and then have an expect statement on the output. Easy. But on a console app I have no idea how to do the same thing. Are there any gems for stepping through a console app in the context of a test? I've been looking all day but can't find anything.
ANY help appreciated.
Inspired by this gem which has a fairly simple implementation, I wrote a method which captures console input & output and can, therefore, be used in tests:
require 'stringio'
module Kernel
def emulate_console(console_input)
$stdin = StringIO.new(console_input)
out = StringIO.new
$stdout = out
yield
return out
ensure
$stdout = STDOUT
$stdin = STDIN
end
end
This method captures console output, and also provides as input the string value which you specify in the console_input parameter.
Basic usage
Here's a simple usage of the emulate_console method:
out = emulate_console("abc\n") do
input = gets.chomp
puts "You entered: #{input}!"
end
The return value out is a StringIO object. To access its value, use the #string method:
out.string
=> "You entered: abc!\n"
Note that the input contains a newline character (\n) to simulate pressing the ENTER key.
Testing
Now, let's assume that you want to test this method, that uses both stdin and stdout:
def console_add_numbers
x = Integer(gets)
y = Integer(gets)
puts x + y
end
The following RSpec test tests the happy path of this code:
require 'rspec/autorun'
RSpec.describe '#console_add_numbers' do
it 'computes correct result' do
input = <<-EOS
2
3
EOS
output = emulate_console(input) { console_add_numbers }
expect(output.string.chomp).to eql '5'
end
end

Ruby: gets.chomp with default value

Is there some simple way how to ask for a user input in Ruby WHILE providing a default value?
Consider this code in bash:
function ask_q {
local PROMPT="$1"
local DEF_V="$2"
read -e -p "$PROMPT" -i "$DEF_V" REPLY
echo $REPLY
}
TEST=$(ask_q "Are you hungry?" "Yes")
echo "Answer was \"$TEST\"."
Can you achieve similar behaviour with Ruby's gets.chomp?
function ask_q(prompt, default="")
puts prompt
reply = gets.chomp() # ???
return reply
def
reply = ask_q("Are you hungry?", "Yes")
I understand I can sort replicate the functionality in Ruby this way ...
def ask_q(prompt, default="")
default_msg = (default.to_s.empty?) ? "" : "[default: \"#{default}\"]"
puts "${prompt} ${default}"
reply = gets.chomp()
reply = (default.to_s.empty?) ? default : reply
return reply
end
... but it does not seem very pretty. I also need to show the default value manually and the user needs to retype it in the prompt line, if he wants to use modified version of it (say yes! instead of yes).
I'm starting with Ruby now, so there may be a lot of syntax mistakes and I also may be missing something obvious ... Also, I googled a lot but surprisingly found no clue.
TL; DR
To make the question clearer, this is what you should see in terminal and what I am able to achieve in bash (and not in Ruby, so far):
### Terminal output of `reply=ask_q("Are you hungry?" "Yes")`
$ Are you hungry?
$ Yes # default editable value
### Terminal output of `reply=ask_q("What do you want to eat?")`
$ What do you want to eat?
$ # blank line waiting for user input, since there is no second parameter
And the actual situation: I am building bootstrap script for my web apps. I need to provide users with existing configuration data, that they can change if needed.
### Terminal output of `reply=ask_q("Define name of database." "CURR_DB_NAME")`
I don't think it's that fancy functionality, that would require switch to GUI app world.
And as I've said before, this is quite easily achievable in bash. Problem is, that other things are pure pain (associative arrays, no return values from functions, passing parameters, ...). I guess I just need to decide what sucks the least in my case ...
You need to do one of two things:
1) Create a gui program.
2) Use curses.
Personally, I think it's a waste of time to spend any time learning curses. Curses has even been removed from the Ruby Standard Library.
A GUI program:
Here is what a gui app looks like using the Tkinter GUI Framework:
def ask_q(prompt, default="")
require 'tk'
root = TkRoot.new
root.title = "Your Info"
#Display the prompt:
TkLabel.new(root) do
text "#{prompt}: "
pack("side" => "left")
end
#Create a textbox that displays the default value:
results_var = TkVariable.new
results_var.value = default
TkEntry.new(root) do
textvariable results_var
pack("side" => "left")
end
user_input = nil
#Create a button for the user to click to send the input to your program:
TkButton.new(root) do
text "OK"
command(Proc.new do
user_input = results_var.value
root.destroy
end)
pack("side" => "right", "padx"=> "50", "pady"=> "10")
end
Tk.mainloop
user_input
end
puts ask_q("What is your name", "Petr Cibulka")
Calling a function in a bash script from ruby:
.../bash_programs/ask_q.sh:
#!/usr/bin/env bash
function ask_q {
local QUESTION="$1"
local DEFAULT_ANSWER="$2"
local PROMPT="$QUESTION"
read -p "$PROMPT $DEFAULT_ANSWER" USERS_ANSWER #I left out the -i stuff, because it doesn't work for my version of bash
echo $USERS_ANSWER
}
ruby_prog.rb:
answer = %x{
source ../bash_programs/ask_q.sh; #When ask_q.sh is not in a directory in your $PATH, this allows the file to be seen.
ask_q 'Are you Hungry?' 'Yes' #Now you can call functions defined inside ask_q.sh
}
p answer.chomp #=> "Maybe"
Using curses:
require 'rbcurse/core/util/app'
def help_text
<<-eos
Enter as much help text
here as you want
eos
end
user_answer = "error"
App.new do #Ctrl+Q to terminate curses, or F10(some terminals don't process function keys)
#form.help_manager.help_text = help_text() #User can hit F1 to get help text (some terminals do not process function keys)
question = "Are You Hungry?"
default_answer = "Yes"
row_position = 1
column_position = 10
text_field = Field.new(#form).
name("textfield1").
label(question).
text(default_answer).
display_length(20).
bgcolor(:white).
color(:black).
row(row_position).
col(column_position)
text_field.cursor_end
text_field.bind_key(13, 'return') do
user_answer = text_field.text
throw :close
end
end
puts user_answer

How can I handle using multiple variables depending on which element of an iterator I am using? Aka, how can I DRY up this ruby code?

I have some variables that look like this:
top_script_path = "path/to/top"
bottom_script_path = "path/to/bottom"
script_names = ["top", "bottom"]
and I'd like to call each of the scripts
`#{top_script_path} "top"`
puts "top script successful"
`#{bottom_script_path} "bottom"`
puts "bottom script successful"
This solution, however, doesn't feel DRY enough to me. I'd like to be able to do something like
script_names.each do |name|
`#{#{name}_script_path} #{name}`
puts "#{name} script successful"
end
Obviously, it isn't possible to put a #{expression} inside of a #{expression} as above, but is there any other way to dry up this code with a loop?
Use hashes:
script_paths = {
:top => 'path/to/top',
:bottom => 'path/to/bottom',
}
script_names = script_paths.keys
script_names.each do |name|
# `...`
puts "#{script_paths[name]} #{name}"
end
Run:
$ ruby qq.rb
path/to/top top
path/to/bottom bottom
script_names.each do |name|
`#{eval("#{name}_script_path")} #{name}`
puts "#{name} script successful"
end
I would refactor the variables into one structure, something like:
scripts = {
'top' => 'path/to/top',
'bottom' => 'path/to/bottom'
}
scripts.each do |name, path|
`#{path} #{name}`
puts "#{name} script successful"
end
If you are writing some kind of build script, consider using Rake.

Ruby command line program that saves and updates users/preferences

I'm building an application that takes in stdin to save a user and their preferences. Should I write the stdin to a text file and save the user input there?
commandline.rb
class CommandLine
def initialize(filename)
#file = File.open(filename, 'w')
end
def add_user(input)
#file = File.open('new_accounts.txt', 'r+')
#file.write(input)
puts input
end
def run
puts "Welcome to the Command Line Client!"
command = ''
while command != 'quit'
printf "enter command: "
input = gets.chomp
parts = input.split
command = parts[0]
case command
when 'quit' then puts 'Goodbye!'
when '-a' then add_user(parts[1..-1].join(" "))
else
puts 'Invalid command #{command}, please try again.'
end
end
end
end
a = CommandLine.new('new_accounts.txt')
a.run
Let's say I want the user to enter '-a tommy likes apples' in the command line, I want it to output:
tommy likes apples
The same user tommy could also input '-a tommy likes oranges' which would then update his previous preference:
tommy likes oranges
Any help/direction is appreciated, thanks!
I don't see a problem with using a text file if you are doing something simple. Alternatives are many and without more detail I'm afraid I can't make a good recommendation.
def add_user(input)
File.open('new_accounts.txt', 'w') {|file|
file.write(input)
}
puts input
end
FYI: This will make it so that your text file updates. :-)
EDIT: Changed the add_user method.

How can I test rspec user input and output with Highline?

I'd like to test response to user input. That input is queried using Highline:
def get_name
return HighLine.new.ask("What is your name?")
end
I'd like to do something similar to this question and put it in my test:
STDOUT.should_receive(:puts).with("What is your name?")
STDIN.should_receive(:read).and_return("Inigo Montoya")
MyClass.new.get_name.should == "Inigo Montoya"
What is the correct way to do this with Highline?
The best way to find out how to test Highline is to see how the author tests his package.
class TestHighLine < Test::Unit::TestCase
def setup
#input = StringIO.new
#output = StringIO.new
#terminal = HighLine.new(#input, #output)..
end
..
def test_agree
#input << "y\nyes\nYES\nHell no!\nNo\n"
#input.rewind
assert_equal(true, #terminal.agree("Yes or no? "))
assert_equal(true, #terminal.agree("Yes or no? "))
assert_equal(true, #terminal.agree("Yes or no? "))
assert_equal(false, #terminal.agree("Yes or no? "))
....
#input.truncate(#input.rewind)
#input << "yellow"
#input.rewind
assert_equal(true, #terminal.agree("Yes or no? ", :getc))
end
def test_ask
name = "James Edward Gray II"
#input << name << "\n"
#input.rewind
assert_equal(name, #terminal.ask("What is your name? "))
....
assert_raise(EOFError) { #terminal.ask("Any input left? ") }
end
Etc., as shown in his code. You can find this information in the highline source paying close attention to the setup, which I have highlighted in the link.
Notice how he uses the STDIN IO pipe to act in the place of typing the keys on the keyboard.
What this indicates, really, is that you don't need to use highline to test that kind of thing. The setup in his tests are really key here. Along with his use of StringIO as an object.
Highline already has it's own tests to make sure that it outputs to STDOUT and reads from STDIN. There is no reason to write these types of tests. It's the same reason that you would not write ActiveRecord tests that make sure attributes can be saved and read from the database.
However...
it would be extremely useful if there were a framework for Highline that works in a similar fashion as Capybara for web forms...
something that actually drives the input from the UI and tests the logic of your command-line utility.
For example, the following kind of hypothetical test would be nice:
run 'my_utility.rb'
highline.should :show_menu
select :add
highline.should(:ask).with_prompt("name?")
enter "John Doe"
output.should == "created new user"
I have published HighLine::Test - it allows you to run your tests in one process and your application in another (in the same way as you would with browser-based tests with Selenium).
GitHub link: https://github.com/joeyates/highline-test
gem: 'highline-test'
I worked on this DSL to try to solve this problem:
https://github.com/bonzofenix/cancun
require 'spec_helper'
describe Foo do
include Cancun::Highline
before { init_highline_test }
describe '#hello' do
it 'says hello correctly' do
execute do
Foo.new.salute
end.and_type 'bonzo'
expect(output).to include('Hi bonzo')
end

Resources