thor 0.20.3 → 1.3.2

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -9
  3. data/lib/thor/actions/create_file.rb +4 -3
  4. data/lib/thor/actions/create_link.rb +3 -2
  5. data/lib/thor/actions/directory.rb +8 -18
  6. data/lib/thor/actions/empty_directory.rb +1 -1
  7. data/lib/thor/actions/file_manipulation.rb +22 -24
  8. data/lib/thor/actions/inject_into_file.rb +34 -13
  9. data/lib/thor/actions.rb +39 -30
  10. data/lib/thor/base.rb +196 -49
  11. data/lib/thor/command.rb +34 -18
  12. data/lib/thor/core_ext/hash_with_indifferent_access.rb +10 -0
  13. data/lib/thor/error.rb +14 -22
  14. data/lib/thor/group.rb +13 -2
  15. data/lib/thor/invocation.rb +2 -1
  16. data/lib/thor/line_editor/basic.rb +1 -1
  17. data/lib/thor/line_editor/readline.rb +6 -6
  18. data/lib/thor/line_editor.rb +2 -2
  19. data/lib/thor/nested_context.rb +29 -0
  20. data/lib/thor/parser/argument.rb +17 -1
  21. data/lib/thor/parser/arguments.rb +35 -15
  22. data/lib/thor/parser/option.rb +45 -13
  23. data/lib/thor/parser/options.rb +79 -11
  24. data/lib/thor/parser.rb +4 -4
  25. data/lib/thor/rake_compat.rb +3 -2
  26. data/lib/thor/runner.rb +43 -32
  27. data/lib/thor/shell/basic.rb +68 -162
  28. data/lib/thor/shell/color.rb +9 -43
  29. data/lib/thor/shell/column_printer.rb +29 -0
  30. data/lib/thor/shell/html.rb +7 -49
  31. data/lib/thor/shell/lcs_diff.rb +49 -0
  32. data/lib/thor/shell/table_printer.rb +118 -0
  33. data/lib/thor/shell/terminal.rb +42 -0
  34. data/lib/thor/shell/wrapped_printer.rb +38 -0
  35. data/lib/thor/shell.rb +5 -5
  36. data/lib/thor/util.rb +25 -8
  37. data/lib/thor/version.rb +1 -1
  38. data/lib/thor.rb +182 -17
  39. data/thor.gemspec +22 -10
  40. metadata +25 -11
  41. data/CHANGELOG.md +0 -204
  42. data/lib/thor/core_ext/io_binary_read.rb +0 -12
  43. data/lib/thor/core_ext/ordered_hash.rb +0 -129
@@ -1,8 +1,10 @@
1
+ require_relative "column_printer"
2
+ require_relative "table_printer"
3
+ require_relative "wrapped_printer"
4
+
1
5
  class Thor
2
6
  module Shell
3
7
  class Basic
4
- DEFAULT_TERMINAL_WIDTH = 80
5
-
6
8
  attr_accessor :base
7
9
  attr_reader :padding
8
10
 
@@ -65,15 +67,15 @@ class Thor
65
67
  # Readline.
66
68
  #
67
69
  # ==== Example
68
- # ask("What is your name?")
70
+ # ask("What is your name?")
69
71
  #
70
- # ask("What is the planet furthest from the sun?", :default => "Pluto")
72
+ # ask("What is the planet furthest from the sun?", :default => "Neptune")
71
73
  #
72
- # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
74
+ # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
73
75
  #
74
- # ask("What is your password?", :echo => false)
76
+ # ask("What is your password?", :echo => false)
75
77
  #
76
- # ask("Where should the file be saved?", :path => true)
78
+ # ask("Where should the file be saved?", :path => true)
77
79
  #
78
80
  def ask(statement, *args)
79
81
  options = args.last.is_a?(Hash) ? args.pop : {}
@@ -91,9 +93,11 @@ class Thor
91
93
  # are passed straight to puts (behavior got from Highline).
92
94
  #
