highline-sgonyea 1.6.12

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