canis 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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