93
95
  # ==== Example
94
- # say("I know you knew that.")
96
+ # say("I know you knew that.")
95
97
  #
96
98
  def say(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/))
99
+ return if quiet?
100
+
97
101
  buffer = prepare_message(message, *color)
98
102
  buffer << "\n" if force_new_line && !message.to_s.end_with?("\n")
99
103
 
@@ -101,6 +105,23 @@ class Thor
101
105
  stdout.flush
102
106
  end
103
107
 
108
+ # Say (print) an error to the user. If the sentence ends with a whitespace
109
+ # or tab character, a new line is not appended (print + flush). Otherwise
110
+ # are passed straight to puts (behavior got from Highline).
111
+ #
112
+ # ==== Example
113
+ # say_error("error: something went wrong")
114
+ #
115
+ def say_error(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/))
116
+ return if quiet?
117
+
118
+ buffer = prepare_message(message, *color)
119
+ buffer << "\n" if force_new_line && !message.to_s.end_with?("\n")
120
+
121
+ stderr.print(buffer)
122
+ stderr.flush
123
+ end
124
+
104
125
  # Say a status with the given color and appends the message. Since this
105
126
  # method is used frequently by actions, it allows nil or false to be given
106
127
  # in log_status, avoiding the message from being shown. If a Symbol is
@@ -109,30 +130,31 @@ class Thor
109
130
  def say_status(status, message, log_status = true)
110
131
  return if quiet? || log_status == false
111
132
  spaces = " " * (padding + 1)
112
- color = log_status.is_a?(Symbol) ? log_status : :green
113
-
114
133
  status = status.to_s.rjust(12)
134
+ margin = " " * status.length + spaces
135
+
136
+ color = log_status.is_a?(Symbol) ? log_status : :green
115
137
  status = set_color status, color, true if color
116
138
 
117
- buffer = "#{status}#{spaces}#{message}"
118
- buffer = "#{buffer}\n" unless buffer.end_with?("\n")
139
+ message = message.to_s.chomp.gsub(/(?<!\A)^/, margin)
140
+ buffer = "#{status}#{spaces}#{message}\n"
119
141
 
120
142
  stdout.print(buffer)
121
143
  stdout.flush
122
144
  end
123
145
 
124
- # Make a question the to user and returns true if the user replies "y" or
146
+ # Asks the user a question and returns true if the user replies "y" or
125
147
  # "yes".
126
148
  #
127
149
  def yes?(statement, color = nil)
128
- !!(ask(statement, color, :add_to_history => false) =~ is?(:yes))
150
+ !!(ask(statement, color, add_to_history: false) =~ is?(:yes))
129
151
  end
130
152
 
131
- # Make a question the to user and returns true if the user replies "n" or
153
+ # Asks the user a question and returns true if the user replies "n" or
132
154
  # "no".
133
155
  #
134
156
  def no?(statement, color = nil)
135
- !!(ask(statement, color, :add_to_history => false) =~ is?(:no))
157
+ !!(ask(statement, color, add_to_history: false) =~ is?(:no))
136
158
  end
137
159
 
138
160
  # Prints values in columns
@@ -141,16 +163,8 @@ class Thor
141
163
  # Array[String, String, ...]
142
164
  #
143
165
  def print_in_columns(array)
144
- return if array.empty?
145
- colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2
146
- array.each_with_index do |value, index|
147
- # Don't output trailing spaces when printing the last column
148
- if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length
149
- stdout.puts value
150
- else
151
- stdout.printf("%-#{colwidth}s", value)
152
- end
153
- end
166
+ printer = ColumnPrinter.new(stdout)
167
+ printer.print(array)
154
168
  end
155
169
 
156
170
  # Prints a table.
@@ -161,58 +175,11 @@ class Thor
161
175
  # ==== Options
162
176
  # indent<Integer>:: Indent the first column by indent value.
163
177
  # colwidth<Integer>:: Force the first column to colwidth spaces wide.
