summaryrefslogtreecommitdiff
path: root/lib/highline/question_asker.rb
blob: 53b8988f0781a7bcec6a632340fb66dd52994cd5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157

class QuestionAsker
  attr_reader :question

  include CustomErrors

  def initialize(question, highline)
    @question = question
    @highline = highline
  end

  #
  # Gets one answer, as opposed to #gather
  #
  def ask_once
    # 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.
    @highline.say(question) unless ((question.readline) and (question.echo == true and !question.limit))

    begin
      question.get_response_or_default(@highline)
      raise NotValidQuestionError unless question.valid_answer?

      question.convert

      if question.confirm
        # need to add a layer of scope (new_scope) to ask a question inside a
        # question, without destroying instance data

        raise NoConfirmationQuestionError unless @highline.send(:confirm, question)
      end

    rescue NoConfirmationQuestionError
      explain_error(nil)
      retry

    rescue NotInRangeQuestionError
      explain_error(:not_in_range)
      retry

    rescue NotValidQuestionError
      explain_error(:not_valid)
      retry

    rescue QuestionError
      retry

    rescue ArgumentError => error
      case error.message
      when /ambiguous/
        # the assumption here is that OptionParser::Completion#complete
        # (used for ambiguity resolution) throws exceptions containing
        # the word 'ambiguous' whenever resolution fails
        explain_error(:ambiguous_completion)
        retry
      when /invalid value for/
        explain_error(:invalid_type)
        retry
      else
        raise
      end

    rescue NoAutoCompleteMatch
      explain_error(:no_completion)
      retry
    end
    question.answer
  end

  ## Multiple questions

  #
  # Collects an Array/Hash full of answers as described in
  # HighLine::Question.gather().
  #

  def gather_answers
    original_question_template = question.template
    verify_match = question.verify_match

    begin   # when verify_match is set this loop will repeat until unique_answers == 1
      question.template = original_question_template

      answers =
      case question.gather
      when Integer
        gather_integer
      when ::String, Regexp
        gather_regexp
      when Hash
        gather_hash
      end

      if verify_match && (@highline.send(:unique_answers, answers).size > 1)
        explain_error(:mismatch)
      else
        verify_match = false
      end

    end while verify_match

    question.verify_match ? @highline.send(:last_answer, answers) : answers
  end

  public :gather_answers

  def gather_integer
    answers = []

    answers << ask_once

    question.template = ""

    (question.gather-1).times do
      answers  << ask_once
    end

    answers
  end

  def gather_regexp
    answers = []

    answers << ask_once

    question.template = ""
    until (question.gather.is_a?(::String) and answers.last.to_s == question.gather) or
        (question.gather.is_a?(Regexp) and answers.last.to_s =~ question.gather)
      answers  << ask_once
    end

    answers.pop
    answers
  end

  def gather_hash
    answers = {}

    question.gather.keys.sort.each do |key|
      @highline.key = key
      answers[key]  = ask_once
    end
    answers
  end

  ## Delegate to Highline

  private

  def explain_error(error)
    @highline.say(question.responses[error]) if error
    @highline.say(question.ask_on_error_msg)
  end
end