keybox 1.1.1 → 1.2.0

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