178
+ # borders<Boolean>:: Adds ascii borders.
164
179
  #
165
- def print_table(array, options = {}) # rubocop:disable MethodLength
166
- return if array.empty?
167
-
168
- formats = []
169
- indent = options[:indent].to_i
170
- colwidth = options[:colwidth]
171
- options[:truncate] = terminal_width if options[:truncate] == true
172
-
173
- formats << "%-#{colwidth + 2}s".dup if colwidth
174
- start = colwidth ? 1 : 0
175
-
176
- colcount = array.max { |a, b| a.size <=> b.size }.size
177
-
178
- maximas = []
179
-
180
- start.upto(colcount - 1) do |index|
181
- maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max
182
- maximas << maxima
183
- formats << if index == colcount - 1
184
- # Don't output 2 trailing spaces when printing the last column
185
- "%-s".dup
186
- else
187
- "%-#{maxima + 2}s".dup
188
- end
189
- end
190
-
191
- formats[0] = formats[0].insert(0, " " * indent)
192
- formats << "%s"
193
-
194
- array.each do |row|
195
- sentence = "".dup
196
-
197
- row.each_with_index do |column, index|
198
- maxima = maximas[index]
199
-
200
- f = if column.is_a?(Numeric)
201
- if index == row.size - 1
202
- # Don't output 2 trailing spaces when printing the last column
203
- "%#{maxima}s"
204
- else
205
- "%#{maxima}s "
206
- end
207
- else
208
- formats[index]
209
- end
210
- sentence << f % column.to_s
211
- end
212
-
213
- sentence = truncate(sentence, options[:truncate]) if options[:truncate]
214
- stdout.puts sentence
215
- end
180
+ def print_table(array, options = {}) # rubocop:disable Metrics/MethodLength
181
+ printer = TablePrinter.new(stdout, options)
182
+ printer.print(array)
216
183
  end
217
184
 
218
185
  # Prints a long string, word-wrapping the text to the current width of the
@@ -225,32 +192,8 @@ class Thor
225
192
  # indent<Integer>:: Indent each line of the printed paragraph by indent value.
226
193
  #
227
194
  def print_wrapped(message, options = {})
228
- indent = options[:indent] || 0
229
- width = terminal_width - indent
230
- paras = message.split("\n\n")
231
-
232
- paras.map! do |unwrapped|
233
- counter = 0
234
- unwrapped.split(" ").inject do |memo, word|
235
- word = word.gsub(/\n\005/, "\n").gsub(/\005/, "\n")
236
- counter = 0 if word.include? "\n"
237
- if (counter + word.length + 1) < width
238
- memo = "#{memo} #{word}"
239
- counter += (word.length + 1)
240
- else
241
- memo = "#{memo}\n#{word}"
242
- counter = word.length
243
- end
244
- memo
245
- end
246
- end.compact!
247
-
248
- paras.each do |para|
249
- para.split("\n").each do |line|
250
- stdout.puts line.insert(0, " " * indent)
251
- end
252
- stdout.puts unless para == paras.last
253
- end
195
+ printer = WrappedPrinter.new(stdout, options)
196
+ printer.print(message)
254
197
  end
255
198
 
256
199
  # Deals with file collision and returns true if the file should be
@@ -268,7 +211,7 @@ class Thor
268
211
  loop do
