peony 0.1.6 → 0.1.8

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.
@@ -0,0 +1,429 @@
1
+ require 'tempfile'
2
+ require 'io/console' if RUBY_VERSION > '1.9.2'
3
+
4
+ module Peony
5
+ module Shell
6
+ class Basic # rubocop:disable ClassLength
7
+ attr_accessor :base
8
+ attr_reader :padding
9
+
10
+ # Initialize base, mute and padding to nil.
11
+ #
12
+ def initialize #:nodoc:
13
+ @base, @mute, @padding, @always_force = nil, false, 0, false
14
+ end
15
+
16
+ # Mute everything that's inside given block
17
+ #
18
+ def mute
19
+ @mute = true
20
+ yield
21
+ ensure
22
+ @mute = false
23
+ end
24
+
25
+ # Check if base is muted
26
+ #
27
+ def mute? # rubocop:disable TrivialAccessors
28
+ @mute
29
+ end
30
+
31
+ # Sets the output padding, not allowing less than zero values.
32
+ #
33
+ def padding_to(value)
34
+ @padding = [0, value].max
35
+ end
36
+
37
+ def padding_up
38
+ @padding += 1
39
+ end
40
+
41
+ def padding_down
42
+ @padding += 1
43
+ end
44
+
45
+ # Asks something to the user and receives a response.
46
+ #
47
+ # If asked to limit the correct responses, you can pass in an
48
+ # array of acceptable answers. If one of those is not supplied,
49
+ # they will be shown a message stating that one of those answers
50
+ # must be given and re-asked the question.
51
+ #
52
+ # If asking for sensitive information, the :echo option can be set
53
+ # to false to mask user input from $stdin.
54
+ #
55
+ # If the required input is a path, then set the path option to
56
+ # true. This will enable tab completion for file paths relative
57
+ # to the current working directory on systems that support
58
+ # Readline.
59
+ #
60
+ # ==== Example
61
+ # ask("What is your name?")
62
+ #
63
+ # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
64
+ #
65
+ # ask("What is your password?", :echo => false)
66
+ #
67
+ # ask("Where should the file be saved?", :path => true)
68
+ #
69
+ def ask(statement, *args)
70
+ options = args.last.is_a?(Hash) ? args.pop : {}
71
+ color = args.first
72
+
73
+ if options[:limited_to]
74
+ ask_filtered(statement, color, options)
75
+ else
76
+ ask_simply(statement, color, options)
77
+ end
78
+ end
79
+
80
+ # Say (print) something to the user. If the sentence ends with a whitespace
81
+ # or tab character, a new line is not appended (print + flush). Otherwise
82
+ # are passed straight to puts (behavior got from Highline).
83
+ #
84
+ # ==== Example
85
+ # say("I know you knew that.")
86
+ #
87
+ def say(message = '', color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/))
88
+ buffer = prepare_message(message, *color)
89
+ buffer << "\n" if force_new_line && !message.end_with?("\n")
90
+
91
+ stdout.print(buffer)
92
+ stdout.flush
93
+ end
94
+
95
+ # Say a status with the given color and appends the message. Since this
96
+ # method is used frequently by actions, it allows nil or false to be given
97
+ # in log_status, avoiding the message from being shown. If a Symbol is
98
+ # given in log_status, it's used as the color.
99
+ #
100
+ def say_status(status, message, log_status = true)
101
+ return if quiet? || log_status == false
102
+ spaces = ' ' * (padding + 1)
103
+ color = log_status.is_a?(Symbol) ? log_status : :green
104
+
105
+ status = status.to_s.rjust(12)
106
+ status = set_color status, color, true if color
107
+
108
+ buffer = "#{status}#{spaces}#{message}"
109
+ buffer << "\n" unless buffer.end_with?("\n")
110
+
111
+ stdout.print(buffer)
112
+ stdout.flush
113
+ end
114
+
115
+ # Make a question the to user and returns true if the user replies "y" or
116
+ # "yes".
117
+ #
118
+ def yes?(statement, color = nil)
119
+ !!(ask(statement, color, :add_to_history => false) =~ is?(:yes))
120
+ end
121
+
122
+ # Make a question the to user and returns true if the user replies "n" or
123
+ # "no".
124
+ #
125
+ def no?(statement, color = nil)
126
+ !!(ask(statement, color, :add_to_history => false) =~ is?(:no))
127
+ end
128
+
129
+ # Prints values in columns
130
+ #
131
+ # ==== Parameters
132
+ # Array[String, String, ...]
133
+ #
134
+ def print_in_columns(array)
135
+ return if array.empty?
136
+ colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2
137
+ array.each_with_index do |value, index|
138
+ # Don't output trailing spaces when printing the last column
139
+ if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length
140
+ stdout.puts value
141
+ else
142
+ stdout.printf("%-#{colwidth}s", value)
143
+ end
144
+ end
145
+ end
146
+
147
+ # Prints a table.
148
+ #
149
+ # ==== Parameters
150
+ # Array[Array[String, String, ...]]
151
+ #
152
+ # ==== Options
153
+ # indent<Integer>:: Indent the first column by indent value.
154
+ # colwidth<Integer>:: Force the first column to colwidth spaces wide.
155
+ #
156
+ def print_table(array, options = {}) # rubocop:disable MethodLength
157
+ return if array.empty?
158
+
159
+ formats, indent, colwidth = [], options[:indent].to_i, options[:colwidth]
160
+ options[:truncate] = terminal_width if options[:truncate] == true
161
+
162
+ formats << "%-#{colwidth + 2}s" if colwidth
163
+ start = colwidth ? 1 : 0
164
+
165
+ colcount = array.max { |a, b| a.size <=> b.size }.size
166
+
167
+ maximas = []
168
+
169
+ start.upto(colcount - 1) do |index|
170
+ maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max
171
+ maximas << maxima
172
+ if index == colcount - 1
173
+ # Don't output 2 trailing spaces when printing the last column
174
+ formats << '%-s'
175
+ else
176
+ formats << "%-#{maxima + 2}s"
177
+ end
178
+ end
179
+
180
+ formats[0] = formats[0].insert(0, ' ' * indent)
181
+ formats << '%s'
182
+
183
+ array.each do |row|
184
+ sentence = ''
185
+
186
+ row.each_with_index do |column, index|
187
+ maxima = maximas[index]
188
+
189
+ if column.is_a?(Numeric)
190
+ if index == row.size - 1
191
+ # Don't output 2 trailing spaces when printing the last column
192
+ f = "%#{maxima}s"
193
+ else
194
+ f = "%#{maxima}s "
195
+ end
196
+ else
197
+ f = formats[index]
198
+ end
199
+ sentence << f % column.to_s
200
+ end
201
+
202
+ sentence = truncate(sentence, options[:truncate]) if options[:truncate]
203
+ stdout.puts sentence
204
+ end
205
+ end
206
+
207
+ # Prints a long string, word-wrapping the text to the current width of the
208
+ # terminal display. Ideal for printing heredocs.
209
+ #
210
+ # ==== Parameters
211
+ # String
212
+ #
213
+ # ==== Options
214
+ # indent<Integer>:: Indent each line of the printed paragraph by indent value.
215
+ #
216
+ def print_wrapped(message, options = {})
217
+ indent = options[:indent] || 0
218
+ width = terminal_width - indent
219
+ paras = message.split("\n\n")
220
+
221
+ paras.map! do |unwrapped|
222
+ unwrapped.strip.gsub(/\n/, ' ').squeeze(' ').gsub(/.{1,#{width}}(?:\s|\Z)/) { ($& + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n") }
223
+ end
224
+
225
+ paras.each do |para|
226
+ para.split("\n").each do |line|
227
+ stdout.puts line.insert(0, ' ' * indent)
228
+ end
229
+ stdout.puts unless para == paras.last
230
+ end
231
+ end
232
+
233
+ # Deals with file collision and returns true if the file should be
234
+ # overwritten and false otherwise. If a block is given, it uses the block
235
+ # response as the content for the diff.
236
+ #
237
+ # ==== Parameters
238
+ # destination<String>:: the destination file to solve conflicts
239
+ # block<Proc>:: an optional block that returns the value to be used in diff
240
+ #
241
+ def file_collision(destination) # rubocop:disable MethodLength
242
+ return true if @always_force
243
+ options = block_given? ? '[Ynaqdh]' : '[Ynaqh]'
244
+
245
+ loop do
246
+ answer = ask(
247
+ %[Overwrite #{destination}? (enter "h" for help) #{options}],
248
+ :add_to_history => false
249
+ )
250
+
251
+ case answer
252
+ when is?(:yes), is?(:force), ''
253
+ return true
254
+ when is?(:no), is?(:skip)
255
+ return false
256
+ when is?(:always)
257
+ return @always_force = true
258
+ when is?(:quit)
259
+ say 'Aborting...'
260
+ fail SystemExit
261
+ when is?(:diff)
262
+ show_diff(destination, yield) if block_given?
263
+ say 'Retrying...'
264
+ else
265
+ say file_collision_help
266
+ end
267
+ end
268
+ end
269
+
270
+ # This code was copied from Rake, available under MIT-LICENSE
271
+ # Copyright (c) 2003, 2004 Jim Weirich
272
+ def terminal_width
273
+ if ENV['THOR_COLUMNS']
274
+ result = ENV['THOR_COLUMNS'].to_i
275
+ else
276
+ result = unix? ? dynamic_width : 80
277
+ end
278
+ result < 10 ? 80 : result
279
+ rescue
280
+ 80
281
+ end
282
+
283
+ # Called if something goes wrong during the execution. This is used by Thor
284
+ # internally and should not be used inside your scripts. If something went
285
+ # wrong, you can always raise an exception. If you raise a Thor::Error, it
286
+ # will be rescued and wrapped in the method below.
287
+ #
288
+ def error(statement)
289
+ stderr.puts statement
290
+ end
291
+
292
+ # Apply color to the given string with optional bold. Disabled in the
293
+ # Thor::Shell::Basic class.
294
+ #
295
+ def set_color(string, *args) #:nodoc:
296
+ string
297
+ end
298
+
299
+ protected
300
+
301
+ def prepare_message(message, *color)
302
+ spaces = ' ' * padding
303
+ spaces + set_color(message.to_s, *color)
304
+ end
305
+
306
+ def can_display_colors?
307
+ false
308
+ end
309
+
310
+ def lookup_color(color)
311
+ return color unless color.is_a?(Symbol)
312
+ self.class.const_get(color.to_s.upcase)
313
+ end
314
+
315
+ def stdout
316
+ $stdout
317
+ end
318
+
319
+ def stderr
320
+ $stderr
321
+ end
322
+
323
+ def is?(value) #:nodoc:
324
+ value = value.to_s
325
+
326
+ if value.size == 1
327
+ /\A#{value}\z/i
328
+ else
329
+ /\A(#{value}|#{value[0, 1]})\z/i
330
+ end
331
+ end
332
+
333
+ def file_collision_help #:nodoc:
334
+ <<-HELP
335
+ Y - yes, overwrite
336
+ n - no, do not overwrite
337
+ a - all, overwrite this and all others
338
+ q - quit, abort
339
+ d - diff, show the differences between the old and the new
340
+ h - help, show this help
341
+ HELP
342
+ end
343
+
344
+ def show_diff(destination, content) #:nodoc:
345
+ diff_cmd = ENV['THOR_DIFF'] || ENV['RAILS_DIFF'] || 'diff -u'
346
+
347
+ Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
348
+ temp.write content
349
+ temp.rewind
350
+ system %(#{diff_cmd} "#{destination}" "#{temp.path}")
351
+ end
352
+ end
353
+
354
+ def quiet? #:nodoc:
355
+ mute? || (base && base.options[:quiet])
356
+ end
357
+
358
+ # Calculate the dynamic width of the terminal
359
+ def dynamic_width
360
+ @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
361
+ end
362
+
363
+ def dynamic_width_stty
364
+ %x(stty size 2>/dev/null).split[1].to_i
365
+ end
366
+
367
+ def dynamic_width_tput
368
+ %x(tput cols 2>/dev/null).to_i
369
+ end
370
+
371
+ def unix?
372
+ RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
373
+ end
374
+
375
+ def truncate(string, width)
376
+ as_unicode do
377
+ chars = string.chars.to_a
378
+ if chars.length <= width
379
+ chars.join
380
+ else
381
+ ( chars[0, width - 3].join) + '...'
382
+ end
383
+ end
384
+ end
385
+
386
+ if ''.respond_to?(:encode)
387
+ def as_unicode
388
+ yield
389
+ end
390
+ else
391
+ def as_unicode
392
+ old, $KCODE = $KCODE, 'U'
393
+ yield
394
+ ensure
395
+ $KCODE = old
396
+ end
397
+ end
398
+
399
+ def ask_simply(statement, color, options)
400
+ default = options[:default]
401
+ message = [statement, ("(#{default})" if default), nil].uniq.join(' ')
402
+ message = prepare_message(message, color)
403
+ result = Peony::LineEditor.readline(message, options)
404
+
405
+ return unless result
406
+
407
+ result.strip!
408
+
409
+ if default && result == ''
410
+ default
411
+ else
412
+ result
413
+ end
414
+ end
415
+
416
+ def ask_filtered(statement, color, options)
417
+ answer_set = options[:limited_to]
418
+ correct_answer = nil
419
+ until correct_answer
420
+ answers = answer_set.join(', ')
421
+ answer = ask_simply("#{statement} [#{answers}]", color, options)
422
+ correct_answer = answer_set.include?(answer) ? answer : nil
423
+ say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
424
+ end
425
+ correct_answer
426
+ end
427
+ end
428
+ end
429
+ end
@@ -0,0 +1,149 @@
1
+ require 'peony/shell/basic'
2
+
3
+ module Peony
4
+ module Shell
5
+ # Inherit from Thor::Shell::Basic and add set_color behavior. Check
6
+ # Thor::Shell::Basic to see all available methods.
7
+ #
8
+ class Color < Basic
9
+ # Embed in a String to clear all previous ANSI sequences.
10
+ CLEAR = "\e[0m"
11
+ # The start of an ANSI bold sequence.
12
+ BOLD = "\e[1m"
13
+
14
+ # Set the terminal's foreground ANSI color to black.
15
+ BLACK = "\e[30m"
16
+ # Set the terminal's foreground ANSI color to red.
17
+ RED = "\e[31m"
18
+ # Set the terminal's foreground ANSI color to green.
19
+ GREEN = "\e[32m"
20
+ # Set the terminal's foreground ANSI color to yellow.
21
+ YELLOW = "\e[33m"
22
+ # Set the terminal's foreground ANSI color to blue.
23
+ BLUE = "\e[34m"
24
+ # Set the terminal's foreground ANSI color to magenta.
25
+ MAGENTA = "\e[35m"
26
+ # Set the terminal's foreground ANSI color to cyan.
27
+ CYAN = "\e[36m"
28
+ # Set the terminal's foreground ANSI color to white.
29
+ WHITE = "\e[37m"
30
+
31
+ # Set the terminal's background ANSI color to black.
32
+ ON_BLACK = "\e[40m"
33
+ # Set the terminal's background ANSI color to red.
34
+ ON_RED = "\e[41m"
35
+ # Set the terminal's background ANSI color to green.
36
+ ON_GREEN = "\e[42m"
37
+ # Set the terminal's background ANSI color to yellow.
38
+ ON_YELLOW = "\e[43m"
39
+ # Set the terminal's background ANSI color to blue.
40
+ ON_BLUE = "\e[44m"
41
+ # Set the terminal's background ANSI color to magenta.
42
+ ON_MAGENTA = "\e[45m"
43
+ # Set the terminal's background ANSI color to cyan.
44
+ ON_CYAN = "\e[46m"
45
+ # Set the terminal's background ANSI color to white.
46
+ ON_WHITE = "\e[47m"
47
+
48
+ # Set color by using a string or one of the defined constants. If a third
49
+ # option is set to true, it also adds bold to the string. This is based
50
+ # on Highline implementation and it automatically appends CLEAR to the end
51
+ # of the returned String.
52
+ #
53
+ # Pass foreground, background and bold options to this method as
54
+ # symbols.
55
+ #
56
+ # Example:
57
+ #
58
+ # set_color "Hi!", :red, :on_white, :bold
59
+ #
60
+ # The available colors are:
61
+ #
62
+ # :bold
63
+ # :black
64
+ # :red
65
+ # :green
66
+ # :yellow
67
+ # :blue
68
+ # :magenta
69
+ # :cyan
70
+ # :white
71
+ # :on_black
72
+ # :on_red
73
+ # :on_green
74
+ # :on_yellow
75
+ # :on_blue
76
+ # :on_magenta
77
+ # :on_cyan
78
+ # :on_white
79
+ def set_color(string, *colors)
80
+ if colors.compact.empty? || !can_display_colors?
81
+ string
82
+ elsif colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) }
83
+ ansi_colors = colors.map { |color| lookup_color(color) }
84
+ "#{ansi_colors.join}#{string}#{CLEAR}"
85
+ else
86
+ # The old API was `set_color(color, bold=boolean)`. We
87
+ # continue to support the old API because you should never
88
+ # break old APIs unnecessarily :P
89
+ foreground, bold = colors
90
+ foreground = self.class.const_get(foreground.to_s.upcase) if foreground.is_a?(Symbol)
91
+
92
+ bold = bold ? BOLD : ''
93
+ "#{bold}#{foreground}#{string}#{CLEAR}"
94
+ end
95
+ end
96
+
97
+ protected
98
+
99
+ 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
117
+ end
118
+
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
131
+ end
132
+
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
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,126 @@
1
+ require 'peony/shell/basic'
2
+
3
+ module Peony
4
+ module Shell
5
+ # Inherit from Thor::Shell::Basic and add set_color behavior. Check
6
+ # Thor::Shell::Basic to see all available methods.
7
+ #
8
+ class HTML < Basic
9
+ # The start of an HTML bold sequence.
10
+ BOLD = 'font-weight: bold'
11
+
12
+ # Set the terminal's foreground HTML color to black.
13
+ BLACK = 'color: black'
14
+ # Set the terminal's foreground HTML color to red.
15
+ RED = 'color: red'
16
+ # Set the terminal's foreground HTML color to green.
17
+ GREEN = 'color: green'
18
+ # Set the terminal's foreground HTML color to yellow.
19
+ YELLOW = 'color: yellow'
20
+ # Set the terminal's foreground HTML color to blue.
21
+ BLUE = 'color: blue'
22
+ # Set the terminal's foreground HTML color to magenta.
23
+ MAGENTA = 'color: magenta'
24
+ # Set the terminal's foreground HTML color to cyan.
25
+ CYAN = 'color: cyan'
26
+ # Set the terminal's foreground HTML color to white.
27
+ WHITE = 'color: white'
28
+
29
+ # Set the terminal's background HTML color to black.
30
+ ON_BLACK = 'background-color: black'
31
+ # Set the terminal's background HTML color to red.
32
+ ON_RED = 'background-color: red'
33
+ # Set the terminal's background HTML color to green.
34
+ ON_GREEN = 'background-color: green'
35
+ # Set the terminal's background HTML color to yellow.
36
+ ON_YELLOW = 'background-color: yellow'
37
+ # Set the terminal's background HTML color to blue.
38
+ ON_BLUE = 'background-color: blue'
39
+ # Set the terminal's background HTML color to magenta.
40
+ ON_MAGENTA = 'background-color: magenta'
41
+ # Set the terminal's background HTML color to cyan.
42
+ ON_CYAN = 'background-color: cyan'
43
+ # Set the terminal's background HTML color to white.
44
+ ON_WHITE = 'background-color: white'
45
+
46
+ # Set color by using a string or one of the defined constants. If a third
47
+ # option is set to true, it also adds bold to the string. This is based
48
+ # on Highline implementation and it automatically appends CLEAR to the end
49
+ # of the returned String.
50
+ #
51
+ def set_color(string, *colors)
52
+ if colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) }
53
+ html_colors = colors.map { |color| lookup_color(color) }
54
+ "<span style=\"#{html_colors.join("; ")};\">#{string}</span>"
55
+ else
56
+ color, bold = colors
57
+ html_color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol)
58
+ styles = [html_color]
59
+ styles << BOLD if bold
60
+ "<span style=\"#{styles.join("; ")};\">#{string}</span>"
61
+ end
62
+ end
63
+
64
+ # Ask something to the user and receives a response.
65
+ #
66
+ # ==== Example
67
+ # ask("What is your name?")
68
+ #
69
+ # TODO: Implement #ask for Thor::Shell::HTML
70
+ def ask(statement, color = nil)
71
+ fail NotImplementedError, 'Implement #ask for Thor::Shell::HTML'
72
+ end
73
+
74
+ protected
75
+
76
+ def can_display_colors?
77
+ true
78
+ 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
+ end
125
+ end
126
+ end