truex-skylight 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +277 -0
  3. data/CLA.md +9 -0
  4. data/CONTRIBUTING.md +1 -0
  5. data/LICENSE.md +79 -0
  6. data/README.md +4 -0
  7. data/bin/skylight +3 -0
  8. data/ext/extconf.rb +186 -0
  9. data/ext/libskylight.yml +6 -0
  10. data/ext/skylight_memprof.c +115 -0
  11. data/ext/skylight_native.c +416 -0
  12. data/ext/skylight_native.h +20 -0
  13. data/lib/skylight.rb +2 -0
  14. data/lib/skylight/api.rb +79 -0
  15. data/lib/skylight/cli.rb +146 -0
  16. data/lib/skylight/compat.rb +47 -0
  17. data/lib/skylight/config.rb +498 -0
  18. data/lib/skylight/core.rb +122 -0
  19. data/lib/skylight/data/cacert.pem +3894 -0
  20. data/lib/skylight/formatters/http.rb +17 -0
  21. data/lib/skylight/gc.rb +107 -0
  22. data/lib/skylight/helpers.rb +137 -0
  23. data/lib/skylight/instrumenter.rb +290 -0
  24. data/lib/skylight/middleware.rb +75 -0
  25. data/lib/skylight/native.rb +69 -0
  26. data/lib/skylight/normalizers.rb +133 -0
  27. data/lib/skylight/normalizers/action_controller/process_action.rb +35 -0
  28. data/lib/skylight/normalizers/action_controller/send_file.rb +76 -0
  29. data/lib/skylight/normalizers/action_view/render_collection.rb +18 -0
  30. data/lib/skylight/normalizers/action_view/render_partial.rb +18 -0
  31. data/lib/skylight/normalizers/action_view/render_template.rb +18 -0
  32. data/lib/skylight/normalizers/active_record/sql.rb +79 -0
  33. data/lib/skylight/normalizers/active_support/cache.rb +50 -0
  34. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  35. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  36. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  37. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  38. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  39. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  40. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  41. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  42. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  43. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  44. data/lib/skylight/normalizers/default.rb +21 -0
  45. data/lib/skylight/normalizers/moped/query.rb +141 -0
  46. data/lib/skylight/probes.rb +91 -0
  47. data/lib/skylight/probes/excon.rb +25 -0
  48. data/lib/skylight/probes/excon/middleware.rb +65 -0
  49. data/lib/skylight/probes/net_http.rb +44 -0
  50. data/lib/skylight/probes/redis.rb +30 -0
  51. data/lib/skylight/probes/sequel.rb +30 -0
  52. data/lib/skylight/probes/sinatra.rb +74 -0
  53. data/lib/skylight/probes/tilt.rb +27 -0
  54. data/lib/skylight/railtie.rb +122 -0
  55. data/lib/skylight/sinatra.rb +4 -0
  56. data/lib/skylight/subscriber.rb +92 -0
  57. data/lib/skylight/trace.rb +191 -0
  58. data/lib/skylight/util.rb +16 -0
  59. data/lib/skylight/util/allocation_free.rb +17 -0
  60. data/lib/skylight/util/clock.rb +53 -0
  61. data/lib/skylight/util/gzip.rb +15 -0
  62. data/lib/skylight/util/hostname.rb +17 -0
  63. data/lib/skylight/util/http.rb +218 -0
  64. data/lib/skylight/util/inflector.rb +110 -0
  65. data/lib/skylight/util/logging.rb +87 -0
  66. data/lib/skylight/util/multi_io.rb +21 -0
  67. data/lib/skylight/util/native_ext_fetcher.rb +205 -0
  68. data/lib/skylight/util/platform.rb +67 -0
  69. data/lib/skylight/util/ssl.rb +50 -0
  70. data/lib/skylight/vendor/active_support/notifications.rb +207 -0
  71. data/lib/skylight/vendor/active_support/notifications/fanout.rb +159 -0
  72. data/lib/skylight/vendor/active_support/notifications/instrumenter.rb +72 -0
  73. data/lib/skylight/vendor/active_support/per_thread_registry.rb +52 -0
  74. data/lib/skylight/vendor/cli/highline.rb +1034 -0
  75. data/lib/skylight/vendor/cli/highline/color_scheme.rb +134 -0
  76. data/lib/skylight/vendor/cli/highline/compatibility.rb +16 -0
  77. data/lib/skylight/vendor/cli/highline/import.rb +41 -0
  78. data/lib/skylight/vendor/cli/highline/menu.rb +381 -0
  79. data/lib/skylight/vendor/cli/highline/question.rb +481 -0
  80. data/lib/skylight/vendor/cli/highline/simulate.rb +48 -0
  81. data/lib/skylight/vendor/cli/highline/string_extensions.rb +111 -0
  82. data/lib/skylight/vendor/cli/highline/style.rb +181 -0
  83. data/lib/skylight/vendor/cli/highline/system_extensions.rb +242 -0
  84. data/lib/skylight/vendor/cli/thor.rb +473 -0
  85. data/lib/skylight/vendor/cli/thor/actions.rb +318 -0
  86. data/lib/skylight/vendor/cli/thor/actions/create_file.rb +105 -0
  87. data/lib/skylight/vendor/cli/thor/actions/create_link.rb +60 -0
  88. data/lib/skylight/vendor/cli/thor/actions/directory.rb +119 -0
  89. data/lib/skylight/vendor/cli/thor/actions/empty_directory.rb +137 -0
  90. data/lib/skylight/vendor/cli/thor/actions/file_manipulation.rb +314 -0
  91. data/lib/skylight/vendor/cli/thor/actions/inject_into_file.rb +109 -0
  92. data/lib/skylight/vendor/cli/thor/base.rb +652 -0
  93. data/lib/skylight/vendor/cli/thor/command.rb +136 -0
  94. data/lib/skylight/vendor/cli/thor/core_ext/hash_with_indifferent_access.rb +80 -0
  95. data/lib/skylight/vendor/cli/thor/core_ext/io_binary_read.rb +12 -0
  96. data/lib/skylight/vendor/cli/thor/core_ext/ordered_hash.rb +100 -0
  97. data/lib/skylight/vendor/cli/thor/error.rb +28 -0
  98. data/lib/skylight/vendor/cli/thor/group.rb +282 -0
  99. data/lib/skylight/vendor/cli/thor/invocation.rb +172 -0
  100. data/lib/skylight/vendor/cli/thor/parser.rb +4 -0
  101. data/lib/skylight/vendor/cli/thor/parser/argument.rb +74 -0
  102. data/lib/skylight/vendor/cli/thor/parser/arguments.rb +171 -0
  103. data/lib/skylight/vendor/cli/thor/parser/option.rb +121 -0
  104. data/lib/skylight/vendor/cli/thor/parser/options.rb +218 -0
  105. data/lib/skylight/vendor/cli/thor/rake_compat.rb +72 -0
  106. data/lib/skylight/vendor/cli/thor/runner.rb +322 -0
  107. data/lib/skylight/vendor/cli/thor/shell.rb +88 -0
  108. data/lib/skylight/vendor/cli/thor/shell/basic.rb +393 -0
  109. data/lib/skylight/vendor/cli/thor/shell/color.rb +148 -0
  110. data/lib/skylight/vendor/cli/thor/shell/html.rb +127 -0
  111. data/lib/skylight/vendor/cli/thor/util.rb +270 -0
  112. data/lib/skylight/vendor/cli/thor/version.rb +3 -0
  113. data/lib/skylight/vendor/thread_safe.rb +126 -0
  114. data/lib/skylight/vendor/thread_safe/non_concurrent_cache_backend.rb +133 -0
  115. data/lib/skylight/vendor/thread_safe/synchronized_cache_backend.rb +76 -0
  116. data/lib/skylight/version.rb +4 -0
  117. data/lib/skylight/vm/gc.rb +70 -0
  118. data/lib/sql_lexer.rb +6 -0
  119. data/lib/sql_lexer/lexer.rb +579 -0
  120. data/lib/sql_lexer/string_scanner.rb +11 -0
  121. data/lib/sql_lexer/version.rb +3 -0
  122. metadata +179 -0
