Class: HighLine::Question

Inherits:
Object show all
Includes:
CustomErrors
Defined in:
lib/highline/question.rb,
lib/highline/question/answer_converter.rb

Overview

Question objects contain all the details of a single invocation of HighLine.ask(). The object is initialized by the parameters passed to HighLine.ask() and then queried to make sure each step of the input process is handled according to the users wishes.

Direct Known Subclasses

Menu

Defined Under Namespace

Classes: AnswerConverter

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(template, answer_type) {|_self| ... } ⇒ Question

Create an instance of HighLine::Question. Expects a template to ask (can be "") and an answer_type to convert the answer to. The answer_type parameter must be a type recognized by Question.convert(). If given, a block is yielded the new Question object to allow custom initialization.

Parameters:

  • template (String)

    any String

  • answer_type (Class)

    the type the answer will be converted to it.

Yields:

  • (_self)

Yield Parameters:


52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/highline/question.rb', line 52

def initialize(template, answer_type)
  # initialize instance data
  @template    = String(template).dup
  @answer_type = answer_type
  @completion  = @answer_type

  @echo         = true
  @default_hint_show = true
  @whitespace   = :strip
  @case         = nil
  @in           = nil
  @first_answer = nil
  @glob         = "*"
  @overwrite    = false
  @user_responses = {}
  @internal_responses = default_responses_hash
  @directory = Pathname.new(File.expand_path(File.dirname($PROGRAM_NAME)))

  # allow block to override settings
  yield self if block_given?

  # finalize responses based on settings
  build_responses
end

Instance Attribute Details

#aboveObject

Used to control range checks for answer.


154
155
156
# File 'lib/highline/question.rb', line 154

def above
  @above
end

#answerObject

The answer, set by HighLine#ask


81
82
83
# File 'lib/highline/question.rb', line 81

def answer
  @answer
end

#answer_typeObject

The type that will be used to convert this answer.


84
85
86
# File 'lib/highline/question.rb', line 84

def answer_type
  @answer_type
end

#belowObject

Used to control range checks for answer.


154
155
156
# File 'lib/highline/question.rb', line 154

def below
  @below
end

#caseObject

Used to control character case processing for the answer to this question. See HighLine::Question.change_case() for acceptable settings.


137
138
139
# File 'lib/highline/question.rb', line 137

def case
  @case
end

#characterObject

Can be set to true to use HighLine's cross-platform character reader instead of fetching an entire line of input. (Note: HighLine's character reader ONLY supports STDIN on Windows and Unix.) Can also be set to :getc to use that method on the input stream.

WARNING: The echo and overwrite attributes for a question are ignored when using the :getc method.


96
97
98
# File 'lib/highline/question.rb', line 96

def character
  @character
end

#completionObject

For Auto-completion


86
87
88
# File 'lib/highline/question.rb', line 86

def completion
  @completion
end

#confirmObject

Asks a yes or no confirmation question, to ensure a user knows what they have just agreed to. The confirm attribute can be set to : true : In this case the question will be, “Are you sure?”. Proc : The Proc is yielded the answer given. The Proc must

output a string which is then used as the confirm
question.

String : The String must use ERB syntax. The String is

evaluated with access to question and answer and
is then used as the confirm question.

When set to false or nil (the default), answers are not confirmed.


168
169
170
# File 'lib/highline/question.rb', line 168

def confirm
  @confirm
end

#defaultObject

Used to provide a default answer to this question.


139
140
141
# File 'lib/highline/question.rb', line 139

def default
  @default
end

#default_hint_showObject

Set it to a truthy or falsy value to enable or disable showing the default value hint between vertical bars (pipes) when asking the question. Defaults to true


143
144
145
# File 'lib/highline/question.rb', line 143

def default_hint_show
  @default_hint_show
end

#directoryObject

The directory from which a user will be allowed to select files, when File or Pathname is specified as an answer_type. Initially set to Pathname.new(File.expand_path(File.dirname($0))).


201
202
203
# File 'lib/highline/question.rb', line 201

def directory
  @directory
end

#echoObject

Can be set to true or false to control whether or not input will be echoed back to the user. A setting of true will cause echo to match input, but any other true value will be treated as a String to echo for each character typed.

