canis 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +45 -0
  3. data/CHANGES +52 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +24 -0
  7. data/Rakefile +2 -0
  8. data/canis.gemspec +25 -0
  9. data/examples/alpmenu.rb +46 -0
  10. data/examples/app.sample +19 -0
  11. data/examples/appemail.rb +191 -0
  12. data/examples/atree.rb +105 -0
  13. data/examples/bline.rb +181 -0
  14. data/examples/common/devel.rb +319 -0
  15. data/examples/common/file.rb +93 -0
  16. data/examples/data/README.markdown +9 -0
  17. data/examples/data/brew.txt +38 -0
  18. data/examples/data/color.2 +37 -0
  19. data/examples/data/gemlist.txt +59 -0
  20. data/examples/data/lotr.txt +12 -0
  21. data/examples/data/ports.txt +136 -0
  22. data/examples/data/table.txt +37 -0
  23. data/examples/data/tasks.csv +88 -0
  24. data/examples/data/tasks.txt +27 -0
  25. data/examples/data/todo.txt +16 -0
  26. data/examples/data/todocsv.csv +28 -0
  27. data/examples/data/unix1.txt +21 -0
  28. data/examples/data/unix2.txt +11 -0
  29. data/examples/dbdemo.rb +506 -0
  30. data/examples/dirtree.rb +177 -0
  31. data/examples/newtabbedwindow.rb +100 -0
  32. data/examples/newtesttabp.rb +92 -0
  33. data/examples/tabular.rb +212 -0
  34. data/examples/tasks.rb +179 -0
  35. data/examples/term2.rb +88 -0
  36. data/examples/testbuttons.rb +307 -0
  37. data/examples/testcombo.rb +102 -0
  38. data/examples/testdb.rb +182 -0
  39. data/examples/testfields.rb +208 -0
  40. data/examples/testflowlayout.rb +43 -0
  41. data/examples/testkeypress.rb +98 -0
  42. data/examples/testlistbox.rb +187 -0
  43. data/examples/testlistbox1.rb +199 -0
  44. data/examples/testmessagebox.rb +144 -0
  45. data/examples/testprogress.rb +116 -0
  46. data/examples/testree.rb +107 -0
  47. data/examples/testsplitlayout.rb +53 -0
  48. data/examples/testsplitlayout1.rb +49 -0
  49. data/examples/teststacklayout.rb +48 -0
  50. data/examples/testwsshortcuts.rb +68 -0
  51. data/examples/testwsshortcuts2.rb +129 -0
  52. data/lib/canis.rb +16 -0
  53. data/lib/canis/core/docs/index.txt +104 -0
  54. data/lib/canis/core/docs/list.txt +16 -0
  55. data/lib/canis/core/docs/style_help.yml +34 -0
  56. data/lib/canis/core/docs/tabbedpane.txt +15 -0
  57. data/lib/canis/core/docs/table.txt +31 -0
  58. data/lib/canis/core/docs/textpad.txt +48 -0
  59. data/lib/canis/core/docs/tree.txt +23 -0
  60. data/lib/canis/core/include/.DS_Store +0 -0
  61. data/lib/canis/core/include/action.rb +83 -0
  62. data/lib/canis/core/include/actionmanager.rb +49 -0
  63. data/lib/canis/core/include/appmethods.rb +179 -0
  64. data/lib/canis/core/include/bordertitle.rb +49 -0
  65. data/lib/canis/core/include/canisparser.rb +100 -0
  66. data/lib/canis/core/include/colorparser.rb +437 -0
  67. data/lib/canis/core/include/defaultfilerenderer.rb +64 -0
  68. data/lib/canis/core/include/io.rb +320 -0
  69. data/lib/canis/core/include/layouts/SplitLayout.rb +161 -0
  70. data/lib/canis/core/include/layouts/abstractlayout.rb +213 -0
  71. data/lib/canis/core/include/layouts/flowlayout.rb +104 -0
  72. data/lib/canis/core/include/layouts/stacklayout.rb +109 -0
  73. data/lib/canis/core/include/listbindings.rb +89 -0
  74. data/lib/canis/core/include/listeditable.rb +319 -0
  75. data/lib/canis/core/include/listoperations.rb +61 -0
  76. data/lib/canis/core/include/listselectionmodel.rb +388 -0
  77. data/lib/canis/core/include/multibuffer.rb +173 -0
  78. data/lib/canis/core/include/ractionevent.rb +73 -0
  79. data/lib/canis/core/include/rchangeevent.rb +27 -0
  80. data/lib/canis/core/include/rhistory.rb +95 -0
  81. data/lib/canis/core/include/rinputdataevent.rb +47 -0
  82. data/lib/canis/core/include/textdocument.rb +111 -0
  83. data/lib/canis/core/include/vieditable.rb +175 -0
  84. data/lib/canis/core/include/widgetmenu.rb +66 -0
  85. data/lib/canis/core/system/colormap.rb +165 -0
  86. data/lib/canis/core/system/keydefs.rb +32 -0
  87. data/lib/canis/core/system/ncurses.rb +237 -0
  88. data/lib/canis/core/system/panel.rb +129 -0
  89. data/lib/canis/core/system/window.rb +1081 -0
  90. data/lib/canis/core/util/ansiparser.rb +119 -0
  91. data/lib/canis/core/util/app.rb +696 -0
  92. data/lib/canis/core/util/basestack.rb +412 -0
  93. data/lib/canis/core/util/defaultcolorparser.rb +84 -0
  94. data/lib/canis/core/util/extras/README +5 -0
  95. data/lib/canis/core/util/extras/bottomline.rb +1815 -0
  96. data/lib/canis/core/util/extras/padreader.rb +192 -0
  97. data/lib/canis/core/util/focusmanager.rb +31 -0
  98. data/lib/canis/core/util/helpmanager.rb +160 -0
  99. data/lib/canis/core/util/oldwidgetshortcuts.rb +304 -0
  100. data/lib/canis/core/util/promptmenu.rb +235 -0
  101. data/lib/canis/core/util/rcommandwindow.rb +933 -0
  102. data/lib/canis/core/util/rdialogs.rb +520 -0
  103. data/lib/canis/core/util/textutils.rb +74 -0
  104. data/lib/canis/core/util/viewer.rb +238 -0
  105. data/lib/canis/core/util/widgetshortcuts.rb +508 -0
  106. data/lib/canis/core/widgets/applicationheader.rb +103 -0
  107. data/lib/canis/core/widgets/box.rb +58 -0
  108. data/lib/canis/core/widgets/divider.rb +310 -0
  109. data/lib/canis/core/widgets/extras/README.md +12 -0
  110. data/lib/canis/core/widgets/extras/rtextarea.rb +960 -0
  111. data/lib/canis/core/widgets/extras/stackflow.rb +474 -0
  112. data/lib/canis/core/widgets/keylabelprinter.rb +194 -0
  113. data/lib/canis/core/widgets/listbox.rb +326 -0
  114. data/lib/canis/core/widgets/listfooter.rb +86 -0
  115. data/lib/canis/core/widgets/rcombo.rb +210 -0
  116. data/lib/canis/core/widgets/rcontainer.rb +415 -0
  117. data/lib/canis/core/widgets/rlink.rb +30 -0
  118. data/lib/canis/core/widgets/rmenu.rb +970 -0
  119. data/lib/canis/core/widgets/rmenulink.rb +30 -0
  120. data/lib/canis/core/widgets/rmessagebox.rb +400 -0
  121. data/lib/canis/core/widgets/rprogress.rb +118 -0
  122. data/lib/canis/core/widgets/rtabbedpane.rb +631 -0
  123. data/lib/canis/core/widgets/rtabbedwindow.rb +70 -0
  124. data/lib/canis/core/widgets/rwidget.rb +3634 -0
  125. data/lib/canis/core/widgets/scrollbar.rb +147 -0
  126. data/lib/canis/core/widgets/statusline.rb +113 -0
  127. data/lib/canis/core/widgets/table.rb +1072 -0
  128. data/lib/canis/core/widgets/tabular.rb +264 -0
  129. data/lib/canis/core/widgets/textpad.rb +1674 -0
  130. data/lib/canis/core/widgets/tree.rb +690 -0
  131. data/lib/canis/core/widgets/tree/treecellrenderer.rb +150 -0
  132. data/lib/canis/core/widgets/tree/treemodel.rb +432 -0
  133. data/lib/canis/version.rb +3 -0
  134. metadata +229 -0
