truex-skylight 0.6.0

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 (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"