This requires HighLine's character reader. See the character attribute for details.

Note: When using HighLine to manage echo on Unix based systems, we recommend installing the termios gem. Without it, it's possible to type fast enough to have letters still show up (when reading character by character only).


117
118
119
# File 'lib/highline/question.rb', line 117

def echo
  @echo
end

#first_answerObject

Returns first_answer, which will be unset following this call.


405
406
407
408
409
# File 'lib/highline/question.rb', line 405

def first_answer
  @first_answer
ensure
  @first_answer = nil
end

#gatherObject

When set, the user will be prompted for multiple answers which will be collected into an Array or Hash and returned as the final answer.

You can set gather to an Integer to have an Array of exactly that many answers collected, or a String/Regexp to match an end input which will not be returned in the Array.

Optionally gather can be set to a Hash. In this case, the question will be asked once for each key and the answers will be returned in a Hash, mapped by key. The @key variable is set before each question is evaluated, so you can use it in your question.


182
183
184
# File 'lib/highline/question.rb', line 182

def gather
  @gather
end

#globObject

The glob pattern used to limit file selection when File or Pathname is specified as an answer_type. Initially set to "*".


206
207
208
# File 'lib/highline/question.rb', line 206

def glob
  @glob
end

#inObject

If set, answer must pass an include?() check on this object.


156
157
158
# File 'lib/highline/question.rb', line 156

def in
  @in
end

#limitObject

Allows you to set a character limit for input.

WARNING: This option forces a character by character read.


102
103
104
# File 'lib/highline/question.rb', line 102

def limit
  @limit
end

#overwriteObject

When set to true the question is asked, but output does not progress to the next line. The Cursor is moved back to the beginning of the question line and it is cleared so that all the contents of the line disappear from the screen.


240
241
242
# File 'lib/highline/question.rb', line 240

def overwrite
  @overwrite
end

#readlineObject

Use the Readline library to fetch input. This allows input editing as well as keeping a history. In addition, tab will auto-complete within an Array of choices or a file listing.

WARNING: This option is incompatible with all of HighLine's character reading modes and it causes HighLine to ignore the specified input stream.


127
128
129
# File 'lib/highline/question.rb', line 127

def readline
  @readline
end

#templateObject

The ERb template of the question to be asked.


78
79
80
# File 'lib/highline/question.rb', line 78

def template
  @template
end

#validateObject

If set to a Regexp, the answer must match (before type conversion). Can also be set to a Proc which will be called with the provided answer to validate with a true or false return. It's possible to use a custom validator class. It must respond to `#valid?`. The result of `#inspect` will be used in error messages. See README.md for details.


152
153
154
# File 'lib/highline/question.rb', line 152

def validate
  @validate
end

#verify_matchObject

When set to true multiple entries will be collected according to the setting for gather, except they will be required to match each other. Multiple identical entries will return a single answer.


188
189
190
# File 'lib/highline/question.rb', line 188

def verify_match
  @verify_match
end

#whitespaceObject

Used to control whitespace processing for the answer to this question. See HighLine::Question.remove_whitespace() for acceptable settings.


132
133
134
# File 'lib/highline/question.rb', line 132

def whitespace
  @whitespace
end

Class Method Details

.build(template_or_question, answer_type = nil, &details) ⇒ Question

If template_or_question is already a Question object just return it. If not, build it.

Parameters:

  • template_or_question (String, Question)

    what to ask

  • answer_type (Class) (defaults to: nil)

    to what class to convert the answer

  • details

    to be passed to Question.new

Returns:


35
36
37
38
39
40
41
# File 'lib/highline/question.rb', line 35

def self.build(template_or_question, answer_type = nil, &details)
  if template_or_question.is_a? Question
    template_or_question
  else
    Question.new(template_or_question, answer_type, &details)
  end
end

Instance Method Details

#answer_or_default(answer_string) ⇒ String

Returns the provided answer_string or the default answer for this Question if a default was set and the answer is empty.

Parameters:

Returns:

  • (String)

    the answer itself or a default message.


248
249
250
251
# File 'lib/highline/question.rb', line 248

def answer_or_default(answer_string)
  return default if answer_string.empty? && default
  answer_string