@@ -0,0 +1,5 @@
1
+ I have discontinued using bottomline. Use at your own risk. Copy into your project
2
+ if you wish to use it.
3
+
4
+ Padreader is not to be used, use viewer.rb instead. Padreader does not display unless
5
+ you press a key first.
@@ -0,0 +1,1815 @@
1
+ require "date"
2
+ require "erb"
3
+ require 'pathname'
4
+ =begin
5
+ * Name : bottomline.rb
6
+ * Description : routines for input at bottom of screen like vim, or anyother line
7
+ * :
8
+ * Author : jkepler
9
+ * Date : 2010-10-25 12:45
10
+ * License :
11
+ Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
12
+
13
+ The character input routines are from io.rb, however, the user-interface to the input
14
+ is copied from the Highline project (James Earl Gray) with permission.
15
+
16
+ May later use a Label and Field.
17
+
18
+ NOTE : Pls avoid directly using this class. I am trying to redo this so ask, agree and say
19
+ can create their own window and be done with it. The hurdle in that is that ask calls
20
+ say, so when to close the window is not clear within say. Some shakeup is expected by
21
+ 1.4.0 or so.
22
+
23
+ =end
24
+ module Canis
25
+
26
+ # --- History class {{{
27
+ # just so the program does not bomb due to a tiny feature
28
+ # I do not raise error on nil array, i create a dummy array
29
+ # which you likely will not be able to use, in any case it will have only one value
30
+ #
31
+ # Enable field history on UP and DOWN during rb_getstr
32
+ class History < Struct.new(:array, :current_index)
33
+ attr_reader :last_index
34
+ attr_reader :current_index
35
+ attr_reader :array
36
+ def initialize a=nil, c=0
37
+ #raise "Array passed to History cannot be nil" unless a
38
+ #@max_index = a.size
39
+ @array = a || []
40
+ @current_index = c
41
+ @last_index = c
42
+ end
43
+ def last
44
+ @current_index = max_index
45
+ @array.last
46
+ end
47
+ def first
48
+ @current_index = 0
49
+ @array.first
50
+ end
51
+ def max_index
52
+ @array.size - 1
53
+ end
54
+ def up
55
+ item = @array[@current_index]
56
+ previous
57
+ return item
58
+ end
59
+ def next
60
+ @last_index = @current_index
61
+ if @current_index + 1 > max_index
62
+ @current_index = 0
63
+ else
64
+ @current_index += 1
65
+ end
66
+ @array[@current_index]
67
+ end
68
+ def previous
69
+ @last_index = @current_index
70
+ if @current_index - 1 < 0
71
+ @current_index = max_index()
72
+ else
73
+ @current_index -= 1
74
+ end
75
+ @array[@current_index]
76
+ end
77
+ def is_last?
78
+ @current_index == max_index()
79
+ end
80
+ def push item
81
+ $log.debug " XXX history push #{item} " if $log.debug?
82
+ @array.push item
83
+ @current_index = max_index
84
+ end
85
+ end # class
86
+ # --- History class }}}
87
+
88
+ # some variables are polluting space of including app,
89
+ # we should make this a class.
90
+ class Bottomline
91
+ attr_accessor :window
92
+ attr_accessor :message_row
93
+ attr_accessor :name # for debugging
94
+ def initialize win=nil, row=nil
95
+ @window = win
96
+ @message_row = 0 # 2011-10-8
97
+ end
98
+ #
99
+ # create a window at bottom and show and hide it.
100
+ #
101
+ def _create_footer_window h = 1 , w = Ncurses.COLS, t = Ncurses.LINES-1, l = 0
102
+ ewin = Canis::Window.new(h, w , t, l)
103
+ #ewin.bkgd(Ncurses.COLOR_PAIR($promptcolor));
104
+ @window = ewin
105
+ return ewin
106
+ end
107
+
108
+ # ---- highline part {{{
109
+ class QuestionError < StandardError
110
+ # do nothing, just creating a unique error type
111
+ end
112
+ class Question
113
+ # An internal HighLine error. User code does not need to trap this.
114
+ class NoAutoCompleteMatch < StandardError
115
+ # do nothing, just creating a unique error type
116
+ end
117
+
118
+ #
119
+ # Create an instance of HighLine::Question. Expects a _question_ to ask
120
+ # (can be <tt>""</tt>) and an _answer_type_ to convert the answer to.
121
+ # The _answer_type_ parameter must be a type recognized by
122
+ # Question.convert(). If given, a block is yeilded the new Question
123
+ # object to allow custom initializaion.
124
+ #
125
+ def initialize( question, answer_type )
126
+ # initialize instance data
127
+ @question = question
128
+ @answer_type = answer_type
129
+
130
+ @character = nil
131
+ @limit = nil
132
+ @echo = true
133
+ @readline = false
134
+ @whitespace = :strip
135
+ @_case = nil
136
+ @default = nil
137
+ @validate = nil
138
+ @above = nil
139
+ @below = nil
140
+ @in = nil
141
+ @confirm = nil
142
+ @gather = false
143
+ @first_answer = nil
144
+ @directory = Pathname.new(File.expand_path(File.dirname($0)))
145
+ @glob = "*"
146
+ @responses = Hash.new
147
+ @overwrite = false
148
+ @history = nil
149
+
150
+ # allow block to override settings
151
+ yield self if block_given?
152
+
153
+ #$log.debug " XXX default #{@default}" if $log.debug?
154
+ #$log.debug " XXX history #{@history}" if $log.debug?
155
+
156
+ # finalize responses based on settings
157
+ build_responses
158
+ end
159
+
160
+ # The ERb template of the question to be asked.
161
+ attr_accessor :question
162
+ # The type that will be used to convert this answer.
163
+ attr_accessor :answer_type
164
+ #
165
+ # Can be set to +true+ to use HighLine's cross-platform character reader
166
+ # instead of fetching an entire line of input. (Note: HighLine's character
167
+ # reader *ONLY* supports STDIN on Windows and Unix.) Can also be set to
168
+ # <tt>:getc</tt> to use that method on the input stream.
169
+ #
170
+ # *WARNING*: The _echo_ and _overwrite_ attributes for a question are
171
+ # ignored when using the <tt>:getc</tt> method.
172
+ #
173
+ attr_accessor :character
174
+ #
175
+ # Allows you to set a character limit for input.
176
+ #
177
+ # If not set, a default of 100 is used
178
+ #
179
+ attr_accessor :limit
180
+ #
181
+ # Can be set to +true+ or +false+ to control whether or not input will
182
+ # be echoed back to the user. A setting of +true+ will cause echo to
183
+ # match input, but any other true value will be treated as to String to
184
+ # echo for each character typed.
185
+ #
186
+ # This requires HighLine's character reader. See the _character_
187
+ # attribute for details.
188
+ #
189
+ # *Note*: When using HighLine to manage echo on Unix based systems, we
190
+ # recommend installing the termios gem. Without it, it's possible to type
191
+ # fast enough to have letters still show up (when reading character by
192
+ # character only).
193
+ #
194
+ attr_accessor :echo
195
+ #
196
+ # Use the Readline library to fetch input. This allows input editing as
197
+ # well as keeping a history. In addition, tab will auto-complete
198
+ # within an Array of choices or a file listing.
199
+ #
200
+ # *WARNING*: This option is incompatible with all of HighLine's
201
+ # character reading modes and it causes HighLine to ignore the
202
+ # specified _input_ stream.
203
+ #
204
+ # this messes up in ncurses RK 2010-10-24 12:23
205
+ attr_accessor :readline
206
+ #
207
+ # Used to control whitespace processing for the answer to this question.
208
+ # See HighLine::Question.remove_whitespace() for acceptable settings.
209
+ #
210
+ attr_accessor :whitespace
211
+ #
212
+ # Used to control character case processing for the answer to this question.
213
+ # See HighLine::Question.change_case() for acceptable settings.
214
+ #
215
+ attr_accessor :_case
216
+ # Used to provide a default answer to this question.
217
+ attr_accessor :default
218
+ #
219
+ # If set to a Regexp, the answer must match (before type conversion).
220
+ # Can also be set to a Proc which will be called with the provided
221
+ # answer to validate with a +true+ or +false+ return.
222
+ #
223
+ attr_accessor :validate
224
+ # Used to control range checks for answer.
225
+ attr_accessor :above, :below
226
+ # If set, answer must pass an include?() check on this object.
227
+ attr_accessor :in
228
+ #
229
+ # Asks a yes or no confirmation question, to ensure a user knows what
230
+ # they have just agreed to. If set to +true+ the question will be,
231
+ # "Are you sure? " Any other true value for this attribute is assumed
232
+ # to be the question to ask. When +false+ or +nil+ (the default),
233
+ # answers are not confirmed.
234
+ #
235
+ attr_accessor :confirm
236
+ #
237
+ # When set, the user will be prompted for multiple answers which will
238
+ # be collected into an Array or Hash and returned as the final answer.
239
+ #
240
+ # You can set _gather_ to an Integer to have an Array of exactly that
241
+ # many answers collected, or a String/Regexp to match an end input which
242
+ # will not be returned in the Array.
243
+ #
244
+ # Optionally _gather_ can be set to a Hash. In this case, the question
245
+ # will be asked once for each key and the answers will be returned in a
246
+ # Hash, mapped by key. The <tt>@key</tt> variable is set before each
247
+ # question is evaluated, so you can use it in your question.
248
+ #
249
+ attr_accessor :gather
250
+ #
251
+ # When set to a non *nil* value, this will be tried as an answer to the
252
+ # question. If this answer passes validations, it will become the result
253
+ # without the user ever being prompted. Otherwise this value is discarded,
254
+ # and this Question is resolved as a normal call to HighLine.ask().
255
+ #
256
+ attr_writer :first_answer
257
+ #
258
+ # The directory from which a user will be allowed to select files, when
259
+ # File or Pathname is specified as an _answer_type_. Initially set to
260
+ # <tt>Pathname.new(File.expand_path(File.dirname($0)))</tt>.
261
+ #
262
+ attr_accessor :directory
263
+ #
264
+ # The glob pattern used to limit file selection when File or Pathname is
265
+ # specified as an _answer_type_. Initially set to <tt>"*"</tt>.
266
+ #
267
+ attr_accessor :glob
268
+ #
269
+ # A Hash that stores the various responses used by HighLine to notify
270
+ # the user. The currently used responses and their purpose are as
271
+ # follows:
272
+ #
273
+ # <tt>:ambiguous_completion</tt>:: Used to notify the user of an
274
+ # ambiguous answer the auto-completion
275
+ # system cannot resolve.
276
+ # <tt>:ask_on_error</tt>:: This is the question that will be
277
+ # redisplayed to the user in the event
278
+ # of an error. Can be set to
279
+ # <tt>:question</tt> to repeat the
280
+ # original question.
281
+ # <tt>:invalid_type</tt>:: The error message shown when a type
282
+ # conversion fails.
283
+ # <tt>:no_completion</tt>:: Used to notify the user that their
284
+ # selection does not have a valid
285
+ # auto-completion match.
286
+ # <tt>:not_in_range</tt>:: Used to notify the user that a
287
+ # provided answer did not satisfy
288
+ # the range requirement tests.
289
+ # <tt>:not_valid</tt>:: The error message shown when
290
+ # validation checks fail.
291
+ #
292
+ attr_reader :responses
293
+ #
294
+ # When set to +true+ the question is asked, but output does not progress to
295
+ # the next line. The Cursor is moved back to the beginning of the question
296
+ # line and it is cleared so that all the contents of the line disappear from
297
+ # the screen.
298
+ #
299
+ attr_accessor :overwrite
300
+
301
+ #
302
+ # If the user presses tab in ask(), then this proc is used to fill in
303
+ # values. Typically, for files. e.g.
304
+ #
305
+ # q.completion_proc = Proc.new {|str| Dir.glob(str +"*") }
306
+ #
307
+ attr_accessor :completion_proc
308
+
309
+ #
310
+ # Called when any character is pressed with the string.
311
+ #
312
+ # q.change_proc = Proc.new {|str| Dir.glob(str +"*") }
313
+ #
314
+ attr_accessor :change_proc
315
+ #
316
+ # Called when any control-key is pressed, one that we are not handling
317
+ #
318
+ # q.key_handler_proc = Proc.new {|ch| xxxx) }
319
+ #
320
+ attr_accessor :key_handler_proc
321
+
322
+ #
323
+ # text to be shown if user presses M-h
324
+ #
325
+ attr_accessor :help_text
326
+ attr_accessor :color_pair
327
+ attr_accessor :history
328
+
329
+ #
330
+ # Returns the provided _answer_string_ or the default answer for this
331
+ # Question if a default was set and the answer is empty.
332
+ # NOTE: in our case, the user actually edits this value (in highline it
333
+ # is used if user enters blank)
334
+ #
335
+ def answer_or_default( answer_string )
336
+ if answer_string.length == 0 and not @default.nil?
337
+ @default
338
+ else
339
+ answer_string
340
+ end
341
+ end
342
+
343
+ #
344
+ # Called late in the initialization process to build intelligent
345
+ # responses based on the details of this Question object.
346
+ #
347
+ def build_responses( )
348
+ ### WARNING: This code is quasi-duplicated in ###
349
+ ### Menu.update_responses(). Check there too when ###
350
+ ### making changes! ###
351
+ append_default unless default.nil?
352
+ @responses = { :ambiguous_completion =>
353
+ "Ambiguous choice. " +
354
+ "Please choose one of #{@answer_type.inspect}.",
355
+ :ask_on_error =>
356
+ "? ",
357
+ :invalid_type =>
358
+ "You must enter a valid #{@answer_type}.",
359
+ :no_completion =>
360
+ "You must choose one of " +
361
+ "#{@answer_type.inspect}.",
362
+ :not_in_range =>
363
+ "Your answer isn't within the expected range " +
364
+ "(#{expected_range}).",
365
+ :not_valid =>
366
+ "Your answer isn't valid (must match " +
367
+ "#{@validate.inspect})." }.merge(@responses)
368
+ ### WARNING: This code is quasi-duplicated in ###
369
+ ### Menu.update_responses(). Check there too when ###
370
+ ### making changes! ###
371
+ end
372
+
373
+ #
374
+ # Returns the provided _answer_string_ after changing character case by
375
+ # the rules of this Question. Valid settings for whitespace are:
376
+ #
377
+ # +nil+:: Do not alter character case.
378
+ # (Default.)
379
+ # <tt>:up</tt>:: Calls upcase().
380
+ # <tt>:upcase</tt>:: Calls upcase().
381
+ # <tt>:down</tt>:: Calls downcase().
382
+ # <tt>:downcase</tt>:: Calls downcase().
383
+ # <tt>:capitalize</tt>:: Calls capitalize().
384
+ #
385
+ # An unrecognized choice (like <tt>:none</tt>) is treated as +nil+.
386
+ #
387
+ def change_case( answer_string )
388
+ if [:up, :upcase].include?(@_case)
389
+ answer_string.upcase
390
+ elsif [:down, :downcase].include?(@_case)
391
+ answer_string.downcase
392
+ elsif @_case == :capitalize
393
+ answer_string.capitalize
394
+ else
395
+ answer_string
396
+ end
397
+ end
398
+
399
+ #
400
+ # Transforms the given _answer_string_ into the expected type for this
401
+ # Question. Currently supported conversions are:
402
+ #
403
+ # <tt>[...]</tt>:: Answer must be a member of the passed Array.
404
+ # Auto-completion is used to expand partial
405
+ # answers.
406
+ # <tt>lambda {...}</tt>:: Answer is passed to lambda for conversion.
407
+ # Date:: Date.parse() is called with answer.
408
+ # DateTime:: DateTime.parse() is called with answer.
409
+ # File:: The entered file name is auto-completed in
410
+ # terms of _directory_ + _glob_, opened, and
411
+ # returned.
412
+ # Float:: Answer is converted with Kernel.Float().
413
+ # Integer:: Answer is converted with Kernel.Integer().
414
+ # +nil+:: Answer is left in String format. (Default.)
415
+ # Pathname:: Same as File, save that a Pathname object is
416
+ # returned.
417
+ # String:: Answer is converted with Kernel.String().
418
+ # Regexp:: Answer is fed to Regexp.new().
419
+ # Symbol:: The method to_sym() is called on answer and
420
+ # the result returned.
421
+ # <i>any other Class</i>:: The answer is passed on to
422
+ # <tt>Class.parse()</tt>.
423
+ #
424
+ # This method throws ArgumentError, if the conversion cannot be
425
+ # completed for any reason.
426
+ #
427
+ def convert( answer_string )
428
+ if @answer_type.nil?
429
+ answer_string
430
+ elsif [Float, Integer, String].include?(@answer_type)
431
+ Kernel.send(@answer_type.to_s.to_sym, answer_string)
432
+ elsif @answer_type == Symbol
433
+ answer_string.to_sym
434
+ elsif @answer_type == Regexp
435
+ Regexp.new(answer_string)
436
+ elsif @answer_type.is_a?(Array) or [File, Pathname].include?(@answer_type)
437
+ # cheating, using OptionParser's Completion module
438
+ choices = selection
439
+ #choices.extend(OptionParser::Completion)
440
+ #answer = choices.complete(answer_string)
441
+ answer = choices # bug in completion of optparse
442
+ if answer.nil?
443
+ raise NoAutoCompleteMatch
444
+ end
445
+ if @answer_type.is_a?(Array)
446
+ #answer.last # we don't need this anylonger
447
+ answer_string # we have already selected
448
+ elsif @answer_type == File
449
+ File.open(File.join(@directory.to_s, answer_string))
450
+ else
451
+ #Pathname.new(File.join(@directory.to_s, answer.last))
452
+ Pathname.new(File.join(@directory.to_s, answer_string))
453
+ end
454
+ elsif [Date, DateTime].include?(@answer_type) or @answer_type.is_a?(Class)
455
+ @answer_type.parse(answer_string)
456
+ elsif @answer_type.is_a?(Proc)
457
+ @answer_type[answer_string]
458
+ end
459
+ end
460
+
461
+ # Returns a english explination of the current range settings.
462
+ def expected_range( )
463
+ expected = [ ]
464
+
465
+ expected << "above #{@above}" unless @above.nil?
466
+ expected << "below #{@below}" unless @below.nil?
467
+ expected << "included in #{@in.inspect}" unless @in.nil?
468
+
469
+ case expected.size
470
+ when 0 then ""
471
+ when 1 then expected.first
472
+ when 2 then expected.join(" and ")
473
+ else expected[0..-2].join(", ") + ", and #{expected.last}"
474
+ end
475
+ end
476
+
477
+ # Returns _first_answer_, which will be unset following this call.
478
+ def first_answer( )
479
+ @first_answer
480
+ ensure
481
+ @first_answer = nil
482
+ end
483
+
484
+ # Returns true if _first_answer_ is set.
485
+ def first_answer?( )
486
+ not @first_answer.nil?
487
+ end
488
+
489
+ #
490
+ # Returns +true+ if the _answer_object_ is greater than the _above_
491
+ # attribute, less than the _below_ attribute and included?()ed in the
492
+ # _in_ attribute. Otherwise, +false+ is returned. Any +nil+ attributes
493
+ # are not checked.
494
+ #
495
+ def in_range?( answer_object )
496
+ (@above.nil? or answer_object > @above) and
497
+ (@below.nil? or answer_object < @below) and
498
+ (@in.nil? or @in.include?(answer_object))
499
+ end
500
+
501
+ #
502
+ # Returns the provided _answer_string_ after processing whitespace by
503
+ # the rules of this Question. Valid settings for whitespace are:
504
+ #
505
+ # +nil+:: Do not alter whitespace.
506
+ # <tt>:strip</tt>:: Calls strip(). (Default.)
507
+ # <tt>:chomp</tt>:: Calls chomp().
508
+ # <tt>:collapse</tt>:: Collapses all whitspace runs to a
509
+ # single space.
510
+ # <tt>:strip_and_collapse</tt>:: Calls strip(), then collapses all
511
+ # whitspace runs to a single space.
512
+ # <tt>:chomp_and_collapse</tt>:: Calls chomp(), then collapses all
513
+ # whitspace runs to a single space.
514
+ # <tt>:remove</tt>:: Removes all whitespace.
515
+ #
516
+ # An unrecognized choice (like <tt>:none</tt>) is treated as +nil+.
517
+ #
518
+ # This process is skipped, for single character input.
519
+ #
520
+ def remove_whitespace( answer_string )
521
+ if @whitespace.nil?
522
+ answer_string
523
+ elsif [:strip, :chomp].include?(@whitespace)
524
+ answer_string.send(@whitespace)
525
+ elsif @whitespace == :collapse
526
+ answer_string.gsub(/\s+/, " ")
527
+ elsif [:strip_and_collapse, :chomp_and_collapse].include?(@whitespace)
528
+ result = answer_string.send(@whitespace.to_s[/^[a-z]+/])
529
+ result.gsub(/\s+/, " ")
530
+ elsif @whitespace == :remove
531
+ answer_string.gsub(/\s+/, "")
532
+ else
533
+ answer_string
534
+ end
535
+ end
536
+
537
+ #
538
+ # Returns an Array of valid answers to this question. These answers are
539
+ # only known when _answer_type_ is set to an Array of choices, File, or
540
+ # Pathname. Any other time, this method will return an empty Array.
541
+ #
542
+ def selection( )
543
+ if @answer_type.is_a?(Array)
544
+ @answer_type
545
+ elsif [File, Pathname].include?(@answer_type)
546
+ Dir[File.join(@directory.to_s, @glob)].map do |file|
547
+ File.basename(file)
548
+ end
549
+ else
550
+ [ ]
551
+ end
552
+ end
553
+
554
+ # Stringifies the question to be asked.
555
+ def to_str( )
556
+ @question
557
+ end
558
+
559
+ #
560
+ # Returns +true+ if the provided _answer_string_ is accepted by the
561
+ # _validate_ attribute or +false+ if it's not.
562
+ #
563
+ # It's important to realize that an answer is validated after whitespace
564
+ # and case handling.
565
+ #
566
+ def valid_answer?( answer_string )
567
+ @validate.nil? or
568
+ (@validate.is_a?(Regexp) and answer_string =~ @validate) or
569
+ (@validate.is_a?(Proc) and @validate[answer_string])
570
+ end
571
+
572
+ private
573
+
574
+ #
575
+ # Adds the default choice to the end of question between <tt>|...|</tt>.
576
+ # Trailing whitespace is preserved so the function of HighLine.say() is
577
+ # not affected.
578
+ #
579
+ def append_default( )
580
+ if @question =~ /([\t ]+)\Z/
581
+ @question << "|#{@default}|#{$1}"
582
+ elsif @question == ""
583
+ @question << "|#{@default}| "
584
+ elsif @question[-1, 1] == "\n"
585
+ @question[-2, 0] = " |#{@default}|"
586
+ else
587
+ @question << " |#{@default}|"
588
+ end
589
+ end
590
+ end # class
591
+
592
+ # Menu objects encapsulate all the details of a call to HighLine.choose().
593
+ # Using the accessors and Menu.choice() and Menu.choices(), the block passed
594
+ # to HighLine.choose() can detail all aspects of menu display and control.
595
+ #
596
+ class Menu < Question
597
+ #
598
+ # Create an instance of HighLine::Menu. All customization is done
599
+ # through the passed block, which should call accessors and choice() and
600
+ # choices() as needed to define the Menu. Note that Menus are also
601
+ # Questions, so all that functionality is available to the block as
602
+ # well.
603
+ #
604
+ def initialize( )
605
+ #
606
+ # Initialize Question objects with ignored values, we'll
607
+ # adjust ours as needed.
608
+ #
609
+ super("Ignored", [ ], &nil) # avoiding passing the block along
610
+
611
+ @items = [ ]
612
+ @hidden_items = [ ]
613
+ @help = Hash.new("There's no help for that topic.")
614
+
615
+ @index = :number
616
+ @index_suffix = ". "
617
+ @select_by = :index_or_name
618
+ @flow = :rows
619
+ @list_option = nil
620
+ @header = nil
621
+ @prompt = "? "
622
+ @layout = :list
623
+ @shell = false
624
+ @nil_on_handled = false
625
+
626
+ # Override Questions responses, we'll set our own.
627
+ @responses = { }
628
+ # Context for action code.
629
+ @highline = nil
630
+
631
+ yield self if block_given?
632
+
633
+ init_help if @shell and not @help.empty?
634
+ end
635
+
636
+ #
637
+ # An _index_ to append to each menu item in display. See
638
+ # Menu.index=() for details.
639
+ #
640
+ attr_reader :index
641
+ #
642
+ # The String placed between an _index_ and a menu item. Defaults to
643
+ # ". ". Switches to " ", when _index_ is set to a String (like "-").
644
+ #
645
+ attr_accessor :index_suffix
646
+ #
647
+ # The _select_by_ attribute controls how the user is allowed to pick a
648
+ # menu item. The available choices are:
649
+ #
650
+ # <tt>:index</tt>:: The user is allowed to type the numerical
651
+ # or alphetical index for their selection.
652
+ # <tt>:index_or_name</tt>:: Allows both methods from the
653
+ # <tt>:index</tt> option and the
654
+ # <tt>:name</tt> option.
655
+ # <tt>:name</tt>:: Menu items are selected by typing a portion
656
+ # of the item name that will be
657
+ # auto-completed.
658
+ #
659
+ attr_accessor :select_by
660
+ #
661
+ # This attribute is passed directly on as the mode to HighLine.list() by
662
+ # all the preset layouts. See that method for appropriate settings.
663
+ #
664
+ attr_accessor :flow
665
+ #
666
+ # This setting is passed on as the third parameter to HighLine.list()
667
+ # by all the preset layouts. See that method for details of its
668
+ # effects. Defaults to +nil+.
669
+ #
670
+ attr_accessor :list_option
671
+ #
672
+ # Used by all the preset layouts to display title and/or introductory
673
+ # information, when set. Defaults to +nil+.
674
+ #
675
+ attr_accessor :header
676
+ #
677
+ # Used by all the preset layouts to ask the actual question to fetch a
678
+ # menu selection from the user. Defaults to "? ".
679
+ #
680
+ attr_accessor :prompt
681
+ #
682
+ # An ERb _layout_ to use when displaying this Menu object. See
683
+ # Menu.layout=() for details.
684
+ #
685
+ attr_reader :layout
686
+ #
687
+ # When set to +true+, responses are allowed to be an entire line of
688
+ # input, including details beyond the command itself. Only the first
689
+ # "word" of input will be matched against the menu choices, but both the
690
+ # command selected and the rest of the line will be passed to provided
691
+ # action blocks. Defaults to +false+.
692
+ #
693
+ attr_accessor :shell
694
+ #
695
+ # When +true+, any selected item handled by provided action code, will
696
+ # return +nil+, instead of the results to the action code. This may
697
+ # prove handy when dealing with mixed menus where only the names of
698
+ # items without any code (and +nil+, of course) will be returned.
699
+ # Defaults to +false+.
700
+ #
701
+ attr_accessor :nil_on_handled
702
+
703
+ #
704
+ # Adds _name_ to the list of available menu items. Menu items will be
705
+ # displayed in the order they are added.
706
+ #
707
+ # An optional _action_ can be associated with this name and if provided,
708
+ # it will be called if the item is selected. The result of the method
709
+ # will be returned, unless _nil_on_handled_ is set (when you would get
710
+ # +nil+ instead). In _shell_ mode, a provided block will be passed the
711
+ # command chosen and any details that followed the command. Otherwise,
712
+ # just the command is passed. The <tt>@highline</tt> variable is set to
713
+ # the current HighLine context before the action code is called and can
714
+ # thus be used for adding output and the like.
715
+ #
716
+ def choice( name, help = nil, &action )
717
+ @items << [name, action]
718
+
719
+ @help[name.to_s.downcase] = help unless help.nil?
720
+ update_responses # rebuild responses based on our settings
721
+ end
722
+
723
+ #
724
+ # A shortcut for multiple calls to the sister method choice(). <b>Be
725
+ # warned:</b> An _action_ set here will apply to *all* provided
726
+ # _names_. This is considered to be a feature, so you can easily
727
+ # hand-off interface processing to a different chunk of code.
728
+ #
729
+ def choices( *names, &action )
730
+ names.each { |n| choice(n, &action) }
731
+ end
732
+
733
+ # Identical to choice(), but the item will not be listed for the user.
734
+ def hidden( name, help = nil, &action )
735
+ @hidden_items << [name, action]
736
+
737
+ @help[name.to_s.downcase] = help unless help.nil?
738
+ end
739
+
740
+ #
741
+ # Sets the indexing style for this Menu object. Indexes are appended to
742
+ # menu items, when displayed in list form. The available settings are:
743
+ #
744
+ # <tt>:number</tt>:: Menu items will be indexed numerically, starting
745
+ # with 1. This is the default method of indexing.
746
+ # <tt>:letter</tt>:: Items will be indexed alphabetically, starting
747
+ # with a.
748
+ # <tt>:none</tt>:: No index will be appended to menu items.
749
+ # <i>any String</i>:: Will be used as the literal _index_.
750
+ #
751
+ # Setting the _index_ to <tt>:none</tt> a literal String, also adjusts
752
+ # _index_suffix_ to a single space and _select_by_ to <tt>:none</tt>.
753
+ # Because of this, you should make a habit of setting the _index_ first.
754
+ #
755
+ def index=( style )
756
+ @index = style
757
+
758
+ # Default settings.
759
+ if @index == :none or @index.is_a?(String)
760
+ @index_suffix = " "
761
+ @select_by = :name
762
+ end
763
+ end
764
+
765
+ #
766
+ # Initializes the help system by adding a <tt>:help</tt> choice, some
767
+ # action code, and the default help listing.
768
+ #
769
+ def init_help( )
770
+ return if @items.include?(:help)
771
+
772
+ topics = @help.keys.sort
773
+ help_help = @help.include?("help") ? @help["help"] :
774
+ "This command will display helpful messages about " +
775
+ "functionality, like this one. To see the help for " +
776
+ "a specific topic enter:\n\thelp [TOPIC]\nTry asking " +
777
+ "for help on any of the following:\n\n" +
778
+ "<%= list(#{topics.inspect}, :columns_across) %>"
779
+ choice(:help, help_help) do |command, topic|
780
+ topic.strip!
781
+ topic.downcase!
782
+ if topic.empty?
783
+ @highline.say(@help["help"])
784
+ else
785
+ @highline.say("= #{topic}\n\n#{@help[topic]}")
786
+ end
787
+ end
788
+ end
789
+
790
+ #
791
+ # Used to set help for arbitrary topics. Use the topic <tt>"help"</tt>
792
+ # to override the default message.
793
+ #
794
+ def help( topic, help )
795
+ @help[topic] = help
796
+ end
797
+
798
+ #
799
+ # Setting a _layout_ with this method also adjusts some other attributes
800
+ # of the Menu object, to ideal defaults for the chosen _layout_. To
801
+ # account for that, you probably want to set a _layout_ first in your
802
+ # configuration block, if needed.
803
+ #
804
+ # Accepted settings for _layout_ are:
805
+ #
806
+ # <tt>:list</tt>:: The default _layout_. The _header_ if set
807
+ # will appear at the top on its own line with
808
+ # a trailing colon. Then the list of menu
809
+ # items will follow. Finally, the _prompt_
810
+ # will be used as the ask()-like question.
811
+ # <tt>:one_line</tt>:: A shorter _layout_ that fits on one line.
812
+ # The _header_ comes first followed by a
813
+ # colon and spaces, then the _prompt_ with menu
814
+ # items between trailing parenthesis.
815
+ # <tt>:menu_only</tt>:: Just the menu items, followed up by a likely
816
+ # short _prompt_.
817
+ # <i>any ERb String</i>:: Will be taken as the literal _layout_. This
818
+ # String can access <tt>@header</tt>,
819
+ # <tt>@menu</tt> and <tt>@prompt</tt>, but is
820
+ # otherwise evaluated in the typical HighLine
821
+ # context, to provide access to utilities like
822
+ # HighLine.list() primarily.
823
+ #
824
+ # If set to either <tt>:one_line</tt>, or <tt>:menu_only</tt>, _index_
825
+ # will default to <tt>:none</tt> and _flow_ will default to
826
+ # <tt>:inline</tt>.
827
+ #
828
+ def layout=( new_layout )
829
+ @layout = new_layout
830
+
831
+ # Default settings.
832
+ case @layout
833
+ when :one_line, :menu_only
834
+ self.index = :none
835
+ @flow = :inline
836
+ end
837
+ end
838
+
839
+ #
840
+ # This method returns all possible options for auto-completion, based
841
+ # on the settings of _index_ and _select_by_.
842
+ #
843
+ def options( )
844
+ # add in any hidden menu commands
845
+ @items.concat(@hidden_items)
846
+
847
+ by_index = if @index == :letter
848
+ l_index = "`"
849
+ @items.map { "#{l_index.succ!}" }
850
+ else
851
+ (1 .. @items.size).collect { |s| String(s) }
852
+ end
853
+ by_name = @items.collect { |c| c.first }
854
+
855
+ case @select_by
856
+ when :index then
857
+ by_index
858
+ when :name
859
+ by_name
860
+ else
861
+ by_index + by_name
862
+ end
863
+ ensure
864
+ # make sure the hidden items are removed, before we return
865
+ @items.slice!(@items.size - @hidden_items.size, @hidden_items.size)
866
+ end
867
+
868
+ #
869
+ # This method processes the auto-completed user selection, based on the
870
+ # rules for this Menu object. If an action was provided for the
871
+ # selection, it will be executed as described in Menu.choice().
872
+ #
873
+ def select( highline_context, selection, details = nil )
874
+ # add in any hidden menu commands
875
+ @items.concat(@hidden_items)
876
+
877
+ # Find the selected action.
878
+ name, action = if selection =~ /^\d+$/
879
+ @items[selection.to_i - 1]
880
+ else
881
+ l_index = "`"
882
+ index = @items.map { "#{l_index.succ!}" }.index(selection)
883
+ $log.debug "iindex #{index}, #{@items} " if $log.debug?
884
+ @items.find { |c| c.first == selection } or @items[index]
885
+ end
886
+
887
+ # Run or return it.
888
+ if not @nil_on_handled and not action.nil?
889
+ @highline = highline_context
890
+ if @shell
891
+ action.call(name, details)
892
+ else
893
+ action.call(name)
894
+ end
895
+ elsif action.nil?
896
+ name
897
+ else
898
+ nil
899
+ end
900
+ ensure
901
+ # make sure the hidden items are removed, before we return
902
+ @items.slice!(@items.size - @hidden_items.size, @hidden_items.size)
903
+ end
904
+
905
+ #
906
+ # Allows Menu objects to pass as Arrays, for use with HighLine.list().
907
+ # This method returns all menu items to be displayed, complete with
908
+ # indexes.
909
+ #
910
+ def to_ary( )
911
+ case @index
912
+ when :number
913
+ @items.map { |c| "#{@items.index(c) + 1}#{@index_suffix}#{c.first}" }
914
+ when :letter
915
+ l_index = "`"
916
+ @items.map { |c| "#{l_index.succ!}#{@index_suffix}#{c.first}" }
917
+ when :none
918
+ @items.map { |c| "#{c.first}" }
919
+ else
920
+ @items.map { |c| "#{index}#{@index_suffix}#{c.first}" }
921
+ end
922
+ end
923
+
924
+ #
925
+ # Allows Menu to behave as a String, just like Question. Returns the
926
+ # _layout_ to be rendered, which is used by HighLine.say().
927
+ #
928
+ def to_str( )
929
+ case @layout
930
+ when :list
931
+ '<%= if @header.nil? then '' else "#{@header}:\n" end %>' +
932
+ "<%= list( @menu, #{@flow.inspect},
933
+ #{@list_option.inspect} ) %>" +
934
+ "<%= @prompt %>"
935
+ when :one_line
936
+ '<%= if @header.nil? then '' else "#{@header}: " end %>' +
937
+ "<%= @prompt %>" +
938
+ "(<%= list( @menu, #{@flow.inspect},
939
+ #{@list_option.inspect} ) %>)" +
940
+ "<%= @prompt[/\s*$/] %>"
941
+ when :menu_only
942
+ "<%= list( @menu, #{@flow.inspect},
943
+ #{@list_option.inspect} ) %><%= @prompt %>"
944
+ else
945
+ @layout
946
+ end
947
+ end
948
+
949
+ #
950
+ # This method will update the intelligent responses to account for
951
+ # Menu specific differences. This overrides the work done by
952
+ # Question.build_responses().
953
+ #
954
+ def update_responses( )
955
+ append_default unless default.nil?
956
+ @responses = @responses.merge(
957
+ :ambiguous_completion =>
958
+ "Ambiguous choice. " +
959
+ "Please choose one of #{options.inspect}.",
960
+ :ask_on_error =>
961
+ "? ",
962
+ :invalid_type =>
963
+ "You must enter a valid #{options}.",
964
+ :no_completion =>
965
+ "You must choose one of " +
966
+ "#{options.inspect}.",
967
+ :not_in_range =>
968
+ "Your answer isn't within the expected range " +
969
+ "(#{expected_range}).",
970
+ :not_valid =>
971
+ "Your answer isn't valid (must match " +
972
+ "#{@validate.inspect})."
973
+ )
974
+ end
975
+ end
976
+ # --- highline classes }}}
977
+ def ask(question, answer_type=String, &details)
978
+ $log.debug "XXXX inside ask win #{@window} "
979
+ @window ||= _create_footer_window
980
+ #@window.show #unless @window.visible?
981
+
982
+ @question ||= Question.new(question, answer_type, &details)
983
+ say(@question) #unless @question.echo == true
984
+
985
+ @completion_proc = @question.completion_proc
986
+ @change_proc = @question.change_proc
987
+ @key_handler_proc = @question.key_handler_proc
988
+ @default = @question.default
989
+ $log.debug "XXX: ASK RBGETS got default: #{@default} "
990
+ @help_text = @question.help_text
991
+ @answer_type = @question.answer_type
992
+ if @question.answer_type.is_a? Array
993
+ @completion_proc = Proc.new{|str| @answer_type.dup.grep Regexp.new("^#{str}") }
994
+ end
995
+
996
+ begin
997
+ # FIXME a C-c still returns default to user !
998
+ @answer = @question.answer_or_default(get_response)
999
+ unless @question.valid_answer?(@answer)
1000
+ explain_error(:not_valid)
1001
+ raise QuestionError
1002
+ end
1003
+
1004
+ @answer = @question.convert(@answer)
1005
+
1006
+ if @question.in_range?(@answer)
1007
+ if @question.confirm
1008
+ # need to add a layer of scope to ask a question inside a
1009
+ # question, without destroying instance data
1010
+ context_change = self.class.new(@input, @output, @wrap_at, @page_at)
1011
+ if @question.confirm == true
1012
+ confirm_question = "Are you sure? "
1013
+ else
1014
+ # evaluate ERb under initial scope, so it will have
1015
+ # access to @question and @answer
1016
+ template = ERB.new(@question.confirm, nil, "%")
1017
+ confirm_question = template.result(binding)
1018
+ end
1019
+ unless context_change.agree(confirm_question)
1020
+ explain_error(nil)
1021
+ raise QuestionError
1022
+ end
1023
+ end
1024
+
1025
+ @answer
1026
+ else
1027
+ explain_error(:not_in_range)
1028
+ raise QuestionError
1029
+ end
1030
+ rescue QuestionError
1031
+ retry
1032
+ rescue ArgumentError, NameError => error
1033
+ #raise
1034
+ raise if error.is_a?(NoMethodError)
1035
+ if error.message =~ /ambiguous/
1036
+ # the assumption here is that OptionParser::Completion#complete
1037
+ # (used for ambiguity resolution) throws exceptions containing
1038
+ # the word 'ambiguous' whenever resolution fails
1039
+ explain_error(:ambiguous_completion)
1040
+ else
1041
+ explain_error(:invalid_type)
1042
+ end
1043
+ retry
1044
+ rescue Question::NoAutoCompleteMatch
1045
+ explain_error(:no_completion)
1046
+ retry
1047
+ rescue Interrupt
1048
+ $log.warn "User interrupted ask() get_response does not want operation to proceed"
1049
+ return nil
1050
+ ensure
1051
+ @question = nil # Reset Question object.
1052
+ $log.debug "XXX: HIDE B AT ENSURE OF ASK"
1053
+ hide_bottomline # assuming this method made it visible, not sure if this is called.
1054
+ end
1055
+ end
1056
+ #
1057
+ # bottomline user has to hide window if he called say().
1058
+ # Call this if you find the window persists after using some method from here
1059
+ # usually say or ask.
1060
+ #
1061
+ # NOTE: after callign this you must call window.show. Otherwise, next time
1062
+ # you call this, it will not hide.
1063
+ #
1064
+ # @param [int, float] time to sleep before hiding window.
1065
+ #
1066
+ def hide wait=nil
1067
+ if @window
1068
+ $log.debug "XXX: HIDE BOTTOMLINE INSIDE"
1069
+ sleep(wait) if wait
1070
+ #if @window.visible?
1071
+ #@window.hide # THIS HAS SUDDENLY STOPPED WORKING
1072
+ @window.destroy
1073
+ @window = nil
1074
+ #@window.wrefresh
1075
+ #Ncurses::Panel.update_panels
1076
+ #end
1077
+ end
1078
+ end
1079
+ alias :hide_bottomline :hide
1080
+ #
1081
+ # destroy window, to be called by app when shutting down
1082
+ # since we are normally hiding the window only.
1083
+ def destroy
1084
+ $log.debug "bottomline destroy... #{@window} "
1085
+ @window.destroy if @window
1086
+ @window = nil
1087
+ end
1088
+
1089
+ #
1090
+ # The basic output method for HighLine objects.
1091
+ #
1092
+ # The _statement_ parameter is processed as an ERb template, supporting
1093
+ # embedded Ruby code. The template is evaluated with a binding inside
1094
+ # the HighLine instance.
1095
+ # NOTE: modified from original highline, does not care about space at end of
1096
+ # question. Also, ansi color constants will not work. Be careful what ruby code
1097
+ # you pass in.
1098
+ #
1099
+ # NOTE: This uses a window, so it will persist in the last row. You must call
1100
+ # hide_bottomline to remove the window. It is preferable to call say_with_pause
1101
+ # from user programs
1102
+ #
1103
+ def say statement, config={}
1104
+ @window ||= _create_footer_window
1105
+ #@window.show #unless @window.visible?
1106
+ $log.debug "XXX: inside say win #{@window} !"
1107
+ case statement
1108
+ when Question
1109
+
1110
+ if config.has_key? :color_pair
1111
+ $log.debug "INSIDE QUESTION 2 " if $log.debug?
1112
+ else
1113
+ $log.debug "XXXX SAY using colorpair: #{statement.color_pair} " if $log.debug?
1114
+ config[:color_pair] = statement.color_pair
1115
+ end
1116
+ else
1117
+ $log.debug "XXX INSDIE SAY #{statement.class} " if $log.debug?
1118
+ end
1119
+ statement = statement.to_str
1120
+ template = ERB.new(statement, nil, "%")
1121
+ statement = template.result(binding)
1122
+
1123
+ @prompt_length = statement.length # required by ask since it prints after
1124
+ @statement = statement #
1125
+ clear_line
1126
+ print_str statement, config
1127
+ end
1128
+ #
1129
+ # display some text at bottom and wait for a key before hiding window
1130
+ #
1131
+ def say_with_pause statement, config={}
1132
+ @window ||= _create_footer_window
1133
+ #@window.show #unless @window.visible? # 2011-10-14 23:52:52
1134
+ say statement, config
1135
+ @window.wrefresh
1136
+ Ncurses::Panel.update_panels
1137
+ ch=@window.getchar()
1138
+ hide_bottomline
1139
+ ## return char so we can use for asking for one character
1140
+ return ch
1141
+ end
1142
+ # since say does not leave the screen, it is not exactly recommended
1143
+ # as it will hide what's below. It's better to call pause, or this, which
1144
+ # will quickly go off. If the message is not important enough to ask for a pause,
1145
+ # the will flicker on screen, but not for too long.
1146
+ def say_with_wait statement, config={}
1147
+ @window ||= _create_footer_window
1148
+ #@window.show #unless @window.visible? # 2011-10-14 23:52:59
1149
+ say statement, config
1150
+ @window.wrefresh
1151
+ Ncurses::Panel.update_panels
1152
+ sleep 0.5
1153
+ hide_bottomline
1154
+ end
1155
+ # A helper method for sending the output stream and error and repeat
1156
+ # of the question.
1157
+ #
1158
+ # FIXME: since we write on one line in say, this often gets overidden
1159
+ # by next say or ask
1160
+ def explain_error( error )
1161
+ say_with_pause(@question.responses[error]) unless error.nil?
1162
+ if @question.responses[:ask_on_error] == :question
1163
+ say(@question)
1164
+ elsif @question.responses[:ask_on_error]
1165
+ say(@question.responses[:ask_on_error])
1166
+ end
1167
+ end
1168
+
1169
+ #
1170
+ # Internal method for printing a string
1171
+ #
1172
+ def print_str(text, config={})
1173
+ win = config.fetch(:window, @window) # assuming its in App
1174
+ x = config.fetch :x, 0 # @message_row # Ncurses.LINES-1, 0 since one line window 2011-10-8
1175
+ y = config.fetch :y, 0
1176
+ $log.debug "XXX: print_str #{win} with text : #{text} at #{x} #{y} "
1177
+ color = config[:color_pair] || $datacolor
1178
+ raise "no window for ask print in #{self.class} name: #{name} " unless win
1179
+ color=Ncurses.COLOR_PAIR(color);
1180
+ win.attron(color);
1181
+ #win.mvprintw(x, y, "%-40s" % text);
1182
+ win.mvprintw(x, y, "%s" % text);
1183
+ win.attroff(color);
1184
+ win.refresh # FFI NW 2011-09-9 , added back gets overwritten
1185
+ end
1186
+
1187
+ # actual input routine, gets each character from user, taking care of echo, limit,
1188
+ # completion proc, and some control characters such as C-a, C-e, C-k
1189
+ # Taken from io.rb, has some improvements to it. However, does not print the prompt
1190
+ # any longer
1191
+ # Completion proc is vim style, on pressing tab it cycles through options
1192
+ def rb_getstr
1193
+ r = @message_row
1194
+ c = 0
1195
+ win = @window
1196
+ @limit = @question.limit
1197
+ @history = @question.history
1198
+ @history_list = History.new(@history)
1199
+ maxlen = @limit || 100 # fixme
1200
+
1201
+
1202
+ raise "rb_getstr got no window. bottomline.rb" if win.nil?
1203
+ ins_mode = false
1204
+ oldstr = nil # for tab completion, origal word entered by user
1205
+ default = @default || ""
1206
+ $log.debug "XXX: RBGETS got default: #{@default} "
1207
+ if @default && @history
1208
+ if !@history.include?(default)
1209
+ @history_list.push default
1210
+ end
1211
+ end
1212
+
1213
+ len = @prompt_length
1214
+
1215
+ # clear the area of len+maxlen
1216
+ color = $datacolor
1217
+ str = ""
1218
+ #str = default
1219
+ cpentries = nil
1220
+ #clear_line len+maxlen+1
1221
+ #print_str(prompt+str)
1222
+ print_str(str, :y => @prompt_length+0) if @default
1223
+ len = @prompt_length + str.length
1224
+ begin
1225
+ Ncurses.noecho();
1226
+ curpos = str.length
1227
+ prevchar = 0
1228
+ entries = nil
1229
+ while true
1230
+ ch=win.getchar()
1231
+ $log.debug " XXXX FFI rb_getstr got ch:#{ch}, str:#{str}. "
1232
+ case ch
1233
+ when 3 # -1 # C-c # sometimes this causes an interrupt and crash
1234
+ return -1, nil
1235
+ when ?\C-g.getbyte(0) # ABORT, emacs style
1236
+ return -1, nil
1237
+ when 10, 13 # hits ENTER, complete entry and return
1238
+ @history_list.push str
1239
+ break
1240
+ when ?\C-h.getbyte(0), ?\C-?.getbyte(0), KEY_BSPACE, 263 # delete previous character/backspace
1241
+ # C-h is giving 263 i/o 8. 2011-09-19
1242
+ len -= 1 if len > @prompt_length
1243
+ curpos -= 1 if curpos > 0
1244
+ str.slice!(curpos)
1245
+ clear_line len+maxlen+1, @prompt_length
1246
+ when 330 # delete character on cursor
1247
+ str.slice!(curpos) #rescue next
1248
+ clear_line len+maxlen+1, @prompt_length
1249
+ when ?\M-h.getbyte(0) # HELP KEY
1250
+ help_text = @help_text || "No help provided..."
1251
+ print_help(help_text)
1252
+ clear_line len+maxlen+1
1253
+ print_str @statement # UGH
1254
+ #return 7, nil
1255
+ #next
1256
+ when KEY_LEFT
1257
+ curpos -= 1 if curpos > 0
1258
+ len -= 1 if len > @prompt_length
1259
+ win.move r, c+len # since getchar is not going back on del and bs wmove to move FFIWINDOW
1260
+ win.wrefresh
1261
+ next
1262
+ when KEY_RIGHT
1263
+ if curpos < str.length
1264
+ curpos += 1 #if curpos < str.length
1265
+ len += 1
1266
+ win.move r, c+len # since getchar is not going back on del and bs
1267
+ win.wrefresh
1268
+ end
1269
+ next
1270
+ when ?\C-a.getbyte(0)
1271
+ #olen = str.length
1272
+ clear_line len+maxlen+1, @prompt_length
1273
+ len -= curpos
1274
+ curpos = 0
1275
+ win.move r, c+len # since getchar is not going back on del and bs
1276
+ when ?\C-e.getbyte(0)
1277
+ olen = str.length
1278
+ len += (olen - curpos)
1279
+ curpos = olen
1280
+ clear_line len+maxlen+1, @prompt_length
1281
+ win.move r, c+len # since getchar is not going back on del and bs
1282
+
1283
+ when ?\M-i.getbyte(0)
1284
+ ins_mode = !ins_mode
1285
+ next
1286
+ when ?\C-k.getbyte(0) # delete forward
1287
+ @delete_buffer = str.slice!(curpos..-1) #rescue next
1288
+ clear_line len+maxlen+1, @prompt_length
1289
+ when ?\C-u.getbyte(0) # delete to the left of cursor till start of line
1290
+ @delete_buffer = str.slice!(0..curpos-1) #rescue next
1291
+ curpos = 0
1292
+ clear_line len+maxlen+1, @prompt_length
1293
+ len = @prompt_length
1294
+ when ?\C-y.getbyte(0) # paste what's in delete buffer
1295
+ if @delete_buffer
1296
+ olen = str.length
1297
+ str << @delete_buffer if @delete_buffer
1298
+ curpos = str.length
1299
+ len += str.length - olen
1300
+ end
1301
+ when KEY_TAB # TAB
1302
+ if !@completion_proc.nil?
1303
+ # place cursor at end of completion
1304
+ # after all completions, what user entered should come back so he can edit it
1305
+ if prevchar == 9
1306
+ if !entries.nil? and !entries.empty?
1307
+ olen = str.length
1308
+ str = entries.delete_at(0)
1309
+ str = str.to_s.dup
1310
+ #str = entries[@current_index].dup
1311
+ #@current_index += 1
1312
+ #@current_index = 0 if @current_index == entries.length
1313
+ curpos = str.length
1314
+ len += str.length - olen
1315
+ clear_line len+maxlen+1, @prompt_length
1316
+ else
1317
+ olen = str.length
1318
+ str = oldstr if oldstr
1319
+ curpos = str.length
1320
+ len += str.length - olen
1321
+ clear_line len+maxlen+1, @prompt_length
1322
+ prevchar = ch = nil # so it can start again completing
1323
+ end
1324
+ else
1325
+ #@current_index = 0
1326
+ tabc = @completion_proc unless tabc
1327
+ next unless tabc
1328
+ oldstr = str.dup
1329
+ olen = str.length
1330
+ entries = tabc.call(str).dup
1331
+ $log.debug "XXX tab [#{str}] got #{entries} "
1332
+ str = entries.delete_at(0) unless entries.nil? or entries.empty?
1333
+ #str = entries[@current_index].dup unless entries.nil? or entries.empty?
1334
+ #@current_index += 1
1335
+ #@current_index = 0 if @current_index == entries.length
1336
+ str = str.to_s.dup
1337
+ if str
1338
+ curpos = str.length
1339
+ len += str.length - olen
1340
+ else
1341
+ alert "NO MORE 2"
1342
+ end
1343
+ end
1344
+ else
1345
+ # there's another type of completion that bash does, which is irritating
1346
+ # compared to what vim does, it does partial completion
1347
+ if cpentries
1348
+ olen = str.length
1349
+ if cpentries.size == 1
1350
+ str = cpentries.first.dup
1351
+ elsif cpentries.size > 1
1352
+ str = shortest_match(cpentries).dup
1353
+ end
1354
+ curpos = str.length
1355
+ len += str.length - olen
1356
+ end
1357
+ end
1358
+ when ?\C-a.getbyte(0) .. ?\C-z.getbyte(0)
1359
+ # here' swhere i wish i could pass stuff back without closing
1360
+ # I'd like the user to be able to scroll list or do something based on
1361
+ # control or other keys
1362
+ if @key_handler_proc # added 2011-11-3 7:38 PM
1363
+ @key_handler_proc.call(ch)
1364
+ next
1365
+ else
1366
+ Ncurses.beep
1367
+ end
1368
+ when KEY_UP
1369
+ if @history && !@history.empty?
1370
+ olen = str.length
1371
+ str = if prevchar == KEY_UP
1372
+ @history_list.previous
1373
+ elsif prevchar == KEY_DOWN
1374
+ @history_list.previous
1375
+ else
1376
+ @history_list.last
1377
+ end
1378
+ str = str.dup
1379
+ curpos = str.length
1380
+ len += str.length - olen
1381
+ clear_line len+maxlen+1, @prompt_length
1382
+ else # try to pick up default, seems we don't get it 2011-10-14
1383
+ if @default
1384
+ olen = str.length
1385
+ str = @default
1386
+ str = str.dup
1387
+ curpos = str.length
1388
+ len += str.length - olen
1389
+ clear_line len+maxlen+1, @prompt_length
1390
+ end
1391
+ end
1392
+ when KEY_DOWN
1393
+ if @history && !@history.empty?
1394
+ olen = str.length
1395
+ str = if prevchar == KEY_UP
1396
+ @history_list.next
1397
+ elsif prevchar == KEY_DOWN
1398
+ @history_list.next
1399
+ else
1400
+ @history_list.first
1401
+ end
1402
+ str = str.dup
1403
+ curpos = str.length
1404
+ len += str.length - olen
1405
+ clear_line len+maxlen+1, @prompt_length
1406
+ end
1407
+
1408
+ else
1409
+ if ch < 0 || ch > 255
1410
+ Ncurses.beep
1411
+ next
1412
+ end
1413
+ # if control char, beep
1414
+ if ch.chr =~ /[[:cntrl:]]/
1415
+ Ncurses.beep
1416
+ next
1417
+ end
1418
+ # we need to trap KEY_LEFT and RIGHT and what of UP for history ?
1419
+ if ins_mode
1420
+ str[curpos] = ch.chr
1421
+ else
1422
+ str.insert(curpos, ch.chr) # FIXME index out of range due to changeproc
1423
+ end
1424
+ len += 1
1425
+ curpos += 1
1426
+ break if str.length >= maxlen
1427
+ end
1428
+ case @question.echo
1429
+ when true
1430
+ begin
1431
+ cpentries = @change_proc.call(str) if @change_proc # added 2010-11-09 23:28
1432
+ rescue => exc
1433
+ $log.error "bottomline: change_proc EXC #{exc} " if $log.debug?
1434
+ $log.error( exc) if exc
1435
+ $log.error(exc.backtrace.join("\n")) if exc
1436
+ Ncurses.error
1437
+ end
1438
+ print_str(str, :y => @prompt_length+0)
1439
+ when false
1440
+ # noop, no echoing what is typed
1441
+ else
1442
+ print_str(@question.echo * str.length, :y => @prompt_length+0)
1443
+ end
1444
+ win.move r, c+len # more for arrow keys, curpos may not be end
1445
+ win.wrefresh # 2011-10-10
1446
+ prevchar = ch
1447
+ end
1448
+ $log.debug "XXXW bottomline: after while loop"
1449
+
1450
+ str = default if str == ""
1451
+ ensure
1452
+ Ncurses.noecho();
1453
+ end
1454
+ return 0, str
1455
+ end
1456
+
1457
+ # compares entries in array and returns longest common starting string
1458
+ # as happens in bash when pressing tab
1459
+ # abc abd abe will return ab
1460
+ def shortest_match a
1461
+ #return "" if a.nil? || a.empty? # should not be called in such situations
1462
+ raise "shortest_match should not be called with nil or empty array" if a.nil? || a.empty? # should not be called in such situations as caller program will err.
1463
+
1464
+ l = a.inject do |memo,word|
1465
+ str = ""
1466
+ 0.upto(memo.size) do |i|
1467
+ if memo[0..i] == word[0..i]
1468
+ str = memo[0..i]
1469
+ else
1470
+ break
1471
+ end
1472
+ end
1473
+ str
1474
+ end
1475
+ end
1476
+ # clears line from 0, not okay in some cases
1477
+ def clear_line len=100, from=0
1478
+ print_str("%-*s" % [len," "], :y => from)
1479
+ end
1480
+
1481
+ def print_help(help_text)
1482
+ # best to popup a window and hsow that with ENTER to dispell
1483
+ print_str("%-*s" % [help_text.length+2," "])
1484
+ print_str("%s" % help_text)
1485
+ sleep(5)
1486
+ end
1487
+ def get_response
1488
+ return @question.first_answer if @question.first_answer?
1489
+ # we always use character reader, so user's value does not matter
1490
+
1491
+ #if @question.character.nil?
1492
+ # if @question.echo == true #and @question.limit.nil?
1493
+ $log.debug "XXX: before RBGETS got default: #{@default} "
1494
+ ret, str = rb_getstr
1495
+ if ret == 0
1496
+ return @question.change_case(@question.remove_whitespace(str))
1497
+ end
1498
+ if ret == -1
1499
+ raise Interrupt
1500
+ end
1501
+ return ""
1502
+ end
1503
+ def agree( yes_or_no_question, character = nil )
1504
+ ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == ?y}) do |q|
1505
+ q.validate = /\Ay(?:es)?|no?\Z/i
1506
+ q.responses[:not_valid] = 'Please enter "yes" or "no".'
1507
+ q.responses[:ask_on_error] = :question
1508
+ q.character = character
1509
+ q.limit = 1 if character
1510
+
1511
+ yield q if block_given?
1512
+ end
1513
+ end
1514
+
1515
+ # Allows a selection in which options are shown over prompt. As user types
1516
+ # options are narrowed down.
1517
+ # NOTE: For a directory we are not showing a slash, so currently you
1518
+ # have to enter the slash manually when searching.
1519
+ # FIXME we can put remarks in fron as in memacs such as [No matches] or [single completion]
1520
+ # @param [Array] a list of items to select from
1521
+ # NOTE: if you use this please copy it to your app. This does not conform to highline's
1522
+ # choose, and I'd like to somehow get it to be identical.
1523
+ #
1524
+ def choose list1, config={}
1525
+ dirlist = true
1526
+ start = 0
1527
+ case list1
1528
+ when NilClass
1529
+ #list1 = Dir.glob("*")
1530
+ list1 = Dir.glob("*").collect { |f| File.directory?(f) ? f+"/" : f }
1531
+ when String
1532
+ list1 = Dir.glob(list1).collect { |f| File.directory?(f) ? f+"/" : f }
1533
+ when Array
1534
+ dirlist = false
1535
+ # let it be, that's how it should come
1536
+ else
1537
+ # Dir listing as default
1538
+ #list1 = Dir.glob("*")
1539
+ list1 = Dir.glob("*").collect { |f| File.directory?(f) ? f+"/" : f }
1540
+ end
1541
+ require 'canis/core/util/rcommandwindow'
1542
+ prompt = config[:prompt] || "Choose: "
1543
+ layout = { :height => 5, :width => Ncurses.COLS-1, :top => Ncurses.LINES-6, :left => 0 }
1544
+ rc = CommandWindow.new nil, :layout => layout, :box => true, :title => config[:title]
1545
+ begin
1546
+ w = rc.window
1547
+ rc.display_menu list1
1548
+ # earlier wmove bombed, now move is (window.rb 121)
1549
+ str = ask(prompt) { |q| q.help_text = config[:help_text] ; q.change_proc = Proc.new { |str| w.wmove(1,1) ; w.wclrtobot;
1550
+
1551
+ l = list1.select{|e| e.index(str)==0} ; # select those starting with str
1552
+
1553
+ if (l.size == 0 || str[-1]=='/') && dirlist
1554
+ # used to help complete directories so we can drill up and down
1555
+ #l = Dir.glob(str+"*")
1556
+ l = Dir.glob(str +"*").collect { |f| File.directory?(f) ? f+"/" : f }
1557
+ end
1558
+ rc.display_menu l;
1559
+ l
1560
+ }
1561
+ q.key_handler_proc = Proc.new { |ch|
1562
+ # this is not very good since it does not respect above list which is filtered
1563
+ # # need to clear the screen before printing - FIXME
1564
+ case ch
1565
+ when ?\C-n.getbyte(0)
1566
+ start += 2 if start < list1.length - 2
1567
+
1568
+ w.wmove(1,1) ; w.wclrtobot;
1569
+ rc.display_menu list1, :startindex => start
1570
+ when ?\C-p.getbyte(0)
1571
+ start -= 2 if start > 2
1572
+ w.wmove(1,1) ; w.wclrtobot;
1573
+ rc.display_menu list1, :startindex => start
1574
+ else
1575
+ alert "unhalderlind by jey "
1576
+ end
1577
+
1578
+ }
1579
+ }
1580
+ # need some validation here that its in the list TODO
1581
+ ensure
1582
+ rc.destroy
1583
+ rc = nil
1584
+ $log.debug "XXX: HIDE B IN ENSURE"
1585
+ hide_bottomline # since we called ask() we need to close bottomline
1586
+ end
1587
+ $log.debug "XXX: HIDE B AT END OF ASK"
1588
+ #hide_bottomline # since we called ask() we need to close bottomline
1589
+ return str
1590
+ end
1591
+ # XXX FIXME this uses only rcommand so what is it doing here.
1592
+ def display_text_interactive text, config={}
1593
+ require 'canis/core/util/rcommandwindow'
1594
+ ht = config[:height] || 15
1595
+ layout = { :height => ht, :width => Ncurses.COLS-1, :top => Ncurses.LINES-ht+1, :left => 0 }
1596
+ rc = CommandWindow.new nil, :layout => layout, :box => true, :title => config[:title]
1597
+ w = rc.window
1598
+ #rc.text "There was a quick brown fox who ran over the lazy dog and then went over the moon over and over again and again"
1599
+ rc.display_interactive(text) { |l|
1600
+ l.focussed_attrib = 'bold' # Ncurses::A_UNDERLINE
1601
+ l.focussed_symbol = '>'
1602
+ }
1603
+ rc = nil
1604
+ end
1605
+
1606
+ # XXX FIXME this uses only rcommand so what is it doing here.
1607
+ #def display_list_interactive text, config={}
1608
+ # returns a ListObject since you may not know what the list itself contained
1609
+ # You can do ret.list[ret.current_index] to get value
1610
+ def display_list text, config={}
1611
+ require 'canis/core/util/rcommandwindow'
1612
+ ht = config[:height] || 15
1613
+ layout = { :height => ht, :width => Ncurses.COLS-1, :top => Ncurses.LINES-ht+1, :left => 0 }
1614
+ rc = CommandWindow.new nil, :layout => layout, :box => true, :title => config[:title]
1615
+ w = rc.window
1616
+ ret = rc.display_interactive text
1617
+ rc = nil
1618
+ ret
1619
+ end
1620
+ #
1621
+ # This method is HighLine's menu handler. For simple usage, you can just
1622
+ # pass all the menu items you wish to display. At that point, choose() will
1623
+ # build and display a menu, walk the user through selection, and return
1624
+ # their choice amoung the provided items. You might use this in a case
1625
+ # statement for quick and dirty menus.
1626
+ #
1627
+ # However, choose() is capable of much more. If provided, a block will be
1628
+ # passed a HighLine::Menu object to configure. Using this method, you can
1629
+ # customize all the details of menu handling from index display, to building
1630
+ # a complete shell-like menuing system. See HighLine::Menu for all the
1631
+ # methods it responds to.
1632
+ #
1633
+ # Raises EOFError if input is exhausted.
1634
+ #
1635
+ def XXXchoose( *items, &details )
1636
+ @menu = @question = Menu.new(&details)
1637
+ @menu.choices(*items) unless items.empty?
1638
+
1639
+ # Set _answer_type_ so we can double as the Question for ask().
1640
+ @menu.answer_type = if @menu.shell
1641
+ lambda do |command| # shell-style selection
1642
+ first_word = command.to_s.split.first || ""
1643
+
1644
+ options = @menu.options
1645
+ options.extend(OptionParser::Completion)
1646
+ answer = options.complete(first_word)
1647
+
1648
+ if answer.nil?
1649
+ raise Question::NoAutoCompleteMatch
1650
+ end
1651
+
1652
+ [answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
1653
+ end
1654
+ else
1655
+ @menu.options # normal menu selection, by index or name
1656
+ end
1657
+
1658
+ # Provide hooks for ERb layouts.
1659
+ @header = @menu.header
1660
+ @prompt = @menu.prompt
1661
+
1662
+ if @menu.shell
1663
+ selected = ask("Ignored", @menu.answer_type)
1664
+ @menu.select(self, *selected)
1665
+ else
1666
+ selected = ask("Ignored", @menu.answer_type)
1667
+ @menu.select(self, selected)
1668
+ end
1669
+ end
1670
+
1671
+ # Each member of the _items_ Array is passed through ERb and thus can contain
1672
+ # their own expansions. Color escape expansions do not contribute to the
1673
+ # final field width.
1674
+ #
1675
+ def list( items, mode = :rows, option = nil )
1676
+ items = items.to_ary.map do |item|
1677
+ ERB.new(item, nil, "%").result(binding)
1678
+ end
1679
+
1680
+ case mode
1681
+ when :inline
1682
+ option = " or " if option.nil?
1683
+
1684
+ case items.size
1685
+ when 0
1686
+ ""
1687
+ when 1
1688
+ items.first
1689
+ when 2
1690
+ "#{items.first}#{option}#{items.last}"
1691
+ else
1692
+ items[0..-2].join(", ") + "#{option}#{items.last}"
1693
+ end
1694
+ when :columns_across, :columns_down
1695
+ max_length = actual_length(
1696
+ items.max { |a, b| actual_length(a) <=> actual_length(b) }
1697
+ )
1698
+
1699
+ if option.nil?
1700
+ limit = @wrap_at || 80
1701
+ option = (limit + 2) / (max_length + 2)
1702
+ end
1703
+
1704
+ items = items.map do |item|
1705
+ pad = max_length + (item.length - actual_length(item))
1706
+ "%-#{pad}s" % item
1707
+ end
1708
+ row_count = (items.size / option.to_f).ceil
1709
+
1710
+ if mode == :columns_across
1711
+ rows = Array.new(row_count) { Array.new }
1712
+ items.each_with_index do |item, index|
1713
+ rows[index / option] << item
1714
+ end
1715
+
1716
+ rows.map { |row| row.join(" ") + "\n" }.join
1717
+ else
1718
+ columns = Array.new(option) { Array.new }
1719
+ items.each_with_index do |item, index|
1720
+ columns[index / row_count] << item
1721
+ end
1722
+
1723
+ list = ""
1724
+ columns.first.size.times do |index|
1725
+ list << columns.map { |column| column[index] }.
1726
+ compact.join(" ") + "\n"
1727
+ end
1728
+ list
1729
+ end
1730
+ else
1731
+ items.map { |i| "#{i}\n" }.join
1732
+ end
1733
+ end
1734
+ end # module
1735
+ end # module
1736
+ #require 'canis/core/util/bottomline'
1737
+ $tt ||= Canis::Bottomline.new
1738
+ $tt.name = "$tt"
1739
+ require 'forwardable'
1740
+ module Kernel
1741
+ extend Forwardable
1742
+ def_delegators :$tt, :ask, :say, :agree, :choose, :numbered_menu, :display_text, :display_text_interactive, :display_list, :say_with_pause, :hide_bottomline, :say_with_wait
1743
+ end
1744
+ if __FILE__ == $PROGRAM_NAME
1745
+
1746
+ #tabc = Proc.new {|str| Dir.glob(str +"*") }
1747
+ require 'canis/core/util/app'
1748
+ require 'forwardable'
1749
+ #include Bottomline
1750
+
1751
+ #$tt = Bottomline.new
1752
+ #module Kernel
1753
+ #extend Forwardable
1754
+ #def_delegators :$tt, :ask, :say, :agree, :choose, :numbered_menu
1755
+ #end
1756
+ App.new do
1757
+ header = app_header "canis 1.2.0", :text_center => "**** Demo", :text_right =>"New Improved!", :color => :black, :bgcolor => :white, :attr => :bold
1758
+ message "Press F1 to exit from here"
1759
+
1760
+ #stack :margin_top => 2, :margin => 5, :width => 30 do
1761
+ #end # stack
1762
+ #-----------------#------------------
1763
+
1764
+ #choose do |menu|
1765
+ #menu.prompt = "Please choose your favorite programming language? "
1766
+ ##menu.layout = :one_line
1767
+ #
1768
+ #menu.choice :ruby do say("Good choice!") end
1769
+ #menu.choice(:python) do say("python Not from around here, are you?") end
1770
+ #menu.choice(:perl) do say("perl Not from around here, are you?") end
1771
+ #menu.choice(:rake) do say("rake Not from around here, are you?") end
1772
+ #end
1773
+ entry = {}
1774
+ entry[:file] = ask("File? ", Pathname) do |q|
1775
+ q.completion_proc = Proc.new {|str| Dir.glob(str +"*") }
1776
+ q.help_text = "Enter start of filename and tab to get completion"
1777
+ end
1778
+ alert "file: #{entry[:file]} "
1779
+ $log.debug "FILE: #{entry[:file]} "
1780
+ entry[:command] = ask("Command? ", %w{archive delete read refresh delete!})
1781
+ exit unless agree("Wish to continue? ", false)
1782
+ entry[:address] = ask("Address? ") { |q| q.color_pair = $promptcolor }
1783
+ entry[:company] = ask("Company? ") { |q| q.default = "none" }
1784
+ entry[:password] = ask("password? ") { |q|
1785
+ q.echo = '*'
1786
+ q.limit = 4
1787
+ }
1788
+ =begin
1789
+ entry[:state] = ask("State? ") do |q|
1790
+ q._case = :up
1791
+ q.validate = /\A[A-Z]{2}\Z/
1792
+ q.help_text = "Enter 2 characters for your state"
1793
+ end
1794
+ entry[:zip] = ask("Zip? ") do |q|
1795
+ q.validate = /\A\d{5}(?:-?\d{4})?\Z/
1796
+ end
1797
+ entry[:phone] = ask( "Phone? ",
1798
+ lambda { |p| p.delete("^0-9").
1799
+ sub(/\A(\d{3})/, '(\1) ').
1800
+ sub(/(\d{4})\Z/, '-\1') } ) do |q|
1801
+ q.validate = lambda { |p| p.delete("^0-9").length == 10 }
1802
+ q.responses[:not_valid] = "Enter a phone numer with area code."
1803
+ end
1804
+ entry[:age] = ask("Age? ", Integer) { |q| q.in = 0..105 }
1805
+ entry[:birthday] = ask("Birthday? ", Date)
1806
+ entry[:interests] = ask( "Interests? (comma separated list) ",
1807
+ lambda { |str| str.split(/,\s*/) } )
1808
+ entry[:description] = ask("Enter a description for this contact.") do |q|
1809
+ q.whitespace = :strip_and_collapse
1810
+ end
1811
+ =end
1812
+ $log.debug "ENTRY: #{entry} " if $log.debug?
1813
+ #puts entry
1814
+ end # app
1815
+ end # FILE