269
212
  answer = ask(
270
213
  %[Overwrite #{destination}? (enter "h" for help) #{options}],
271
- :add_to_history => false
214
+ add_to_history: false
272
215
  )
273
216
 
274
217
  case answer
@@ -295,24 +238,11 @@ class Thor
295
238
 
296
239
  say "Please specify merge tool to `THOR_MERGE` env."
297
240
  else
298
- say file_collision_help
241
+ say file_collision_help(block_given?)
299
242
  end
300
243
  end
301
244
  end
302
245
 
303
- # This code was copied from Rake, available under MIT-LICENSE
304
- # Copyright (c) 2003, 2004 Jim Weirich
305
- def terminal_width
306
- result = if ENV["THOR_COLUMNS"]
307
- ENV["THOR_COLUMNS"].to_i
308
- else
309
- unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH
310
- end
311
- result < 10 ? DEFAULT_TERMINAL_WIDTH : result
312
- rescue
313
- DEFAULT_TERMINAL_WIDTH
314
- end
315
-
316
246
  # Called if something goes wrong during the execution. This is used by Thor
317
247
  # internally and should not be used inside your scripts. If something went
318
248
  # wrong, you can always raise an exception. If you raise a Thor::Error, it
@@ -363,16 +293,21 @@ class Thor
363
293
  end
364
294
  end
365
295
 
366
- def file_collision_help #:nodoc:
367
- <<-HELP
296
+ def file_collision_help(block_given) #:nodoc:
297
+ help = <<-HELP
368
298
  Y - yes, overwrite
369
299
  n - no, do not overwrite
370
300
  a - all, overwrite this and all others
371
301
  q - quit, abort
372
- d - diff, show the differences between the old and the new
373
302
  h - help, show this help
374
- m - merge, run merge tool
375
303
  HELP
304
+ if block_given
305
+ help << <<-HELP
306
+ d - diff, show the differences between the old and the new
307
+ m - merge, run merge tool
308
+ HELP
309
+ end
310
+ help
376
311
  end
377
312
 
378
313
  def show_diff(destination, content) #:nodoc:
@@ -390,46 +325,8 @@ class Thor
390
325
  mute? || (base && base.options[:quiet])
391
326
  end
392
327
 
393
- # Calculate the dynamic width of the terminal
394
- def dynamic_width
395
- @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
396
- end
397
-
398
- def dynamic_width_stty
399
- `stty size 2>/dev/null`.split[1].to_i
400
- end
401
-
402
- def dynamic_width_tput
403
- `tput cols 2>/dev/null`.to_i
404
- end
405
-
406
328
  def unix?
407
- RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
408
- end
409
-
410
- def truncate(string, width)
411
- as_unicode do
412
- chars = string.chars.to_a
413
- if chars.length <= width
414
- chars.join
415
- else
416
- chars[0, width - 3].join + "..."
417
- end
418
- end
419
- end
420
-
421
- if "".respond_to?(:encode)
422
- def as_unicode
423
- yield
424
- end
425
- else
426
- def as_unicode
427
- old = $KCODE
428
- $KCODE = "U"
429
- yield
430
- ensure
431
- $KCODE = old
432
- end
329
+ Terminal.unix?
433
330
  end
434
331
 
435
332
  def ask_simply(statement, color, options)
@@ -451,16 +348,25 @@ class Thor
451
348
 
452
349
  def ask_filtered(statement, color, options)
453
350
  answer_set = options[:limited_to]
351
+ case_insensitive = options.fetch(:case_insensitive, false)
454
352
  correct_answer = nil
455
353
  until correct_answer
456
354
  answers = answer_set.join(", ")
457
355
  answer = ask_simply("#{statement} [#{answers}]", color, options)
458
- correct_answer = answer_set.include?(answer) ? answer : nil
356
+ correct_answer = answer_match(answer_set, answer, case_insensitive)
459
357
  say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
460
358
  end
461
359
  correct_answer
462
360
  end
463
361
 
362
+ def answer_match(possibilities, answer, case_insensitive)
363
+ if case_insensitive
364
+ possibilities.detect{ |possibility| possibility.downcase == answer.downcase }
365
+ else
366
+ possibilities.detect{ |possibility| possibility == answer }
367
+ end
368
+ end
369
+
464
370
  def merge(destination, content) #:nodoc:
465
371
  require "tempfile"
466
372
  Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp|
@@ -1,4 +1,5 @@
1
- require "thor/shell/basic"
1
+ require_relative "basic"
2
+ require_relative "lcs_diff"
2
3
 
3
4
  class Thor
4
5
  module Shell
@@ -6,6 +7,8 @@ class Thor
6
7
  # Thor::Shell::Basic to see all available methods.
7
8
  #
8
9
  class Color < Basic
10
+ include LCSDiff
11
+
9
12
  # Embed in a String to clear all previous ANSI sequences.
10
13
  CLEAR = "\e[0m"
11
14
  # The start of an ANSI bold sequence.
@@ -97,52 +100,15 @@ class Thor
97
100
  protected
98
101
 
99
102
  def can_display_colors?
100
- stdout.tty?
101
- end
102
-
103
- # Overwrite show_diff to show diff with colors if Diff::LCS is
104
- # available.
105
- #
106
- def show_diff(destination, content) #:nodoc:
107
- if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil?
108
- actual = File.binread(destination).to_s.split("\n")
109
- content = content.to_s.split("\n")
110
-
111
- Diff::LCS.sdiff(actual, content).each do |diff|
112
- output_diff_line(diff)
113
- end
114
- else
115
- super
116
- end
103
+ are_colors_supported? && !are_colors_disabled?
117
104
  end
118
105
 
119
- def output_diff_line(diff) #:nodoc:
120
- case diff.action
121
- when "-"
122
- say "- #{diff.old_element.chomp}", :red, true
123
- when "+"
124
- say "+ #{diff.new_element.chomp}", :green, true
125
- when "!"
126
- say "- #{diff.old_element.chomp}", :red, true
127
- say "+ #{diff.new_element.chomp}", :green, true
128
- else
129
- say " #{diff.old_element.chomp}", nil, true
130
- end
106
+ def are_colors_supported?
107
+ stdout.tty? && ENV["TERM"] != "dumb"
131
108
  end
132
109
 
133
- # Check if Diff::LCS is loaded. If it is, use it to create pretty output
134
- # for diff.
135
- #
136
- def diff_lcs_loaded? #:nodoc:
137
- return true if defined?(Diff::LCS)
138
- return @diff_lcs_loaded unless @diff_lcs_loaded.nil?
139
-
140
- @diff_lcs_loaded = begin
141
- require "diff/lcs"
142
- true
143
- rescue LoadError
144
- false
145
- end
110
+ def are_colors_disabled?
111
+ !ENV["NO_COLOR"].nil? && !ENV["NO_COLOR"].empty?
146
112
  end
147
113
  end
148
114
  end
@@ -0,0 +1,29 @@
1
+ require_relative "terminal"
2
+
3
+ class Thor
4
+ module Shell
5
+ class ColumnPrinter
6
+ attr_reader :stdout, :options
7
+
8
+ def initialize(stdout, options = {})
9
+ @stdout = stdout
10
+ @options = options
11
+ @indent = options[:indent].to_i
12
+ end
13
+
14
+ def print(array)
15
+ return if array.empty?
16
+ colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2
17
+ array.each_with_index do |value, index|
18
+ # Don't output trailing spaces when printing the last column
19
+ if ((((index + 1) % (Terminal.terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length
20
+ stdout.puts value
21
+ else
22
+ stdout.printf("%-#{colwidth}s", value)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -1,4 +1,5 @@
1
- require "thor/shell/basic"
1
+ require_relative "basic"
2
+ require_relative "lcs_diff"
2
3
 
3
4
  class Thor
4
5
  module Shell
@@ -6,6 +7,8 @@ class Thor
6
7
  # Thor::Shell::Basic to see all available methods.
7
8
  #
8
9
  class HTML < Basic
10
+ include LCSDiff
11
+
9
12
  # The start of an HTML bold sequence.
10
13
  BOLD = "font-weight: bold"
11
14
 
@@ -51,20 +54,20 @@ class Thor
51
54
  def set_color(string, *colors)
52
55
  if colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) }
53
56
  html_colors = colors.map { |color| lookup_color(color) }
54
- "<span style=\"#{html_colors.join('; ')};\">#{string}</span>"
57
+ "<span style=\"#{html_colors.join('; ')};\">#{Thor::Util.escape_html(string)}</span>"
55
58
  else
56
59
  color, bold = colors
57
60
  html_color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol)
58
61
  styles = [html_color]
59
62
  styles << BOLD if bold
60
- "<span style=\"#{styles.join('; ')};\">#{string}</span>"
63
+ "<span style=\"#{styles.join('; ')};\">#{Thor::Util.escape_html(string)}</span>"
61
64
  end
62
65
  end
63
66
 
64
67
  # Ask something to the user and receives a response.
65
68
  #
66
69
  # ==== Example
67
- # ask("What is your name?")
70
+ # ask("What is your name?")
68
71
  #
69
72
  # TODO: Implement #ask for Thor::Shell::HTML
70
73
  def ask(statement, color = nil)
@@ -76,51 +79,6 @@ class Thor
76
79
  def can_display_colors?
77
80
  true
78
81
  end
79
-
80
- # Overwrite show_diff to show diff with colors if Diff::LCS is
81
- # available.
82
- #
83
- def show_diff(destination, content) #:nodoc:
84
- if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil?
85
- actual = File.binread(destination).to_s.split("\n")
86
- content = content.to_s.split("\n")
87
-
88
- Diff::LCS.sdiff(actual, content).each do |diff|
89
- output_diff_line(diff)
90
- end
91
- else
92
- super
93
- end
94
- end
95
-
96
- def output_diff_line(diff) #:nodoc:
97
- case diff.action
98
- when "-"
99
- say "- #{diff.old_element.chomp}", :red, true
100
- when "+"
101
- say "+ #{diff.new_element.chomp}", :green, true
102
- when "!"
103
- say "- #{diff.old_element.chomp}", :red, true
104
- say "+ #{diff.new_element.chomp}", :green, true
105
- else
106
- say " #{diff.old_element.chomp}", nil, true
107
- end
108
- end
109
-
110
- # Check if Diff::LCS is loaded. If it is, use it to create pretty output
111
- # for diff.
112
- #
113
- def diff_lcs_loaded? #:nodoc:
114
- return true if defined?(Diff::LCS)
115
- return @diff_lcs_loaded unless @diff_lcs_loaded.nil?
116
-
117
- @diff_lcs_loaded = begin
118
- require "diff/lcs"
119
- true
120
- rescue LoadError
121
- false
122
- end
123
- end
124
82
  end
125
83
  end
126
84
  end
@@ -0,0 +1,49 @@
1
+ module LCSDiff
2
+ protected
3
+
4
+ # Overwrite show_diff to show diff with colors if Diff::LCS is
5
+ # available.
6
+ def show_diff(destination, content) #:nodoc:
7
+ if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil?
8
+ actual = File.binread(destination).to_s.split("\n")
9
+ content = content.to_s.split("\n")
10
+
11
+ Diff::LCS.sdiff(actual, content).each do |diff|
12
+ output_diff_line(diff)
13
+ end
14
+ else
15
+ super
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def output_diff_line(diff) #:nodoc:
22
+ case diff.action
23
+ when "-"
24
+ say "- #{diff.old_element.chomp}", :red, true
25
+ when "+"
26
+ say "+ #{diff.new_element.chomp}", :green, true
27
+ when "!"
28
+ say "- #{diff.old_element.chomp}", :red, true
29
+ say "+ #{diff.new_element.chomp}", :green, true
30
+ else
31
+ say " #{diff.old_element.chomp}", nil, true
32
+ end
33
+ end
34
+
35
+ # Check if Diff::LCS is loaded. If it is, use it to create pretty output
36
+ # for diff.
37
+ def diff_lcs_loaded? #:nodoc:
38
+ return true if defined?(Diff::LCS)
39
+ return @diff_lcs_loaded unless @diff_lcs_loaded.nil?
40
+
41
+ @diff_lcs_loaded = begin
42
+ require "diff/lcs"
43
+ true
44
+ rescue LoadError
45
+ false
46
+ end
47
+ end
48
+
49
+ end