5

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 ...

Petr Cibulka
  • 2,452
  • 4
  • 28
  • 45
  • This seems to be a similar question: http://stackoverflow.com/questions/2314105/what-will-give-me-something-like-ruby-readline-with-a-default-value Most of the answers suggest external libraries as well. – Petr Cibulka Aug 30 '14 at 18:14
  • I am not using bash but this code should works for the TL;DR section: `def ask prompt, default = nil; unless default; return gets.chomp; end; return default; end;` and you can use it like: `ask 'what is your name'` waits for your reply. `ask 'what is your name', 'darek'` this just return default value. Well, if that's code that you are looking for I will post it as answer and explain more (because comment section has character limits). ps. or little hackish way: `def ask2 prompt, default = nil; default || gets.chomp; end;` – Darek Nędza Aug 30 '14 at 18:23
  • Derek: Again, your sollution is just (way more) elegant way how to do my own `def ask_q`, right? See my question. Also, the default answer should only be **suggested** to the user, your solution seems to force it. Am I right? – Petr Cibulka Aug 30 '14 at 18:33
  • Yeah, you may call it that way. This **suggestion**, how you want user to edit your suggestion? – Darek Nędza Aug 30 '14 at 18:46
  • Darek: See the "bash version" of the function or this question - http://stackoverflow.com/questions/2642585/read-a-variable-in-bash-with-a-default-value - that describes the exact desired behaviour in bash. – Petr Cibulka Aug 30 '14 at 18:47
  • If that's the case, I guess I can't help. – Darek Nędza Aug 30 '14 at 18:52
  • does the highline gem do what you need? see examples on https://github.com/JEG2/highline – Tim Peters Sep 03 '14 at 05:26
  • @Tim: It seems so, I was considering it before, but ultimately, I want the bash way. – Petr Cibulka Sep 03 '14 at 05:28

1 Answers1

2

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
7stud
  • 46,922
  • 14
  • 101
  • 127
  • Providing support for working with default values seems like a pretty basic functionality to me. It's quite surprising to me that Ruby does not offer that (if even Bash does) ... – Petr Cibulka Aug 30 '14 at 17:58
  • @PetrCibulka, Ruby's defaults are not the issue. The issue is controlling the way a terminal window works. You have to take control of the terminal window and alter its normal behavior to be able to backspace over output. The shell script you posted won't work for me `read: -i: invalid option` , so I don't even know if your shell script allows you to do that. Does it? – 7stud Aug 30 '14 at 18:22
  • It does. See this question - http://stackoverflow.com/questions/2642585/read-a-variable-in-bash-with-a-default-value - and most upvoted answer. However it is true, that you need Bash 4 ... – Petr Cibulka Aug 30 '14 at 18:29
  • @PetrCibulka, Just note that if you execute `echo -n "Enter stuff: "; read` you cannot back space over the output. So the -i option must cause some code to execute that alters the normal behavior of a terminal window. – 7stud Aug 30 '14 at 18:33
  • But there's no need for that `echo -n`, right? AFAIK, `read -e -p "Enter Your Name:" -i "Ricardo" NAME` provides the variable without the trailing \n. But I'm bash novice as well, so you may lost me a bit here. :) – Petr Cibulka Aug 30 '14 at 18:51
  • Well, there's no need for the -e either: If I do: `$ read -p "Enter Your Name: "`, I cannot backspace over the text. The point I'm trying make is: it's not normal operation to be able to backspace over text that is output in the terminal window. – 7stud Aug 30 '14 at 19:15
  • @PetrCibulka, I'll post an example of a gui app, which will allow the user to edit the default value. – 7stud Aug 30 '14 at 19:26
  • Thanks for the example! I may rethink my approach. I upvoted your answer (thanks a lot for your time!), however I will leave my question open, as primarily I'm still looking for a terminal based sollution. For now, it seems I will return back to bash. Thanks again! – Petr Cibulka Aug 31 '14 at 04:10
  • 1
    @PetrCibulka, You can also run bash scripts from ruby using system(), or backticks/%x (which captures an external program's output), or popen3() (more flexible); so if there's something else you would like code in ruby, you can use ruby to execute the bash script to get the user's input, then continue on with ruby. I'll post an example. – 7stud Sep 01 '14 at 20:43
  • Thank you for coming back with more solutions! And yes, I am using inline PHP this way in my script (to configure PHP config files, that mostly just return the array). Although in my particular situation, the question was if I could replace bash altogether with something more flexible. And since 40% of the body of the script are prompts ... :) – Petr Cibulka Sep 02 '14 at 03:21
  • @PetrCibulka, You can replace bash with my latest answer. :( – 7stud Sep 03 '14 at 05:33