end

#ask_on_error_msgself, String

Provides the String to be asked when at an error situation. It may be just the question itself (repeat on error).

Returns:

  • (self)

    if :ask_on_error on responses Hash is set to :question

  • (String)

    if :ask_on_error on responses Hash is set to something else


575
576
577
578
579
580
581
# File 'lib/highline/question.rb', line 575

def ask_on_error_msg
  if final_responses[:ask_on_error] == :question
    self
  elsif final_responses[:ask_on_error]
    final_responses[:ask_on_error]
  end
end

#build_responses(message_source = answer_type) ⇒ Hash

Called late in the initialization process to build intelligent responses based on the details of this Question object. Also used by Menu#update_responses.

Parameters:

  • message_source (Class) (defaults to: answer_type)

    Array or String for example. Same as #answer_type.

Returns:

  • (Hash)

    responses Hash winner (new and old merge).


262
263
264
265
266
267
268
269
# File 'lib/highline/question.rb', line 262

def build_responses(message_source = answer_type)
  append_default_to_template if default_hint_show

  new_hash = build_responses_new_hash(message_source)
  # Update our internal responses with the new hash
  # generated from the message source
  @internal_responses = @internal_responses.merge(new_hash)
end

#build_responses_new_hash(message_source) ⇒ Hash

When updating the responses hash, it generates the new one.

Parameters:

  • message_source (Class)

    Array or String for example. Same as #answer_type.

Returns:

  • (Hash)

    responses hash


281
282
283
284
285
286
287
288
289
290
291
# File 'lib/highline/question.rb', line 281

def build_responses_new_hash(message_source)
  { ambiguous_completion: "Ambiguous choice.  Please choose one of " +
    choice_error_str(message_source) + ".",
    invalid_type: "You must enter a valid #{message_source}.",
    no_completion: "You must choose one of " +
      choice_error_str(message_source) + ".",
    not_in_range: "Your answer isn't within the expected range " \
      "(#{expected_range}).",
    not_valid: "Your answer isn't valid (must match " \
      "#{validate.inspect})." }
end

#change_case(answer_string) ⇒ String

Returns the provided answer_string after changing character case by the rules of this Question. Valid settings for whitespace are:

nil

Do not alter character case. (Default.)

:up

Calls upcase().

:upcase

Calls upcase().

:down

Calls downcase().

:downcase

Calls downcase().

:capitalize

Calls capitalize().

An unrecognized choice (like :none) is treated as nil.

Parameters:

Returns:

  • (String)

    upcased, downcased, capitalized or unchanged answer String.


326
327
328
329
330
331
332
333
334
335
336
# File 'lib/highline/question.rb', line 326

def change_case(answer_string)
  if [:up, :upcase].include?(@case)
    answer_string.upcase
  elsif [:down, :downcase].include?(@case)
    answer_string.downcase
  elsif @case == :capitalize
    answer_string.capitalize
  else
    answer_string
  end
end

#check_rangeObject

Run #in_range? and raise an error if not succesful


372
373
374
# File 'lib/highline/question.rb', line 372

def check_range
  raise NotInRangeQuestionError unless in_range?
end

#choices_complete(answer_string) ⇒ String

Try to auto complete answer_string

Parameters:

Returns:

Raises:


379
380
381
382
383
384
385
386
# File 'lib/highline/question.rb', line 379

def choices_complete(answer_string)
  # cheating, using OptionParser's Completion module
  choices = selection
  choices.extend(OptionParser::Completion)
  answer = choices.complete(answer_string)
  raise NoAutoCompleteMatch unless answer
  answer
end

#confirm_question(highline) ⇒ String

Returns the String to be shown when asking for an answer confirmation.

Parameters:

Returns:


552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
# File 'lib/highline/question.rb', line 552

def confirm_question(highline)
  if confirm == true
    "Are you sure?  "
  elsif confirm.is_a?(Proc)
    confirm.call(answer)
  else
    # evaluate ERb under initial scope, so it will have
    # access to question and answer
    template = if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+
      ERB.new(confirm, trim_mode: "%")
    else
      ERB.new(confirm, nil, "%")
    end
    template_renderer = TemplateRenderer.new(template, self, highline)
    template_renderer.render
  end
