malysz87-highline 1.5.5 → 1.5.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,514 +0,0 @@
1
- #!/usr/local/bin/ruby -w
2
-
3
- # question.rb
4
- #
5
- # Created by James Edward Gray II on 2005-04-26.
6
- # Copyright 2005 Gray Productions. All rights reserved.
7
- #
8
- # This is Free Software. See LICENSE and COPYING for details.
9
-
10
- require "optparse"
11
- require "date"
12
- require "pathname"
13
-
14
- class HighLine
15
- #
16
- # Question objects contain all the details of a single invocation of
17
- # HighLine.ask(). The object is initialized by the parameters passed to
18
- # HighLine.ask() and then queried to make sure each step of the input
19
- # process is handled according to the users wishes.
20
- #
21
- class Question
22
- # An internal HighLine errors. User code does not need to trap this.
23
- class NoAutoCompleteMatch < StandardError
24
- # do nothing, just creating a unique error type
25
- end
26
-
27
- class NotEnoughAnswers < StandardError
28
- # do nothing, just creating a unique error type
29
- end
30
-
31
- #
32
- # Create an instance of HighLine::Question. Expects a _question_ to ask
33
- # (can be <tt>""</tt>) and an _answer_type_ to convert the answer to.
34
- # The _answer_type_ parameter must be a type recongnized by
35
- # Question.convert(). If given, a block is yeilded the new Question
36
- # object to allow custom initializaion.
37
- #
38
- def initialize( question, answer_type )
39
- # initialize instance data
40
- @question = question
41
- @answer_type = answer_type
42
-
43
- @many_answers = nil
44
- @at_least = 1
45
- @character = nil
46
- @limit = nil
47
- @echo = true
48
- @readline = false
49
- @whitespace = :strip
50
- @case = nil
51
- @default = nil
52
- @validate = nil
53
- @above = nil
54
- @below = nil
55
- @in = nil
56
- @confirm = nil
57
- @gather = false
58
- @first_answer = nil
59
- @directory = Pathname.new(File.expand_path(File.dirname($0)))
60
- @glob = "*"
61
- @responses = Hash.new
62
- @overwrite = false
63
-
64
- # allow block to override settings
65
- yield self if block_given?
66
-
67
- # finalize responses based on settings
68
- build_responses
69
- end
70
-
71
-
72
- # The ERb template of the question to be asked.
73
- attr_accessor :question
74
- # The type that will be used to convert this answer.
75
- attr_accessor :answer_type
76
- #
77
- # Can be set to +true+ to use HighLine's cross-platform character reader
78
- # instead of fetching an entire line of input. (Note: HighLine's
79
- # character reader *ONLY* supports STDIN on Windows and Unix.) Can also
80
- # be set to <tt>:getc</tt> to use that method on the input stream.
81
- #
82
- # *WARNING*: The _echo_ and _overwrite_ attributes for a question are
83
- # ignored when using the <tt>:getc</tt> method.
84
- #
85
- attr_accessor :character
86
- #
87
- # Allows you to read many answers
88
- #
89
- #
90
- # *WARNING*: This option forces a character by character read.
91
- #
92
- attr_accessor :many_answers
93
- #
94
- # Allows you to set a character limit for input.
95
- #
96
- attr_accessor :at_least
97
- #
98
- # Specify minimum number of answers that user has to give. It
99
- # should be used only with :many_answers = true
100
- #
101
- attr_accessor :limit
102
- #
103
- # Can be set to +true+ or +false+ to control whether or not input will
104
- # be echoed back to the user. A setting of +true+ will cause echo to
105
- # match input, but any other true value will be treated as to String to
106
- # echo for each character typed.
107
- #
108
- # This requires HighLine's character reader. See the _character_
109
- # attribute for details.
110
- #
111
- # *Note*: When using HighLine to manage echo on Unix based systems, we
112
- # recommend installing the termios gem. Without it, it's possible to type
113
- # fast enough to have letters still show up (when reading character by
114
- # character only).
115
- #
116
- attr_accessor :echo
117
- #
118
- # Use the Readline library to fetch input. This allows input editing as
119
- # well as keeping a history. In addition, tab will auto-complete
120
- # within an Array of choices or a file listing.
121
- #
122
- # *WARNING*: This option is incompatible with all of HighLine's
123
- # character reading modes and it causes HighLine to ignore the
124
- # specified _input_ stream.
125
- #
126
- attr_accessor :readline
127
- #
128
- # Used to control whitespace processing for the answer to this question.
129
- # See HighLine::Question.remove_whitespace() for acceptable settings.
130
- #
131
- attr_accessor :whitespace
132
- #
133
- # Used to control character case processing for the answer to this question.
134
- # See HighLine::Question.change_case() for acceptable settings.
135
- #
136
- attr_accessor :case
137
- # Used to provide a default answer to this question.
138
- attr_accessor :default
139
- #
140
- # If set to a Regexp, the answer must match (before type conversion).
141
- # Can also be set to a Proc which will be called with the provided
142
- # answer to validate with a +true+ or +false+ return.
143
- #
144
- attr_accessor :validate
145
- # Used to control range checks for answer.
146
- attr_accessor :above, :below
147
- # If set, answer must pass an include?() check on this object.
148
- attr_accessor :in
149
- #
150
- # Asks a yes or no confirmation question, to ensure a user knows what
151
- # they have just agreed to. If set to +true+ the question will be,
152
- # "Are you sure? " Any other true value for this attribute is assumed
153
- # to be the question to ask. When +false+ or +nil+ (the default),
154
- # answers are not confirmed.
155
- #
156
- attr_accessor :confirm
157
- #
158
- # When set, the user will be prompted for multiple answers which will
159
- # be collected into an Array or Hash and returned as the final answer.
160
- #
161
- # You can set _gather_ to an Integer to have an Array of exactly that
162
- # many answers collected, or a String/Regexp to match an end input which
163
- # will not be returned in the Array.
164
- #
165
- # Optionally _gather_ can be set to a Hash. In this case, the question
166
- # will be asked once for each key and the answers will be returned in a
167
- # Hash, mapped by key. The <tt>@key</tt> variable is set before each
168
- # question is evaluated, so you can use it in your question.
169
- #
170
- attr_accessor :gather
171
- #
172
- # When set to a non *nil* value, this will be tried as an answer to the
173
- # question. If this answer passes validations, it will become the result
174
- # without the user ever being prompted. Otherwise this value is discarded,
175
- # and this Question is resolved as a normal call to HighLine.ask().
176
- #
177
- attr_writer :first_answer
178
- #
179
- # The directory from which a user will be allowed to select files, when
180
- # File or Pathname is specified as an _answer_type_. Initially set to
181
- # <tt>Pathname.new(File.expand_path(File.dirname($0)))</tt>.
182
- #
183
- attr_accessor :directory
184
- #
185
- # The glob pattern used to limit file selection when File or Pathname is
186
- # specified as an _answer_type_. Initially set to <tt>"*"</tt>.
187
- #
188
- attr_accessor :glob
189
- #
190
- # A Hash that stores the various responses used by HighLine to notify
191
- # the user. The currently used responses and their purpose are as
192
- # follows:
193
- #
194
- # <tt>:ambiguous_completion</tt>:: Used to notify the user of an
195
- # ambiguous answer the auto-completion
196
- # system cannot resolve.
197
- # <tt>:ask_on_error</tt>:: This is the question that will be
198
- # redisplayed to the user in the event
199
- # of an error. Can be set to
200
- # <tt>:question</tt> to repeat the
201
- # original question.
202
- # <tt>:invalid_type</tt>:: The error message shown when a type
203
- # conversion fails.
204
- # <tt>:no_completion</tt>:: Used to notify the user that their
205
- # selection does not have a valid
206
- # auto-completion match.
207
- # <tt>:not_in_range</tt>:: Used to notify the user that a
208
- # provided answer did not satisfy
209
- # the range requirement tests.
210
- # <tt>:not_valid</tt>:: The error message shown when
211
- # validation checks fail.
212
- #
213
- attr_reader :responses
214
- #
215
- # When set to +true+ the question is asked, but output does not progress to
216
- # the next line. The Cursor is moved back to the beginning of the question
217
- # line and it is cleared so that all the contents of the line disappear from
218
- # the screen.
219
- #
220
- attr_accessor :overwrite
221
-
222
- #
223
- # Returns the provided _answer_string_ or the default answer for this
224
- # Question if a default was set and the answer is empty.
225
- #
226
- def answer_or_default( answer_string )
227
- if answer_string.length == 0 and not @default.nil?
228
- @default
229
- else
230
- answer_string
231
- end
232
- end
233
-
234
- #
235
- # Called late in the initialization process to build intelligent
236
- # responses based on the details of this Question object.
237
- #
238
- def build_responses( )
239
- ### WARNING: This code is quasi-duplicated in ###
240
- ### Menu.update_responses(). Check there too when ###
241
- ### making changes! ###
242
- append_default unless default.nil?
243
- @responses = { :ambiguous_completion =>
244
- "Ambiguous choice. " +
245
- "Please choose from #{@answer_type.inspect}.",
246
- :ask_on_error =>
247
- "? ",
248
- :invalid_type =>
249
- "You must enter a valid #{@answer_type}.",
250
- :no_completion =>
251
- "You must choose from " +
252
- "#{@answer_type.inspect}.",
253
- :not_enough_answers =>
254
- "You must give at least #{@at_least} answers",
255
- :not_in_range =>
256
- "Your answer isn't within the expected range " +
257
- "(#{expected_range}).",
258
- :not_valid =>
259
- "Your answer isn't valid" + (@validate.is_a?(Regexp) ? " (must match #{@validate.inspect})." : "") }.merge(@responses)
260
- ### WARNING: This code is quasi-duplicated in ###
261
- ### Menu.update_responses(). Check there too when ###
262
- ### making changes! ###
263
- end
264
-
265
- #
266
- # Returns the provided _answer_string_ after changing character case by
267
- # the rules of this Question. Valid settings for whitespace are:
268
- #
269
- # +nil+:: Do not alter character case.
270
- # (Default.)
271
- # <tt>:up</tt>:: Calls upcase().
272
- # <tt>:upcase</tt>:: Calls upcase().
273
- # <tt>:down</tt>:: Calls downcase().
274
- # <tt>:downcase</tt>:: Calls downcase().
275
- # <tt>:capitalize</tt>:: Calls capitalize().
276
- #
277
- # An unrecognized choice (like <tt>:none</tt>) is treated as +nil+.
278
- #
279
- def change_case( answer_string )
280
- if [:up, :upcase].include?(@case)
281
- answer_string.upcase
282
- elsif [:down, :downcase].include?(@case)
283
- answer_string.downcase
284
- elsif @case == :capitalize
285
- answer_string.capitalize
286
- else
287
- answer_string
288
- end
289
- end
290
-
291
- # This method converts answers to proper type
292
- def convert( answer_string )
293
- if @many_answers
294
- answer = []
295
- answer_string.split.each do | single_answer |
296
- answer << convert_single_answer(single_answer)
297
- end
298
- if answer.size < @at_least
299
- raise NotEnoughAnswers
300
- end
301
- else
302
- answer = convert_single_answer(answer_string)
303
- end
304
- answer
305
- end
306
-
307
- # Returns a english explination of the current range settings.
308
- def expected_range( )
309
- expected = [ ]
310
-
311
- expected << "above #{@above}" unless @above.nil?
312
- expected << "below #{@below}" unless @below.nil?
313
- expected << "included in #{@in.inspect}" unless @in.nil?
314
-
315
- case expected.size
316
- when 0 then ""
317
- when 1 then expected.first
318
- when 2 then expected.join(" and ")
319
- else expected[0..-2].join(", ") + ", and #{expected.last}"
320
- end
321
- end
322
-
323
- # Returns _first_answer_, which will be unset following this call.
324
- def first_answer( )
325
- @first_answer
326
- ensure
327
- @first_answer = nil
328
- end
329
-
330
- # Returns true if _first_answer_ is set.
331
- def first_answer?( )
332
- not @first_answer.nil?
333
- end
334
-
335
- #
336
- # Returns +true+ if the _answer_object_ is greater than the _above_
337
- # attribute, less than the _below_ attribute and included?()ed in the
338
- # _in_ attribute. Otherwise, +false+ is returned. Any +nil+ attributes
339
- # are not checked.
340
- #
341
- def in_range?( answer_object )
342
- if !@many_answers
343
- answer_object = [answer_object]
344
- end
345
- answer_object.each do | single_answer_object |
346
- return false unless
347
- (@above.nil? or single_answer_object > @above) and
348
- (@below.nil? or single_answer_object < @below) and
349
- (@in.nil? or @in.include?(single_answer_object))
350
- end
351
- return true
352
- end
353
-
354
- #
355
- # Returns the provided _answer_string_ after processing whitespace by
356
- # the rules of this Question. Valid settings for whitespace are:
357
- #
358
- # +nil+:: Do not alter whitespace.
359
- # <tt>:strip</tt>:: Calls strip(). (Default.)
360
- # <tt>:chomp</tt>:: Calls chomp().
361
- # <tt>:collapse</tt>:: Collapses all whitspace runs to a
362
- # single space.
363
- # <tt>:strip_and_collapse</tt>:: Calls strip(), then collapses all
364
- # whitspace runs to a single space.
365
- # <tt>:chomp_and_collapse</tt>:: Calls chomp(), then collapses all
366
- # whitspace runs to a single space.
367
- # <tt>:remove</tt>:: Removes all whitespace.
368
- #
369
- # An unrecognized choice (like <tt>:none</tt>) is treated as +nil+.
370
- #
371
- # This process is skipped, for single character input.
372
- #
373
- def remove_whitespace( answer_string )
374
- if @whitespace.nil?
375
- answer_string
376
- elsif [:strip, :chomp].include?(@whitespace)
377
- answer_string.send(@whitespace)
378
- elsif @whitespace == :collapse
379
- answer_string.gsub(/\s+/, " ")
380
- elsif [:strip_and_collapse, :chomp_and_collapse].include?(@whitespace)
381
- result = answer_string.send(@whitespace.to_s[/^[a-z]+/])
382
- result.gsub(/\s+/, " ")
383
- elsif @whitespace == :remove
384
- answer_string.gsub(/\s+/, "")
385
- else
386
- answer_string
387
- end
388
- end
389
-
390
- #
391
- # Returns an Array of valid answers to this question. These answers are
392
- # only known when _answer_type_ is set to an Array of choices, File, or
393
- # Pathname. Any other time, this method will return an empty Array.
394
- #
395
- def selection( )
396
- if @answer_type.is_a?(Array)
397
- @answer_type
398
- elsif [File, Pathname].include?(@answer_type)
399
- Dir[File.join(@directory.to_s, @glob)].map do |file|
400
- File.basename(file)
401
- end
402
- else
403
- [ ]
404
- end
405
- end
406
-
407
- # Stringifies the question to be asked.
408
- def to_str( )
409
- @question
410
- end
411
-
412
- #
413
- # Returns +true+ if the provided _answer_string_ is accepted by the
414
- # _validate_ attribute or +false+ if it's not.
415
- #
416
- # It's important to realize that an answer is validated after whitespace
417
- # and case handling.
418
- #
419
- def valid_answer?( answer_string )
420
- return true if @validate.nil?
421
- answers = []
422
- if @many_answers
423
- answers = answer_string.split
424
- else
425
- answers << answer_string
426
- end
427
- answers.each do |single_answer_string|
428
- return false unless
429
- (@validate.is_a?(Regexp) and single_answer_string =~ @validate) or
430
- (@validate.is_a?(Proc) and @validate[single_answer_string])
431
- end
432
- return true
433
- end
434
-
435
- private
436
-
437
- # Convert single answer
438
- # Transforms the given _answer_string_ into the expected type for this
439
- # Question. Currently supported conversions are:
440
- #
441
- # <tt>[...]</tt>:: Answer must be a member of the passed Array.
442
- # Auto-completion is used to expand partial
443
- # answers.
444
- # <tt>lambda {...}</tt>:: Answer is passed to lambda for conversion.
445
- # Date:: Date.parse() is called with answer.
446
- # DateTime:: DateTime.parse() is called with answer.
447
- # File:: The entered file name is auto-completed in
448
- # terms of _directory_ + _glob_, opened, and
449
- # returned.
450
- # Float:: Answer is converted with Kernel.Float().
451
- # Integer:: Answer is converted with Kernel.Integer().
452
- # +nil+:: Answer is left in String format. (Default.)
453
- # Pathname:: Same as File, save that a Pathname object is
454
- # returned.
455
- # String:: Answer is converted with Kernel.String().
456
- # Regexp:: Answer is fed to Regexp.new().
457
- # Symbol:: The method to_sym() is called on answer and
458
- # the result returned.
459
- # <i>any other Class</i>:: The answer is passed on to
460
- # <tt>Class.parse()</tt>.
461
- #
462
- # This method throws ArgumentError, if the conversion cannot be
463
- # completed for any reason.
464
- #
465
-
466
- def convert_single_answer( answer_string )
467
- if @answer_type.nil?
468
- answer_string
469
- elsif [Float, Integer, String].include?(@answer_type)
470
- Kernel.send(@answer_type.to_s.to_sym, answer_string)
471
- elsif @answer_type == Symbol
472
- answer_string.to_sym
473
- elsif @answer_type == Regexp
474
- Regexp.new(answer_string)
475
- elsif @answer_type.is_a?(Array) or [File, Pathname].include?(@answer_type)
476
- # cheating, using OptionParser's Completion module
477
- choices = selection
478
- choices.extend(OptionParser::Completion)
479
- answer = choices.complete(answer_string)
480
- if answer.nil?
481
- raise NoAutoCompleteMatch
482
- end
483
- if @answer_type.is_a?(Array)
484
- answer.last
485
- elsif @answer_type == File
486
- File.open(File.join(@directory.to_s, answer.last))
487
- else
488
- Pathname.new(File.join(@directory.to_s, answer.last))
489
- end
490
- elsif [Date, DateTime].include?(@answer_type) or @answer_type.is_a?(Class)
491
- @answer_type.parse(answer_string)
492
- elsif @answer_type.is_a?(Proc)
493
- @answer_type[answer_string]
494
- end
495
- end
496
-
497
- #
498
- # Adds the default choice to the end of question between <tt>|...|</tt>.
499
- # Trailing whitespace is preserved so the function of HighLine.say() is
500
- # not affected.
501
- #
502
- def append_default( )
503
- if @question =~ /([\t ]+)\Z/
504
- @question << "|#{@default}|#{$1}"
505
- elsif @question == ""
506
- @question << "|#{@default}| "
507
- elsif @question[-1, 1] == "\n"
508
- @question[-2, 0] = " |#{@default}|"
509
- else
510
- @question << " |#{@default}|"
511
- end
512
- end
513
- end
514
- end