@@ -0,0 +1,52 @@
1
+ module ActiveSupport
2
+ # This module is used to encapsulate access to thread local variables.
3
+ #
4
+ # Instead of polluting the thread locals namespace:
5
+ #
6
+ # Thread.current[:connection_handler]
7
+ #
8
+ # you define a class that extends this module:
9
+ #
10
+ # module ActiveRecord
11
+ # class RuntimeRegistry
12
+ # extend ActiveSupport::PerThreadRegistry
13
+ #
14
+ # attr_accessor :connection_handler
15
+ # end
16
+ # end
17
+ #
18
+ # and invoke the declared instance accessors as class methods. So
19
+ #
20
+ # ActiveRecord::RuntimeRegistry.connection_handler = connection_handler
21
+ #
22
+ # sets a connection handler local to the current thread, and
23
+ #
24
+ # ActiveRecord::RuntimeRegistry.connection_handler
25
+ #
26
+ # returns a connection handler local to the current thread.
27
+ #
28
+ # This feature is accomplished by instantiating the class and storing the
29
+ # instance as a thread local keyed by the class name. In the example above
30
+ # a key "ActiveRecord::RuntimeRegistry" is stored in <tt>Thread.current</tt>.
31
+ # The class methods proxy to said thread local instance.
32
+ #
33
+ # If the class has an initializer, it must accept no arguments.
34
+ module PerThreadRegistry
35
+ protected
36
+
37
+ def method_missing(name, *args, &block) # :nodoc:
38
+ # Caches the method definition as a singleton method of the receiver.
39
+ define_singleton_method(name) do |*a, &b|
40
+ per_thread_registry_instance.public_send(name, *a, &b)
41
+ end
42
+
43
+ send(name, *args, &block)
44
+ end
45
+
46
+ private
47
+
48
+ def per_thread_registry_instance
49
+ Thread.current[name] ||= new
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,1034 @@
1
+ # highline.rb
2
+ #
3
+ # Created by James Edward Gray II on 2005-04-26.
4
+ # Copyright 2005 Gray Productions. All rights reserved.
5
+ #
6
+ # See HighLine for documentation.
7
+ #
8
+ # This is Free Software. See LICENSE and COPYING for details.
9
+
10
+ require "erb"
11
+ require "optparse"
12
+ require "stringio"
13
+ require "abbrev"
14
+ require "highline/system_extensions"
15
+ require "highline/question"
16
+ require "highline/menu"
17
+ require "highline/color_scheme"
18
+ require "highline/style"
19
+
20
+ #
21
+ # A HighLine object is a "high-level line oriented" shell over an input and an
22
+ # output stream. HighLine simplifies common console interaction, effectively
23
+ # replacing puts() and gets(). User code can simply specify the question to ask
24
+ # and any details about user interaction, then leave the rest of the work to
25
+ # HighLine. When HighLine.ask() returns, you'll have the answer you requested,
26
+ # even if HighLine had to ask many times, validate results, perform range
27
+ # checking, convert types, etc.
28
+ #
29
+ class HighLine
30
+ # The version of the installed library.
31
+ VERSION = "1.6.21".freeze
32
+
33
+ # An internal HighLine error. User code does not need to trap this.
34
+ class QuestionError < StandardError
35
+ # do nothing, just creating a unique error type
36
+ end
37
+
38
+ # The setting used to disable color output.
39
+ @@use_color = true
40
+
41
+ # Pass +false+ to _setting_ to turn off HighLine's color escapes.
42
+ def self.use_color=( setting )
43
+ @@use_color = setting
44
+ end
45
+
46
+ # Returns true if HighLine is currently using color escapes.
47
+ def self.use_color?
48
+ @@use_color
49
+ end
50
+
51
+ # For checking if the current version of HighLine supports RGB colors
52
+ # Usage: HighLine.supports_rgb_color? rescue false # rescue for compatibility with older versions
53
+ # Note: color usage also depends on HighLine.use_color being set
54
+ def self.supports_rgb_color?
55
+ true
56
+ end
57
+
58
+ # The setting used to disable EOF tracking.
59
+ @@track_eof = true
60
+
61
+ # Pass +false+ to _setting_ to turn off HighLine's EOF tracking.
62
+ def self.track_eof=( setting )
63
+ @@track_eof = setting
64
+ end
65
+
66
+ # Returns true if HighLine is currently tracking EOF for input.
67
+ def self.track_eof?
68
+ @@track_eof
69
+ end
70
+
71
+ # The setting used to control color schemes.
72
+ @@color_scheme = nil
73
+
74
+ # Pass ColorScheme to _setting_ to set a HighLine color scheme.
75
+ def self.color_scheme=( setting )
76
+ @@color_scheme = setting
77
+ end
78
+
79
+ # Returns the current color scheme.
80
+ def self.color_scheme
81
+ @@color_scheme
82
+ end
83
+
84
+ # Returns +true+ if HighLine is currently using a color scheme.
85
+ def self.using_color_scheme?
86
+ not @@color_scheme.nil?
87
+ end
88
+
89
+ #
90
+ # Embed in a String to clear all previous ANSI sequences. This *MUST* be
91
+ # done before the program exits!
92
+ #
93
+
94
+ ERASE_LINE_STYLE = Style.new(:name=>:erase_line, :builtin=>true, :code=>"\e[K") # Erase the current line of terminal output
95
+ ERASE_CHAR_STYLE = Style.new(:name=>:erase_char, :builtin=>true, :code=>"\e[P") # Erase the character under the cursor.
96
+ CLEAR_STYLE = Style.new(:name=>:clear, :builtin=>true, :code=>"\e[0m") # Clear color settings
97
+ RESET_STYLE = Style.new(:name=>:reset, :builtin=>true, :code=>"\e[0m") # Alias for CLEAR.
98
+ BOLD_STYLE = Style.new(:name=>:bold, :builtin=>true, :code=>"\e[1m") # Bold; Note: bold + a color works as you'd expect,
99
+ # for example bold black. Bold without a color displays
100
+ # the system-defined bold color (e.g. red on Mac iTerm)
101
+ DARK_STYLE = Style.new(:name=>:dark, :builtin=>true, :code=>"\e[2m") # Dark; support uncommon
102
+ UNDERLINE_STYLE = Style.new(:name=>:underline, :builtin=>true, :code=>"\e[4m") # Underline
103
+ UNDERSCORE_STYLE = Style.new(:name=>:underscore, :builtin=>true, :code=>"\e[4m") # Alias for UNDERLINE
104
+ BLINK_STYLE = Style.new(:name=>:blink, :builtin=>true, :code=>"\e[5m") # Blink; support uncommon
105
+ REVERSE_STYLE = Style.new(:name=>:reverse, :builtin=>true, :code=>"\e[7m") # Reverse foreground and background
106
+ CONCEALED_STYLE = Style.new(:name=>:concealed, :builtin=>true, :code=>"\e[8m") # Concealed; support uncommon
107
+
108
+ STYLES = %w{CLEAR RESET BOLD DARK UNDERLINE UNDERSCORE BLINK REVERSE CONCEALED}
109
+
110
+ # These RGB colors are approximate; see http://en.wikipedia.org/wiki/ANSI_escape_code
111
+ BLACK_STYLE = Style.new(:name=>:black, :builtin=>true, :code=>"\e[30m", :rgb=>[ 0, 0, 0])
112
+ RED_STYLE = Style.new(:name=>:red, :builtin=>true, :code=>"\e[31m", :rgb=>[128, 0, 0])
113
+ GREEN_STYLE = Style.new(:name=>:green, :builtin=>true, :code=>"\e[32m", :rgb=>[ 0,128, 0])
114
+ BLUE_STYLE = Style.new(:name=>:blue, :builtin=>true, :code=>"\e[34m", :rgb=>[ 0, 0,128])
115
+ YELLOW_STYLE = Style.new(:name=>:yellow, :builtin=>true, :code=>"\e[33m", :rgb=>[128,128, 0])
116
+ MAGENTA_STYLE = Style.new(:name=>:magenta, :builtin=>true, :code=>"\e[35m", :rgb=>[128, 0,128])
117
+ CYAN_STYLE = Style.new(:name=>:cyan, :builtin=>true, :code=>"\e[36m", :rgb=>[ 0,128,128])
118
+ # On Mac OSX Terminal, white is actually gray
119
+ WHITE_STYLE = Style.new(:name=>:white, :builtin=>true, :code=>"\e[37m", :rgb=>[192,192,192])
120
+ # Alias for WHITE, since WHITE is actually a light gray on Macs
121
+ GRAY_STYLE = Style.new(:name=>:gray, :builtin=>true, :code=>"\e[37m", :rgb=>[192,192,192])
122
+ # On Mac OSX Terminal, this is black foreground, or bright white background.
123
+ # Also used as base for RGB colors, if available
124
+ NONE_STYLE = Style.new(:name=>:none, :builtin=>true, :code=>"\e[38m", :rgb=>[ 0, 0, 0])
125
+
126
+ BASIC_COLORS = %w{BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE GRAY NONE}
127
+
128
+ colors = BASIC_COLORS.dup
129
+ BASIC_COLORS.each do |color|
130
+ bright_color = "BRIGHT_#{color}"
131
+ colors << bright_color
132
+ const_set bright_color+'_STYLE', const_get(color + '_STYLE').bright
133
+ end
134
+ COLORS = colors
135
+
136
+ colors.each do |color|
137
+ const_set color, const_get("#{color}_STYLE").code
138
+ const_set "ON_#{color}_STYLE", const_get("#{color}_STYLE").on
139
+ const_set "ON_#{color}", const_get("ON_#{color}_STYLE").code
140
+ end
141
+ ON_NONE_STYLE.rgb = [255,255,255] # Override; white background
142
+
143
+ STYLES.each do |style|
144
+ const_set style, const_get("#{style}_STYLE").code
145
+ end
146
+
147
+ # For RGB colors:
148
+ def self.const_missing(name)
149
+ if name.to_s =~ /^(ON_)?(RGB_)([A-F0-9]{6})(_STYLE)?$/ # RGB color
150
+ on = $1
151
+ suffix = $4
152
+ if suffix
153
+ code_name = $1.to_s + $2 + $3
154
+ else
155
+ code_name = name.to_s
156
+ end
157
+ style_name = code_name + '_STYLE'
158
+ style = Style.rgb($3)
159
+ style = style.on if on
160
+ const_set(style_name, style)
161
+ const_set(code_name, style.code)
162
+ if suffix
163
+ style
164
+ else
165
+ style.code
166
+ end
167
+ else
168
+ raise NameError, "Bad color or uninitialized constant #{name}"
169
+ end
170
+ end
171
+
172
+ #
173
+ # Create an instance of HighLine, connected to the streams _input_
174
+ # and _output_.
175
+ #
176
+ def initialize( input = $stdin, output = $stdout,
177
+ wrap_at = nil, page_at = nil, indent_size=3, indent_level=0 )
178
+ @input = input
179
+ @output = output
180
+
181
+ @multi_indent = true
182
+ @indent_size = indent_size
183
+ @indent_level = indent_level
184
+
185
+ self.wrap_at = wrap_at
186
+ self.page_at = page_at
187
+
188
+ @question = nil
189
+ @answer = nil
190
+ @menu = nil
191
+ @header = nil
192
+ @prompt = nil
193
+ @gather = nil
194
+ @answers = nil
195
+ @key = nil
196
+
197
+ initialize_system_extensions if respond_to?(:initialize_system_extensions)
198
+ end
199
+
200
+ include HighLine::SystemExtensions
201
+
202
+ # The current column setting for wrapping output.
203
+ attr_reader :wrap_at
204
+ # The current row setting for paging output.
205
+ attr_reader :page_at
206
+ # Indentation over multiple lines
207
+ attr_accessor :multi_indent
208
+ # The indentation size
209
+ attr_accessor :indent_size
210
+ # The indentation level
211
+ attr_accessor :indent_level
212
+
213
+ #
214
+ # A shortcut to HighLine.ask() a question that only accepts "yes" or "no"
215
+ # answers ("y" and "n" are allowed) and returns +true+ or +false+
216
+ # (+true+ for "yes"). If provided a +true+ value, _character_ will cause
217
+ # HighLine to fetch a single character response. A block can be provided
218
+ # to further configure the question as in HighLine.ask()
219
+ #
220
+ # Raises EOFError if input is exhausted.
221
+ #
222
+ def agree( yes_or_no_question, character = nil )
223
+ ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == ?y}) do |q|
224
+ q.validate = /\Ay(?:es)?|no?\Z/i
225
+ q.responses[:not_valid] = 'Please enter "yes" or "no".'
226
+ q.responses[:ask_on_error] = :question
227
+ q.character = character
228
+
229
+ yield q if block_given?
230
+ end
231
+ end
232
+
233
+ #
234
+ # This method is the primary interface for user input. Just provide a
235
+ # _question_ to ask the user, the _answer_type_ you want returned, and
236
+ # optionally a code block setting up details of how you want the question
237
+ # handled. See HighLine.say() for details on the format of _question_, and
238
+ # HighLine::Question for more information about _answer_type_ and what's
239
+ # valid in the code block.
240
+ #
241
+ # If <tt>@question</tt> is set before ask() is called, parameters are
242
+ # ignored and that object (must be a HighLine::Question) is used to drive
243
+ # the process instead.
244
+ #
245
+ # Raises EOFError if input is exhausted.
246
+ #
247
+ def ask( question, answer_type = String, &details ) # :yields: question
248
+ @question ||= Question.new(question, answer_type, &details)
249
+
250
+ return gather if @question.gather
251
+
252
+ # readline() needs to handle its own output, but readline only supports
253
+ # full line reading. Therefore if @question.echo is anything but true,
254
+ # the prompt will not be issued. And we have to account for that now.
255
+ # Also, JRuby-1.7's ConsoleReader.readLine() needs to be passed the prompt
256
+ # to handle line editing properly.
257
+ say(@question) unless ((JRUBY or @question.readline) and @question.echo == true)
258
+
259
+ begin
260
+ @answer = @question.answer_or_default(get_response)
261
+ unless @question.valid_answer?(@answer)
262
+ explain_error(:not_valid)
263
+ raise QuestionError
264
+ end
265
+
266
+ @answer = @question.convert(@answer)
267
+
268
+ if @question.in_range?(@answer)
269
+ if @question.confirm
270
+ # need to add a layer of scope to ask a question inside a
271
+ # question, without destroying instance data
272
+ context_change = self.class.new(@input, @output, @wrap_at, @page_at, @indent_size, @indent_level)
273
+ if @question.confirm == true
274
+ confirm_question = "Are you sure? "
275
+ else
276
+ # evaluate ERb under initial scope, so it will have
277
+ # access to @question and @answer
278
+ template = ERB.new(@question.confirm, nil, "%")
279
+ confirm_question = template.result(binding)
280
+ end
281
+ unless context_change.agree(confirm_question)
282
+ explain_error(nil)
283
+ raise QuestionError
284
+ end
285
+ end
286
+
287
+ @answer
288
+ else
289
+ explain_error(:not_in_range)
290
+ raise QuestionError
291
+ end
292
+ rescue QuestionError
293
+ retry
294
+ rescue ArgumentError, NameError => error
295
+ raise if error.is_a?(NoMethodError)
296
+ if error.message =~ /ambiguous/
297
+ # the assumption here is that OptionParser::Completion#complete
298
+ # (used for ambiguity resolution) throws exceptions containing
299
+ # the word 'ambiguous' whenever resolution fails
300
+ explain_error(:ambiguous_completion)
301
+ else
302
+ explain_error(:invalid_type)
303
+ end
304
+ retry
305
+ rescue Question::NoAutoCompleteMatch
306
+ explain_error(:no_completion)
307
+ retry
308
+ ensure
309
+ @question = nil # Reset Question object.
310
+ end
311
+ end
312
+
313
+ #
314
+ # This method is HighLine's menu handler. For simple usage, you can just
315
+ # pass all the menu items you wish to display. At that point, choose() will
316
+ # build and display a menu, walk the user through selection, and return
317
+ # their choice among the provided items. You might use this in a case
318
+ # statement for quick and dirty menus.
319
+ #
320
+ # However, choose() is capable of much more. If provided, a block will be
321
+ # passed a HighLine::Menu object to configure. Using this method, you can
322
+ # customize all the details of menu handling from index display, to building
323
+ # a complete shell-like menuing system. See HighLine::Menu for all the
324
+ # methods it responds to.
325
+ #
326
+ # Raises EOFError if input is exhausted.
327
+ #
328
+ def choose( *items, &details )
329
+ @menu = @question = Menu.new(&details)
330
+ @menu.choices(*items) unless items.empty?
331
+
332
+ # Set auto-completion
333
+ @menu.completion = @menu.options
334
+ # Set _answer_type_ so we can double as the Question for ask().
335
+ @menu.answer_type = if @menu.shell
336
+ lambda do |command| # shell-style selection
337
+ first_word = command.to_s.split.first || ""
338
+
339
+ options = @menu.options
340
+ options.extend(OptionParser::Completion)
341
+ answer = options.complete(first_word)
342
+
343
+ if answer.nil?
344
+ raise Question::NoAutoCompleteMatch
345
+ end
346
+
347
+ [answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
348
+ end
349
+ else
350
+ @menu.options # normal menu selection, by index or name
351
+ end
352
+
353
+ # Provide hooks for ERb layouts.
354
+ @header = @menu.header
355
+ @prompt = @menu.prompt
356
+
357
+ if @menu.shell
358
+ selected = ask("Ignored", @menu.answer_type)
359
+ @menu.select(self, *selected)
360
+ else
361
+ selected = ask("Ignored", @menu.answer_type)
362
+ @menu.select(self, selected)
363
+ end
364
+ end
365
+
366
+ #
367
+ # This method provides easy access to ANSI color sequences, without the user
368
+ # needing to remember to CLEAR at the end of each sequence. Just pass the
369
+ # _string_ to color, followed by a list of _colors_ you would like it to be
370
+ # affected by. The _colors_ can be HighLine class constants, or symbols
371
+ # (:blue for BLUE, for example). A CLEAR will automatically be embedded to
372
+ # the end of the returned String.
373
+ #
374
+ # This method returns the original _string_ unchanged if HighLine::use_color?
375
+ # is +false+.
376
+ #
377
+ def self.color( string, *colors )
378
+ return string unless self.use_color?
379
+ Style(*colors).color(string)
380
+ end
381
+
382
+ # In case you just want the color code, without the embedding and the CLEAR
383
+ def self.color_code(*colors)
384
+ Style(*colors).code
385
+ end
386
+
387
+ # Works as an instance method, same as the class method
388
+ def color_code(*colors)
389
+ self.class.color_code(*colors)
390
+ end
391
+
392
+ # Works as an instance method, same as the class method
393
+ def color(*args)
394
+ self.class.color(*args)
395
+ end
396
+
397
+ # Remove color codes from a string
398
+ def self.uncolor(string)
399
+ Style.uncolor(string)
400
+ end
401
+
402
+ # Works as an instance method, same as the class method
403
+ def uncolor(string)
404
+ self.class.uncolor(string)
405
+ end
406
+
407
+ #
408
+ # This method is a utility for quickly and easily laying out lists. It can
409
+ # be accessed within ERb replacements of any text that will be sent to the
410
+ # user.
411
+ #
412
+ # The only required parameter is _items_, which should be the Array of items
413
+ # to list. A specified _mode_ controls how that list is formed and _option_
414
+ # has different effects, depending on the _mode_. Recognized modes are:
415
+ #
416
+ # <tt>:columns_across</tt>:: _items_ will be placed in columns,
417
+ # flowing from left to right. If given,
418
+ # _option_ is the number of columns to be
419
+ # used. When absent, columns will be
420
+ # determined based on _wrap_at_ or a
421
+ # default of 80 characters.
422
+ # <tt>:columns_down</tt>:: Identical to <tt>:columns_across</tt>,
423
+ # save flow goes down.
424
+ # <tt>:uneven_columns_across</tt>:: Like <tt>:columns_across</tt> but each
425
+ # column is sized independently.
426
+ # <tt>:uneven_columns_down</tt>:: Like <tt>:columns_down</tt> but each
427
+ # column is sized independently.
428
+ # <tt>:inline</tt>:: All _items_ are placed on a single line.
429
+ # The last two _items_ are separated by
430
+ # _option_ or a default of " or ". All
431
+ # other _items_ are separated by ", ".
432
+ # <tt>:rows</tt>:: The default mode. Each of the _items_ is
433
+ # placed on its own line. The _option_
434
+ # parameter is ignored in this mode.
435
+ #
436
+ # Each member of the _items_ Array is passed through ERb and thus can contain
437
+ # their own expansions. Color escape expansions do not contribute to the
438
+ # final field width.
439
+ #
440
+ def list( items, mode = :rows, option = nil )
441
+ items = items.to_ary.map do |item|
442
+ if item.nil?
443
+ ""
444
+ else
445
+ ERB.new(item, nil, "%").result(binding)
446
+ end
447
+ end
448
+
449
+ if items.empty?
450
+ ""
451
+ else
452
+ case mode
453
+ when :inline
454
+ option = " or " if option.nil?
455
+
456
+ if items.size == 1
457
+ items.first
458
+ else
459
+ items[0..-2].join(", ") + "#{option}#{items.last}"
460
+ end
461
+ when :columns_across, :columns_down
462
+ max_length = actual_length(
463
+ items.max { |a, b| actual_length(a) <=> actual_length(b) }
464
+ )
465
+
466
+ if option.nil?
467
+ limit = @wrap_at || 80
468
+ option = (limit + 2) / (max_length + 2)
469
+ end
470
+
471
+ items = items.map do |item|
472
+ pad = max_length + (item.to_s.length - actual_length(item))
473
+ "%-#{pad}s" % item
474
+ end
475
+ row_count = (items.size / option.to_f).ceil
476
+
477
+ if mode == :columns_across
478
+ rows = Array.new(row_count) { Array.new }
479
+ items.each_with_index do |item, index|
480
+ rows[index / option] << item
481
+ end
482
+
483
+ rows.map { |row| row.join(" ") + "\n" }.join
484
+ else
485
+ columns = Array.new(option) { Array.new }
486
+ items.each_with_index do |item, index|
487
+ columns[index / row_count] << item
488
+ end
489
+
490
+ list = ""
491
+ columns.first.size.times do |index|
492
+ list << columns.map { |column| column[index] }.
493
+ compact.join(" ") + "\n"
494
+ end
495
+ list
496
+ end
497
+ when :uneven_columns_across
498
+ if option.nil?
499
+ limit = @wrap_at || 80
500
+ items.size.downto(1) do |column_count|
501
+ row_count = (items.size / column_count.to_f).ceil
502
+ rows = Array.new(row_count) { Array.new }
503
+ items.each_with_index do |item, index|
504
+ rows[index / column_count] << item
505
+ end
506
+
507
+ widths = Array.new(column_count, 0)
508
+ rows.each do |row|
509
+ row.each_with_index do |field, column|
510
+ size = actual_length(field)
511
+ widths[column] = size if size > widths[column]
512
+ end
513
+ end
514
+
515
+ if column_count == 1 or
516
+ widths.inject(0) { |sum, n| sum + n + 2 } <= limit + 2
517
+ return rows.map { |row|
518
+ row.zip(widths).map { |field, i|
519
+ "%-#{i + (field.to_s.length - actual_length(field))}s" % field
520
+ }.join(" ") + "\n"
521
+ }.join
522
+ end
523
+ end
524
+ else
525
+ row_count = (items.size / option.to_f).ceil
526
+ rows = Array.new(row_count) { Array.new }
527
+ items.each_with_index do |item, index|
528
+ rows[index / option] << item
529
+ end
530
+
531
+ widths = Array.new(option, 0)
532
+ rows.each do |row|
533
+ row.each_with_index do |field, column|
534
+ size = actual_length(field)
535
+ widths[column] = size if size > widths[column]
536
+ end
537
+ end
538
+
539
+ return rows.map { |row|
540
+ row.zip(widths).map { |field, i|
541
+ "%-#{i + (field.to_s.length - actual_length(field))}s" % field
542
+ }.join(" ") + "\n"
543
+ }.join
544
+ end
545
+ when :uneven_columns_down
546
+ if option.nil?
547
+ limit = @wrap_at || 80
548
+ items.size.downto(1) do |column_count|
549
+ row_count = (items.size / column_count.to_f).ceil
550
+ columns = Array.new(column_count) { Array.new }
551
+ items.each_with_index do |item, index|
552
+ columns[index / row_count] << item
553
+ end
554
+
555
+ widths = Array.new(column_count, 0)
556
+ columns.each_with_index do |column, i|
557
+ column.each do |field|
558
+ size = actual_length(field)
559
+ widths[i] = size if size > widths[i]
560
+ end
561
+ end
562
+
563
+ if column_count == 1 or
564
+ widths.inject(0) { |sum, n| sum + n + 2 } <= limit + 2
565
+ list = ""
566
+ columns.first.size.times do |index|
567
+ list << columns.zip(widths).map { |column, width|
568
+ field = column[index]
569
+ "%-#{width + (field.to_s.length - actual_length(field))}s" %
570
+ field
571
+ }.compact.join(" ").strip + "\n"
572
+ end
573
+ return list
574
+ end
575
+ end
576
+ else
577
+ row_count = (items.size / option.to_f).ceil
578
+ columns = Array.new(option) { Array.new }
579
+ items.each_with_index do |item, index|
580
+ columns[index / row_count] << item
581
+ end
582
+
583
+ widths = Array.new(option, 0)
584
+ columns.each_with_index do |column, i|
585
+ column.each do |field|
586
+ size = actual_length(field)
587
+ widths[i] = size if size > widths[i]
588
+ end
589
+ end
590
+
591
+ list = ""
592
+ columns.first.size.times do |index|
593
+ list << columns.zip(widths).map { |column, width|
594
+ field = column[index]
595
+ "%-#{width + (field.to_s.length - actual_length(field))}s" % field
596
+ }.compact.join(" ").strip + "\n"
597
+ end
598
+ return list
599
+ end
600
+ else
601
+ items.map { |i| "#{i}\n" }.join
602
+ end
603
+ end
604
+ end
605
+
606
+ #
607
+ # The basic output method for HighLine objects. If the provided _statement_
608
+ # ends with a space or tab character, a newline will not be appended (output
609
+ # will be flush()ed). All other cases are passed straight to Kernel.puts().
610
+ #
611
+ # The _statement_ parameter is processed as an ERb template, supporting
612
+ # embedded Ruby code. The template is evaluated with a binding inside
613
+ # the HighLine instance, providing easy access to the ANSI color constants
614
+ # and the HighLine.color() method.
615
+ #
616
+ def say( statement )
617
+ statement = format_statement(statement)
618
+ return unless statement.length > 0
619
+
620
+ # Don't add a newline if statement ends with whitespace, OR
621
+ # if statement ends with whitespace before a color escape code.
622
+ if /[ \t](\e\[\d+(;\d+)*m)?\Z/ =~ statement
623
+ @output.print(indentation+statement)
624
+ @output.flush
625
+ else
626
+ @output.puts(indentation+statement)
627
+ end
628
+ end
629
+
630
+ #
631
+ # Set to an integer value to cause HighLine to wrap output lines at the
632
+ # indicated character limit. When +nil+, the default, no wrapping occurs. If
633
+ # set to <tt>:auto</tt>, HighLine will attempt to determine the columns
634
+ # available for the <tt>@output</tt> or use a sensible default.
635
+ #
636
+ def wrap_at=( setting )
637
+ @wrap_at = setting == :auto ? output_cols : setting
638
+ end
639
+
640
+ #
641
+ # Set to an integer value to cause HighLine to page output lines over the
642
+ # indicated line limit. When +nil+, the default, no paging occurs. If
643
+ # set to <tt>:auto</tt>, HighLine will attempt to determine the rows available
644
+ # for the <tt>@output</tt> or use a sensible default.
645
+ #
646
+ def page_at=( setting )
647
+ @page_at = setting == :auto ? output_rows - 2 : setting
648
+ end
649
+
650
+ #
651
+ # Outputs indentation with current settings
652
+ #
653
+ def indentation
654
+ return ' '*@indent_size*@indent_level
655
+ end
656
+
657
+ #
658
+ # Executes block or outputs statement with indentation
659
+ #
660
+ def indent(increase=1, statement=nil, multiline=nil)
661
+ @indent_level += increase
662
+ multi = @multi_indent
663
+ @multi_indent = multiline unless multiline.nil?
664
+ begin
665
+ if block_given?
666
+ yield self
667
+ else
668
+ say(statement)
669
+ end
670
+ rescue
671
+ @multi_indent = multi
672
+ @indent_level -= increase
673
+ raise
674
+ end
675
+ @multi_indent = multi
676
+ @indent_level -= increase
677
+ end
678
+
679
+ #
680
+ # Outputs newline
681
+ #
682
+ def newline
683
+ @output.puts
684
+ end
685
+
686
+ #
687
+ # Returns the number of columns for the console, or a default it they cannot
688
+ # be determined.
689
+ #
690
+ def output_cols
691
+ return 80 unless @output.tty?
692
+ terminal_size.first
693
+ rescue
694
+ return 80
695
+ end
696
+
697
+ #
698
+ # Returns the number of rows for the console, or a default if they cannot be
699
+ # determined.
700
+ #
701
+ def output_rows
702
+ return 24 unless @output.tty?
703
+ terminal_size.last
704
+ rescue
705
+ return 24
706
+ end
707
+
708
+ private
709
+
710
+ def format_statement statement
711
+ statement = statement.dup.to_str
712
+ return statement unless statement.length > 0
713
+
714
+ # Allow non-ascii menu prompts in ruby > 1.9.2. ERB eval the menu statement
715
+ # with the environment's default encoding(usually utf8)
716
+ statement.force_encoding(Encoding.default_external) if defined?(Encoding) && Encoding.default_external
717
+
718
+ template = ERB.new(statement, nil, "%")
719
+ statement = template.result(binding)
720
+
721
+ statement = wrap(statement) unless @wrap_at.nil?
722
+ statement = page_print(statement) unless @page_at.nil?
723
+
724
+ statement = statement.gsub(/\n(?!$)/,"\n#{indentation}") if @multi_indent
725
+
726
+ statement
727
+ end
728
+
729
+ #
730
+ # A helper method for sending the output stream and error and repeat
731
+ # of the question.
732
+ #
733
+ def explain_error( error )
734
+ say(@question.responses[error]) unless error.nil?
735
+ if @question.responses[:ask_on_error] == :question
736
+ say(@question)
737
+ elsif @question.responses[:ask_on_error]
738
+ say(@question.responses[:ask_on_error])
739
+ end
740
+ end
741
+
742
+ #
743
+ # Collects an Array/Hash full of answers as described in
744
+ # HighLine::Question.gather().
745
+ #
746
+ # Raises EOFError if input is exhausted.
747
+ #
748
+ def gather( )
749
+ original_question = @question
750
+ original_question_string = @question.question
751
+ original_gather = @question.gather
752
+
753
+ verify_match = @question.verify_match
754
+ @question.gather = false
755
+
756
+ begin # when verify_match is set this loop will repeat until unique_answers == 1
757
+ @answers = [ ]
758
+ @gather = original_gather
759
+ original_question.question = original_question_string
760
+
761
+ case @gather
762
+ when Integer
763
+ @answers << ask(@question)
764
+ @gather -= 1
765
+
766
+ original_question.question = ""
767
+ until @gather.zero?
768
+ @question = original_question
769
+ @answers << ask(@question)
770
+ @gather -= 1
771
+ end
772
+ when ::String, Regexp
773
+ @answers << ask(@question)
774
+
775
+ original_question.question = ""
776
+ until (@gather.is_a?(::String) and @answers.last.to_s == @gather) or
777
+ (@gather.is_a?(Regexp) and @answers.last.to_s =~ @gather)
778
+ @question = original_question
779
+ @answers << ask(@question)
780
+ end
781
+
782
+ @answers.pop
783
+ when Hash
784
+ @answers = { }
785
+ @gather.keys.sort.each do |key|
786
+ @question = original_question
787
+ @key = key
788
+ @answers[key] = ask(@question)
789
+ end
790
+ end
791
+
792
+ if verify_match && (unique_answers(@answers).size > 1)
793
+ @question = original_question
794
+ explain_error(:mismatch)
795
+ else
796
+ verify_match = false
797
+ end
798
+
799
+ end while verify_match
800
+
801
+ original_question.verify_match ? @answer : @answers
802
+ end
803
+
804
+ #
805
+ # A helper method used by HighLine::Question.verify_match
806
+ # for finding whether a list of answers match or differ
807
+ # from each other.
808
+ #
809
+ def unique_answers(list = @answers)
810
+ (list.respond_to?(:values) ? list.values : list).uniq
811
+ end
812
+
813
+ #
814
+ # Read a line of input from the input stream and process whitespace as
815
+ # requested by the Question object.
816
+ #
817
+ # If Question's _readline_ property is set, that library will be used to
818
+ # fetch input. *WARNING*: This ignores the currently set input stream.
819
+ #
820
+ # Raises EOFError if input is exhausted.
821
+ #
822
+ def get_line( )
823
+ if @question.readline
824
+ require "readline" # load only if needed
825
+
826
+ # capture say()'s work in a String to feed to readline()
827
+ old_output = @output
828
+ @output = StringIO.new
829
+ say(@question)
830
+ question = @output.string
831
+ @output = old_output
832
+
833
+ # prep auto-completion
834
+ Readline.completion_proc = lambda do |string|
835
+ @question.selection.grep(/\A#{Regexp.escape(string)}/)
836
+ end
837
+
838
+ # work-around ugly readline() warnings
839
+ old_verbose = $VERBOSE
840
+ $VERBOSE = nil
841
+ raw_answer = Readline.readline(question, true)
842
+ if raw_answer.nil?
843
+ if @@track_eof
844
+ raise EOFError, "The input stream is exhausted."
845
+ else
846
+ raw_answer = String.new # Never return nil
847
+ end
848
+ end
849
+ answer = @question.change_case(
850
+ @question.remove_whitespace(raw_answer))
851
+ $VERBOSE = old_verbose
852
+
853
+ answer
854
+ else
855
+ if JRUBY
856
+ statement = format_statement(@question)
857
+ raw_answer = @java_console.readLine(statement, nil)
858
+
859
+ raise EOFError, "The input stream is exhausted." if raw_answer.nil? and
860
+ @@track_eof
861
+ else
862
+ raise EOFError, "The input stream is exhausted." if @@track_eof and
863
+ @input.eof?
864
+ raw_answer = @input.gets
865
+ end
866
+
867
+ @question.change_case(@question.remove_whitespace(raw_answer))
868
+ end
869
+ end
870
+
871
+ #
872
+ # Return a line or character of input, as requested for this question.
873
+ # Character input will be returned as a single character String,
874
+ # not an Integer.
875
+ #
876
+ # This question's _first_answer_ will be returned instead of input, if set.
877
+ #
878
+ # Raises EOFError if input is exhausted.
879
+ #
880
+ def get_response( )
881
+ return @question.first_answer if @question.first_answer?
882
+
883
+ if @question.character.nil?
884
+ if @question.echo == true and @question.limit.nil?
885
+ get_line
886
+ else
887
+ raw_no_echo_mode
888
+
889
+ line = ""
890
+ backspace_limit = 0
891
+ begin
892
+
893
+ while character = get_character(@input)
894
+ # honor backspace and delete
895
+ if character == 127 or character == 8
896
+ line.slice!(-1, 1)
897
+ backspace_limit -= 1
898
+ else
899
+ line << character.chr
900
+ backspace_limit = line.size
901
+ end
902
+ # looking for carriage return (decimal 13) or
903
+ # newline (decimal 10) in raw input
904
+ break if character == 13 or character == 10
905
+ if @question.echo != false
906
+ if character == 127 or character == 8
907
+ # only backspace if we have characters on the line to
908
+ # eliminate, otherwise we'll tromp over the prompt
909
+ if backspace_limit >= 0 then
910
+ @output.print("\b#{HighLine.Style(:erase_char).code}")
911
+ else
912
+ # do nothing
913
+ end
914
+ else
915
+ if @question.echo == true
916
+ @output.print(character.chr)
917
+ else
918
+ @output.print(@question.echo)
919
+ end
920
+ end
921
+ @output.flush
922
+ end
923
+ break if @question.limit and line.size == @question.limit
924
+ end
925
+ ensure
926
+ restore_mode
927
+ end
928
+ if @question.overwrite
929
+ @output.print("\r#{HighLine.Style(:erase_line).code}")
930
+ @output.flush
931
+ else
932
+ say("\n")
933
+ end
934
+
935
+ @question.change_case(@question.remove_whitespace(line))
936
+ end
937
+ else
938
+ if JRUBY #prompt has not been shown
939
+ say @question
940
+ end
941
+
942
+ raw_no_echo_mode
943
+ begin
944
+ if @question.character == :getc
945
+ response = @input.getbyte.chr
946
+ else
947
+ response = get_character(@input).chr
948
+ if @question.overwrite
949
+ @output.print("\r#{HighLine.Style(:erase_line).code}")
950
+ @output.flush
951
+ else
952
+ echo = if @question.echo == true
953
+ response
954
+ elsif @question.echo != false
955
+ @question.echo
956
+ else
957
+ ""
958
+ end
959
+ say("#{echo}\n")
960
+ end
961
+ end
962
+ ensure
963
+ restore_mode
964
+ end
965
+ @question.change_case(response)
966
+ end
967
+ end
968
+
969
+ #
970
+ # Page print a series of at most _page_at_ lines for _output_. After each
971
+ # page is printed, HighLine will pause until the user presses enter/return
972
+ # then display the next page of data.
973
+ #
974
+ # Note that the final page of _output_ is *not* printed, but returned
975
+ # instead. This is to support any special handling for the final sequence.
976
+ #
977
+ def page_print( output )
978
+ lines = output.scan(/[^\n]*\n?/)
979
+ while lines.size > @page_at
980
+ @output.puts lines.slice!(0...@page_at).join
981
+ @output.puts
982
+ # Return last line if user wants to abort paging
983
+ return (["...\n"] + lines.slice(-2,1)).join unless continue_paging?
984
+ end
985
+ return lines.join
986
+ end
987
+
988
+ #
989
+ # Ask user if they wish to continue paging output. Allows them to type "q" to
990
+ # cancel the paging process.
991
+ #
992
+ def continue_paging?
993
+ command = HighLine.new(@input, @output).ask(
994
+ "-- press enter/return to continue or q to stop -- "
995
+ ) { |q| q.character = true }
996
+ command !~ /\A[qQ]\Z/ # Only continue paging if Q was not hit.
997
+ end
998
+
999
+ #
1000
+ # Wrap a sequence of _lines_ at _wrap_at_ characters per line. Existing
1001
+ # newlines will not be affected by this process, but additional newlines
1002
+ # may be added.
1003
+ #
1004
+ def wrap( text )
1005
+ wrapped = [ ]
1006
+ text.each_line do |line|
1007
+ # take into account color escape sequences when wrapping
1008
+ wrap_at = @wrap_at + (line.length - actual_length(line))
1009
+ while line =~ /([^\n]{#{wrap_at + 1},})/
1010
+ search = $1.dup
1011
+ replace = $1.dup
1012
+ if index = replace.rindex(" ", wrap_at)
1013
+ replace[index, 1] = "\n"
1014
+ replace.sub!(/\n[ \t]+/, "\n")
1015
+ line.sub!(search, replace)
1016
+ else
1017
+ line[$~.begin(1) + wrap_at, 0] = "\n"
1018
+ end
1019
+ end
1020
+ wrapped << line
1021
+ end
1022
+ return wrapped.join
1023
+ end
1024
+
1025
+ #
1026
+ # Returns the length of the passed +string_with_escapes+, minus and color
1027
+ # sequence escapes.
1028
+ #
1029
+ def actual_length( string_with_escapes )
1030
+ string_with_escapes.to_s.gsub(/\e\[\d{1,2}m/, "").length
1031
+ end
1032
+ end
1033
+
1034
+ require "highline/string_extensions"