end

#convertObject

Transforms the given answer_string into the expected type for this Question. Currently supported conversions are:

[...]

Answer must be a member of the passed Array. Auto-completion is used to expand partial answers.

lambda {...}

Answer is passed to lambda for conversion.

Date

Date.parse() is called with answer.

DateTime

DateTime.parse() is called with answer.

File

The entered file name is auto-completed in terms of directory + glob, opened, and returned.

Float

Answer is converted with Kernel.Float().

Integer

Answer is converted with Kernel.Integer().

nil

Answer is left in String format. (Default.)

Pathname

Same as File, save that a Pathname object is returned.

String

Answer is converted with Kernel.String().

HighLine::String

Answer is converted with HighLine::String()

Regexp

Answer is fed to Regexp.new().

Symbol

The method to_sym() is called on answer and the result returned.

any other Class

The answer is passed on to Class.parse().

This method throws ArgumentError, if the conversion cannot be completed for any reason.


367
368
369
# File 'lib/highline/question.rb', line 367

def convert
  AnswerConverter.new(self).convert
end

#default_responses_hashObject


271
272
273
274
275
276
# File 'lib/highline/question.rb', line 271

def default_responses_hash
  {
    ask_on_error: "?  ",
    mismatch: "Your entries didn't match."
  }
end

#expected_rangeObject

Returns an English explanation of the current range settings.


389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/highline/question.rb', line 389

def expected_range
  expected = []

  expected << "above #{above}" if above
  expected << "below #{below}" if below
  expected << "included in #{@in.inspect}" if @in

  case expected.size
  when 0 then ""
  when 1 then expected.first
  when 2 then expected.join(" and ")
  else        expected[0..-2].join(", ") + ", and #{expected.last}"
  end
end

#final_response(error) ⇒ Object


300
301
302
303
304
305
306
307
# File 'lib/highline/question.rb', line 300

def final_response(error)
  response = final_responses[error]
  if response.respond_to?(:call)
    response.call(answer)
  else
    response
  end
end

#final_responsesObject

This is the actual responses hash that gets used in determining output Notice that we give @user_responses precedence over the responses generated internally via build_response


296
297
298
# File 'lib/highline/question.rb', line 296

def final_responses
  @internal_responses.merge(@user_responses)
end

#first_answer?Boolean

Returns true if first_answer is set.

Returns:

  • (Boolean)

412
413
414
# File 'lib/highline/question.rb', line 412

def first_answer?
  true if @first_answer
end

#format_answer(answer_string) ⇒ String

Convert to String, remove whitespace and change case when necessary

Parameters:

Returns:

  • (String)

    converted String


470
471
472
473
474
# File 'lib/highline/question.rb', line 470

def format_answer(answer_string)
  answer_string = String(answer_string)
  answer_string = remove_whitespace(answer_string)
  change_case(answer_string)
end

#get_echo_for_response(response) ⇒ String

Returns an echo string that is adequate for this Question settings.

Parameters:

Returns:


599
600
601
602
603
604
605
606
607
608
609
610
# File 'lib/highline/question.rb', line 599

def get_echo_for_response(response)
  # actually true, not only truethy value
  if echo == true
    response
  # any truethy value, probably a String
  elsif echo
    echo
  # any falsy value, false or nil
  else
    ""
  end
end

#get_response(highline) ⇒ String

Return a line or character of input, as requested for this question. Character input will be returned as a single character String, not an Integer.

This question's first_answer will be returned instead of input, if set.

Raises EOFError if input is exhausted.

Parameters:

Returns:

  • (String)

    a character or line


524
525
526
527
528
529
530
531
532
533
534
535
# File 'lib/highline/question.rb', line 524

def get_response(highline)
  return first_answer if first_answer?

  case character
  when :getc
    highline.get_response_getc_mode(self)
  when true
    highline.get_response_character_mode(self)
  else
    highline.get_response_line_mode(self)
  end
end

#get_response_or_default(highline) ⇒ String

Uses #get_response but returns a default answer using #answer_or_default in case no answers was returned.

Parameters:

Returns:


544
545
546
# File 'lib/highline/question.rb', line 544

def get_response_or_default(highline)
  self.answer = answer_or_default(get_response(highline))
end

#in_range?Boolean

Returns true if the answer_object is greater than the above attribute, less than the below attribute and include?()ed in the in attribute. Otherwise, false is returned. Any nil attributes are not checked.

Returns:

  • (Boolean)

422
423
424
425
426
# File 'lib/highline/question.rb', line 422

def in_range?
  (!above || answer > above) &&
    (!below || answer < below) &&
    (!@in || @in.include?(answer))
end

#remove_whitespace(answer_string) ⇒ String

Returns the provided answer_string after processing whitespace by the rules of this Question. Valid settings for whitespace are:

nil

Do not alter whitespace.

:strip

Calls strip(). (Default.)

:chomp

Calls chomp().

:collapse

Collapses all whitespace runs to a single space.

:strip_and_collapse

Calls strip(), then collapses all whitespace runs to a single space.

:chomp_and_collapse

Calls chomp(), then collapses all whitespace runs to a single space.

:remove

Removes all whitespace.

An unrecognized choice (like :none) is treated as nil.

This process is skipped for single character input.

Parameters:

Returns:

  • (String)

    answer string with whitespaces removed


449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/highline/question.rb', line 449

def remove_whitespace(answer_string)
  if !whitespace
    answer_string
  elsif [:strip, :chomp].include?(whitespace)
    answer_string.send(whitespace)
  elsif whitespace == :collapse
    answer_string.gsub(/\s+/, " ")
  elsif [:strip_and_collapse, :chomp_and_collapse].include?(whitespace)
    result = answer_string.send(whitespace.to_s[/^[a-z]+/])
    result.gsub(/\s+/, " ")
  elsif whitespace == :remove
    answer_string.gsub(/\s+/, "")
  else
    answer_string
  end
end

#responsesObject

A Hash that stores the various responses used by HighLine to notify the user. The currently used responses and their purpose are as follows:

:ambiguous_completion

Used to notify the user of an ambiguous answer the auto-completion system cannot resolve.

:ask_on_error

This is the question that will be redisplayed to the user in the event of an error. Can be set to :question to repeat the original question.

:invalid_type

The error message shown when a type conversion fails.

:no_completion

Used to notify the user that their selection does not have a valid auto-completion match.

:not_in_range

Used to notify the user that a provided answer did not satisfy the range requirement tests.

:not_valid

The error message shown when validation checks fail.


231
232
233
# File 'lib/highline/question.rb', line 231

def responses
  @user_responses
end

#selectionObject

Returns an Array of valid answers to this question. These answers are only known when answer_type is set to an Array of choices, File, or Pathname. Any other time, this method will return an empty Array.


481
482
483
484
485
486
487
488
489
490
491
# File 'lib/highline/question.rb', line 481

def selection
  if completion.is_a?(Array)
    completion
  elsif [File, Pathname].include?(completion)
    Dir[File.join(directory.to_s, glob)].map do |file|
      File.basename(file)
    end
  else
    []
  end
end

#show_question(highline) ⇒ void

This method returns an undefined value.

readline() needs to handle its own output, but readline only supports full line reading. Therefore if question.echo is anything but true, the prompt will not be issued. And we have to account for that now. Also, JRuby-1.7's ConsoleReader.readLine() needs to be passed the prompt to handle line editing properly.

Parameters:


590
591
592
# File 'lib/highline/question.rb', line 590

def show_question(highline)
  highline.say(self)
end

#to_sObject

Stringifies the template to be asked.


494
495
496
# File 'lib/highline/question.rb', line 494

def to_s
  template
end

#valid_answer?Boolean

Returns true if the provided answer_string is accepted by the validate attribute or false if it's not.

It's important to realize that an answer is validated after whitespace and case handling.

Returns:

  • (Boolean)

505
506
507
508
509
510
# File 'lib/highline/question.rb', line 505

def valid_answer?
  !validate ||
    (validate.is_a?(Regexp) && answer =~ validate) ||
    (validate.is_a?(Proc)   && validate[answer]) ||
    (validate.respond_to?(:valid?) && validate.valid?(answer))
end