tty-file 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +144 -75
- data/lib/tty-file.rb +1 -1
- data/lib/tty/file.rb +237 -155
- data/lib/tty/file/compare_files.rb +70 -0
- data/lib/tty/file/create_file.rb +28 -14
- data/lib/tty/file/differ.rb +40 -20
- data/lib/tty/file/download_file.rb +2 -2
- data/lib/tty/file/version.rb +1 -1
- metadata +10 -8
data/lib/tty-file.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require_relative
|
1
|
+
require_relative "tty/file"
|
data/lib/tty/file.rb
CHANGED
@@ -5,10 +5,10 @@ require "erb"
|
|
5
5
|
require "tempfile"
|
6
6
|
require "pathname"
|
7
7
|
|
8
|
+
require_relative "file/compare_files"
|
8
9
|
require_relative "file/create_file"
|
9
10
|
require_relative "file/digest_file"
|
10
11
|
require_relative "file/download_file"
|
11
|
-
require_relative "file/differ"
|
12
12
|
require_relative "file/read_backward_file"
|
13
13
|
require_relative "file/version"
|
14
14
|
|
@@ -56,7 +56,7 @@ module TTY
|
|
56
56
|
buffer = read_to_char(relative_path, bytes, 0)
|
57
57
|
|
58
58
|
begin
|
59
|
-
|
59
|
+
buffer !~ /\A[\s[[:print:]]]*\z/m
|
60
60
|
rescue ArgumentError => error
|
61
61
|
return true if error.message =~ /invalid byte sequence/
|
62
62
|
raise
|
@@ -98,9 +98,8 @@ module TTY
|
|
98
98
|
# @param [File, IO, String, Pathname] source
|
99
99
|
# the source to generate checksum for
|
100
100
|
# @param [String] mode
|
101
|
-
# @param [
|
102
|
-
#
|
103
|
-
# No operation
|
101
|
+
# @param [Boolean] noop
|
102
|
+
# when true skip this action
|
104
103
|
#
|
105
104
|
# @example
|
106
105
|
# checksum_file("/path/to/file")
|
@@ -112,21 +111,25 @@ module TTY
|
|
112
111
|
# the generated hex value
|
113
112
|
#
|
114
113
|
# @api public
|
115
|
-
def checksum_file(source, *args,
|
114
|
+
def checksum_file(source, *args, noop: false)
|
116
115
|
mode = args.size.zero? ? "sha256" : args.pop
|
117
116
|
digester = DigestFile.new(source, mode)
|
118
|
-
digester.call unless
|
117
|
+
digester.call unless noop
|
119
118
|
end
|
120
119
|
module_function :checksum_file
|
121
120
|
|
122
121
|
# Change file permissions
|
123
122
|
#
|
124
123
|
# @param [String, Pathname] relative_path
|
124
|
+
# the string or path to a file
|
125
125
|
# @param [Integer,String] permisssions
|
126
|
-
#
|
127
|
-
# @
|
128
|
-
#
|
129
|
-
# @
|
126
|
+
# the string or octal number for permissoins
|
127
|
+
# @param [Boolean] noop
|
128
|
+
# when true skips this action
|
129
|
+
# @param [Boolean] verbose
|
130
|
+
# when true displays logging information
|
131
|
+
# @param [Symbol] color
|
132
|
+
# the name for the color to format display message, :green by default
|
130
133
|
#
|
131
134
|
# @example
|
132
135
|
# chmod("Gemfile", 0755)
|
@@ -138,10 +141,9 @@ module TTY
|
|
138
141
|
# chmod("Gemfile", "u+x,g+x")
|
139
142
|
#
|
140
143
|
# @api public
|
141
|
-
def chmod(relative_path, permissions,
|
142
|
-
log_status(:chmod, relative_path,
|
143
|
-
|
144
|
-
::FileUtils.chmod_R(permissions, relative_path) unless options[:noop]
|
144
|
+
def chmod(relative_path, permissions, verbose: true, color: :green, noop: false)
|
145
|
+
log_status(:chmod, relative_path, verbose: verbose, color: color)
|
146
|
+
::FileUtils.chmod_R(permissions, relative_path) unless noop
|
145
147
|
end
|
146
148
|
module_function :chmod
|
147
149
|
|
@@ -149,6 +151,18 @@ module TTY
|
|
149
151
|
#
|
150
152
|
# @param [String, Pathname, Hash] destination
|
151
153
|
# the path or data structure describing directory tree
|
154
|
+
# @param [Object] context
|
155
|
+
# the context for template evaluation
|
156
|
+
# @param [Boolean] quiet
|
157
|
+
# when true leaves prompt output, otherwise clears
|
158
|
+
# @param [Boolean] force
|
159
|
+
# when true overwrites existing files, false by default
|
160
|
+
# @param [Boolean] noop
|
161
|
+
# when true skips this action
|
162
|
+
# @param [Boolean] verbose
|
163
|
+
# when true displays logging information
|
164
|
+
# @param [Symbol] color
|
165
|
+
# the name for the color to format display message, :green by default
|
152
166
|
#
|
153
167
|
# @example
|
154
168
|
# create_directory("/path/to/dir")
|
@@ -170,7 +184,9 @@ module TTY
|
|
170
184
|
# @return [void]
|
171
185
|
#
|
172
186
|
# @api public
|
173
|
-
def create_directory(destination, *args,
|
187
|
+
def create_directory(destination, *args, context: nil, verbose: true,
|
188
|
+
color: :green, noop: false, force: false, skip: false,
|
189
|
+
quiet: true)
|
174
190
|
parent = args.size.nonzero? ? args.pop : nil
|
175
191
|
if destination.is_a?(String) || destination.is_a?(Pathname)
|
176
192
|
destination = { destination.to_s => [] }
|
@@ -180,15 +196,18 @@ module TTY
|
|
180
196
|
path = parent.nil? ? dir : ::File.join(parent, dir)
|
181
197
|
unless ::File.exist?(path)
|
182
198
|
::FileUtils.mkdir_p(path)
|
183
|
-
log_status(:create, path,
|
184
|
-
options.fetch(:color, :green))
|
199
|
+
log_status(:create, path, verbose: verbose, color: color)
|
185
200
|
end
|
186
201
|
|
187
202
|
files.each do |filename, contents|
|
188
203
|
if filename.respond_to?(:each_pair)
|
189
|
-
create_directory(filename, path,
|
204
|
+
create_directory(filename, path, context: context,
|
205
|
+
verbose: verbose, color: color, noop: noop,
|
206
|
+
force: force, skip: skip, quiet: quiet)
|
190
207
|
else
|
191
|
-
create_file(::File.join(path, filename), contents,
|
208
|
+
create_file(::File.join(path, filename), contents, context: context,
|
209
|
+
verbose: verbose, color: color, noop: noop, force: force,
|
210
|
+
skip: skip, quiet: quiet)
|
192
211
|
end
|
193
212
|
end
|
194
213
|
end
|
@@ -203,9 +222,20 @@ module TTY
|
|
203
222
|
# @param [String, Pathname] relative_path
|
204
223
|
# @param [String|nil] content
|
205
224
|
# the content to add to file
|
206
|
-
# @param [
|
207
|
-
#
|
225
|
+
# @param [Object] context
|
226
|
+
# the binding to use for the template
|
227
|
+
# @param [Symbol] color
|
228
|
+
# the color name to use for logging
|
229
|
+
# @param [Boolean] force
|
208
230
|
# forces ovewrite if conflict present
|
231
|
+
# @param [Boolean] verbose
|
232
|
+
# when true log the action status to stdout
|
233
|
+
# @param [Boolean] noop
|
234
|
+
# when true do not execute the action
|
235
|
+
# @param [Boolean] skip
|
236
|
+
# when true skip the action
|
237
|
+
# @param [Boolean] quiet
|
238
|
+
# when true leaves prompt output, otherwise clears
|
209
239
|
#
|
210
240
|
# @example
|
211
241
|
# create_file("doc/README.md", "# Title header")
|
@@ -216,11 +246,14 @@ module TTY
|
|
216
246
|
# end
|
217
247
|
#
|
218
248
|
# @api public
|
219
|
-
def create_file(relative_path, *args,
|
249
|
+
def create_file(relative_path, *args, context: nil, force: false, skip: false,
|
250
|
+
verbose: true, color: :green, noop: false, quiet: true, &block)
|
220
251
|
relative_path = relative_path.to_s
|
221
252
|
content = block_given? ? block[] : args.join
|
222
253
|
|
223
|
-
CreateFile.new(self, relative_path, content,
|
254
|
+
CreateFile.new(self, relative_path, content, context: context, force: force,
|
255
|
+
skip: skip, verbose: verbose, color: color, noop: noop,
|
256
|
+
quiet: quiet).call
|
224
257
|
end
|
225
258
|
module_function :create_file
|
226
259
|
|
@@ -239,29 +272,33 @@ module TTY
|
|
239
272
|
# copy_file "templates/%name%.rb", "app/%name%.rb", context: vars
|
240
273
|
#
|
241
274
|
# @param [String, Pathname] source_path
|
242
|
-
#
|
243
|
-
# @
|
275
|
+
# the file path to copy file from
|
276
|
+
# @param [Object] context
|
244
277
|
# the binding to use for the template
|
245
|
-
# @
|
246
|
-
#
|
247
|
-
# are preserved on the copied file, defaults to false
|
248
|
-
# @
|
249
|
-
#
|
250
|
-
# @
|
251
|
-
#
|
278
|
+
# @param [Boolean] preserve
|
279
|
+
# when true, the owner, group, permissions and modified time
|
280
|
+
# are preserved on the copied file, defaults to false
|
281
|
+
# @param [Boolean] noop
|
282
|
+
# when true does not execute the action
|
283
|
+
# @param [Boolean] verbose
|
284
|
+
# when true log the action status to stdout
|
285
|
+
# @param [Symbol] color
|
286
|
+
# the color name to use for logging
|
252
287
|
#
|
253
288
|
# @api public
|
254
|
-
def copy_file(source_path, *args,
|
289
|
+
def copy_file(source_path, *args, context: nil, force: false, skip: false,
|
290
|
+
verbose: true, color: :green, noop: false, preserve: nil, &block)
|
255
291
|
source_path = source_path.to_s
|
256
292
|
dest_path = (args.first || source_path).to_s.sub(/\.erb$/, "")
|
257
293
|
|
258
|
-
ctx = if
|
259
|
-
|
294
|
+
ctx = if context
|
295
|
+
context.instance_eval("binding")
|
260
296
|
else
|
261
297
|
instance_eval("binding")
|
262
298
|
end
|
263
299
|
|
264
|
-
create_file(dest_path,
|
300
|
+
create_file(dest_path, context: context, force: force, skip: skip,
|
301
|
+
verbose: verbose, color: color, noop: noop) do
|
265
302
|
version = ERB.version.scan(/\d+\.\d+\.\d+/)[0]
|
266
303
|
template = if version.to_f >= 2.2
|
267
304
|
ERB.new(::File.binread(source_path), trim_mode: "-", eoutvar: "@output_buffer")
|
@@ -272,9 +309,10 @@ module TTY
|
|
272
309
|
content = block[content] if block
|
273
310
|
content
|
274
311
|
end
|
275
|
-
return unless
|
312
|
+
return unless preserve
|
276
313
|
|
277
|
-
copy_metadata(source_path, dest_path,
|
314
|
+
copy_metadata(source_path, dest_path, verbose: verbose, noop: noop,
|
315
|
+
color: color)
|
278
316
|
end
|
279
317
|
module_function :copy_file
|
280
318
|
|
@@ -314,38 +352,43 @@ module TTY
|
|
314
352
|
# command.rb
|
315
353
|
# README
|
316
354
|
#
|
317
|
-
# @param [String, Pathname] source_path
|
318
|
-
# @param [Hash[Symbol]] options
|
319
|
-
# @option options [Symbol] :preserve
|
320
|
-
# If true, the owner, group, permissions and modified time
|
321
|
-
# are preserved on the copied file, defaults to false.
|
322
|
-
# @option options [Symbol] :recursive
|
323
|
-
# If false, copies only top level files, defaults to true.
|
324
|
-
# @option options [Symbol] :exclude
|
325
|
-
# A regex that specifies files to ignore when copying.
|
326
|
-
#
|
327
355
|
# @example
|
328
356
|
# copy_directory("app", "new_app", recursive: false)
|
357
|
+
#
|
358
|
+
# @example
|
329
359
|
# copy_directory("app", "new_app", exclude: /docs/)
|
330
360
|
#
|
361
|
+
# @param [String, Pathname] source_path
|
362
|
+
# the source directory to copy files from
|
363
|
+
# @param [Boolean] preserve
|
364
|
+
# when true, the owner, group, permissions and modified time
|
365
|
+
# are preserved on the copied file, defaults to false.
|
366
|
+
# @param [Boolean] recursive
|
367
|
+
# when false, copies only top level files, defaults to true
|
368
|
+
# @param [Regexp] exclude
|
369
|
+
# a regex that specifies files to ignore when copying
|
370
|
+
#
|
331
371
|
# @api public
|
332
|
-
def copy_directory(source_path, *args,
|
372
|
+
def copy_directory(source_path, *args, context: nil, force: false, skip: false,
|
373
|
+
verbose: true, color: :green, noop: false, preserve: nil,
|
374
|
+
recursive: true, exclude: nil, &block)
|
333
375
|
source_path = source_path.to_s
|
334
376
|
check_path(source_path)
|
335
377
|
source = escape_glob_path(source_path)
|
336
378
|
dest_path = (args.first || source).to_s
|
337
|
-
|
338
|
-
pattern = opts[:recursive] ? ::File.join(source, "**") : source
|
379
|
+
pattern = recursive ? ::File.join(source, "**") : source
|
339
380
|
glob_pattern = ::File.join(pattern, "*")
|
340
381
|
|
341
382
|
Dir.glob(glob_pattern, ::File::FNM_DOTMATCH).sort.each do |file_source|
|
342
383
|
next if ::File.directory?(file_source)
|
343
|
-
next if
|
384
|
+
next if exclude && file_source.match(exclude)
|
344
385
|
|
345
386
|
dest = ::File.join(dest_path, file_source.gsub(source_path, "."))
|
346
387
|
file_dest = ::Pathname.new(dest).cleanpath.to_s
|
347
388
|
|
348
|
-
copy_file(file_source, file_dest,
|
389
|
+
copy_file(file_source, file_dest, context: context, force: force,
|
390
|
+
skip: skip, verbose: verbose, color: color, noop: noop,
|
391
|
+
preserve: preserve, &block)
|
349
392
|
end
|
350
393
|
end
|
351
394
|
module_function :copy_directory
|
@@ -356,57 +399,88 @@ module TTY
|
|
356
399
|
# Diff files line by line
|
357
400
|
#
|
358
401
|
# @param [String, Pathname] path_a
|
402
|
+
# the path to the original file
|
359
403
|
# @param [String, Pathname] path_b
|
360
|
-
#
|
361
|
-
# @
|
404
|
+
# the path to a new file
|
405
|
+
# @param [Symbol] format
|
362
406
|
# the diffining output format
|
363
|
-
# @
|
407
|
+
# @param [Intger] lines
|
364
408
|
# the number of extra lines for the context
|
365
|
-
# @
|
409
|
+
# @param [Integer] threshold
|
366
410
|
# maximum file size in bytes
|
367
411
|
#
|
368
412
|
# @example
|
369
413
|
# diff(file_a, file_b, format: :old)
|
370
414
|
#
|
371
415
|
# @api public
|
372
|
-
def diff(path_a, path_b,
|
373
|
-
|
374
|
-
|
416
|
+
def diff(path_a, path_b, threshold: 10_000_000, format: :unified, lines: 3,
|
417
|
+
header: true, verbose: true, color: :green, noop: false)
|
418
|
+
open_tempfile_if_missing(path_a) do |file_a, temp_a|
|
419
|
+
message = check_binary_or_large(file_a, threshold)
|
420
|
+
return message if message
|
375
421
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
end
|
380
|
-
if binary?(file_a)
|
381
|
-
raise ArgumentError, "(#{file_a.path} is binary, diff output suppressed)"
|
382
|
-
end
|
383
|
-
open_tempfile_if_missing(path_b) do |file_b|
|
384
|
-
if binary?(file_b)
|
385
|
-
raise ArgumentError, "(#{file_a.path} is binary, diff output suppressed)"
|
386
|
-
end
|
387
|
-
if ::File.size(file_b) > threshold
|
388
|
-
return "(file size of #{file_b.path} exceeds #{threshold} bytes, diff output suppressed)"
|
389
|
-
end
|
422
|
+
open_tempfile_if_missing(path_b) do |file_b, temp_b|
|
423
|
+
message = check_binary_or_large(file_b, threshold)
|
424
|
+
return message if message
|
390
425
|
|
391
|
-
|
392
|
-
|
393
|
-
return output.join if options[:noop]
|
426
|
+
file_a_path, file_b_path = *diff_paths(file_a, file_b, temp_a, temp_b)
|
427
|
+
.map { |path| ::File.join(*path) }
|
394
428
|
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
429
|
+
log_status(:diff, "#{file_a_path} and #{file_b_path}",
|
430
|
+
verbose: verbose, color: color)
|
431
|
+
|
432
|
+
return "" if noop
|
433
|
+
|
434
|
+
diff_files = CompareFiles.new(format: format, context_lines: lines,
|
435
|
+
header: header, verbose: verbose,
|
436
|
+
color: color, noop: noop,
|
437
|
+
diff_colors: diff_colors)
|
438
|
+
|
439
|
+
return diff_files.call(file_a, file_b, file_a_path, file_b_path)
|
401
440
|
end
|
402
441
|
end
|
403
|
-
output.join
|
404
442
|
end
|
405
443
|
module_function :diff
|
406
444
|
|
407
445
|
alias diff_files diff
|
408
446
|
module_function :diff_files
|
409
447
|
|
448
|
+
# @api private
|
449
|
+
def diff_paths(file_a, file_b, temp_a, temp_b)
|
450
|
+
if temp_a && !temp_b
|
451
|
+
[["a", file_b.path], ["b", file_b.path]]
|
452
|
+
elsif !temp_a && temp_b
|
453
|
+
[["a", file_a.path], ["b", file_a.path]]
|
454
|
+
elsif temp_a && temp_b
|
455
|
+
[["a"], ["b"]]
|
456
|
+
else
|
457
|
+
[file_a.path, file_b.path]
|
458
|
+
end
|
459
|
+
end
|
460
|
+
private_module_function :diff_paths
|
461
|
+
|
462
|
+
def diff_colors
|
463
|
+
{
|
464
|
+
green: @pastel.green.detach,
|
465
|
+
red: @pastel.red.detach,
|
466
|
+
cyan: @pastel.cyan.detach
|
467
|
+
}
|
468
|
+
end
|
469
|
+
private_module_function :diff_colors
|
470
|
+
|
471
|
+
# Check if file is binary or exceeds threshold size
|
472
|
+
#
|
473
|
+
# @api private
|
474
|
+
def check_binary_or_large(file, threshold)
|
475
|
+
if binary?(file)
|
476
|
+
"#{file.path} is binary, diff output suppressed"
|
477
|
+
elsif ::File.size(file) > threshold
|
478
|
+
"file size of #{file.path} exceeds #{threshold} bytes, " \
|
479
|
+
" diff output suppressed"
|
480
|
+
end
|
481
|
+
end
|
482
|
+
private_module_function :check_binary_or_large
|
483
|
+
|
410
484
|
# Download the content from a given address and
|
411
485
|
# save at the given relative destination. If block
|
412
486
|
# is provided in place of destination, the content of
|
@@ -416,9 +490,8 @@ module TTY
|
|
416
490
|
# the URI address
|
417
491
|
# @param [String, Pathname] dest
|
418
492
|
# the relative path to save
|
419
|
-
# @param [
|
420
|
-
#
|
421
|
-
# the limit of redirects
|
493
|
+
# @param [Integer] limit
|
494
|
+
# the number of maximium redirects
|
422
495
|
#
|
423
496
|
# @example
|
424
497
|
# download_file("https://gist.github.com/4701967",
|
@@ -439,7 +512,7 @@ module TTY
|
|
439
512
|
return
|
440
513
|
end
|
441
514
|
|
442
|
-
content = DownloadFile.new(uri, dest_path, options).call
|
515
|
+
content = DownloadFile.new(uri, dest_path, limit: options[:limit]).call
|
443
516
|
|
444
517
|
if block_given?
|
445
518
|
content = (block.arity.nonzero? ? block[content] : block[])
|
@@ -467,11 +540,11 @@ module TTY
|
|
467
540
|
# end
|
468
541
|
#
|
469
542
|
# @api public
|
470
|
-
def prepend_to_file(relative_path, *args,
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
543
|
+
def prepend_to_file(relative_path, *args, verbose: true, color: :green,
|
544
|
+
force: true, noop: false, &block)
|
545
|
+
log_status(:prepend, relative_path, verbose: verbose, color: color)
|
546
|
+
inject_into_file(relative_path, *args, before: /\A/, verbose: false,
|
547
|
+
color: color, force: force, noop: noop, &block)
|
475
548
|
end
|
476
549
|
module_function :prepend_to_file
|
477
550
|
|
@@ -498,11 +571,11 @@ module TTY
|
|
498
571
|
# end
|
499
572
|
#
|
500
573
|
# @api public
|
501
|
-
def append_to_file(relative_path, *args,
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
574
|
+
def append_to_file(relative_path, *args, verbose: true, color: :green,
|
575
|
+
force: true, noop: false, &block)
|
576
|
+
log_status(:append, relative_path, verbose: verbose, color: color)
|
577
|
+
inject_into_file(relative_path, *args, after: /\z/, verbose: false,
|
578
|
+
force: force, noop: noop, color: color, &block)
|
506
579
|
end
|
507
580
|
module_function :append_to_file
|
508
581
|
|
@@ -520,16 +593,18 @@ module TTY
|
|
520
593
|
# Inject content into file at a given location
|
521
594
|
#
|
522
595
|
# @param [String, Pathname] relative_path
|
523
|
-
#
|
524
|
-
# @param [Hash] options
|
525
|
-
# @option options [Symbol] :before
|
596
|
+
# @param [String] before
|
526
597
|
# the matching line to insert content before
|
527
|
-
# @
|
598
|
+
# @param [String] after
|
528
599
|
# the matching line to insert content after
|
529
|
-
# @
|
600
|
+
# @param [Boolean] force
|
530
601
|
# insert content more than once
|
531
|
-
# @
|
532
|
-
# log status
|
602
|
+
# @param [Boolean] verbose
|
603
|
+
# when true log status
|
604
|
+
# @param [Symbol] color
|
605
|
+
# the color name used in displaying this action
|
606
|
+
# @param [Boolean] noop
|
607
|
+
# when true skip perfomring this action
|
533
608
|
#
|
534
609
|
# @example
|
535
610
|
# inject_into_file("Gemfile", "gem 'tty'", after: "gem 'rack'\n")
|
@@ -543,15 +618,12 @@ module TTY
|
|
543
618
|
# end
|
544
619
|
#
|
545
620
|
# @api public
|
546
|
-
def inject_into_file(relative_path, *args,
|
621
|
+
def inject_into_file(relative_path, *args, verbose: true, color: :green,
|
622
|
+
after: nil, before: nil, force: true, noop: false, &block)
|
547
623
|
check_path(relative_path)
|
548
624
|
replacement = block_given? ? block[] : args.join
|
549
625
|
|
550
|
-
flag, match =
|
551
|
-
[:after, options.delete(:after)]
|
552
|
-
else
|
553
|
-
[:before, options.delete(:before)]
|
554
|
-
end
|
626
|
+
flag, match = after ? [:after, after] : [:before, before]
|
555
627
|
|
556
628
|
match = match.is_a?(Regexp) ? match : Regexp.escape(match)
|
557
629
|
content = if flag == :after
|
@@ -560,10 +632,9 @@ module TTY
|
|
560
632
|
replacement + '\0'
|
561
633
|
end
|
562
634
|
|
563
|
-
log_status(:inject, relative_path,
|
564
|
-
|
565
|
-
|
566
|
-
**options.merge(verbose: false))
|
635
|
+
log_status(:inject, relative_path, verbose: verbose, color: color)
|
636
|
+
replace_in_file(relative_path, /#{match}/, content, verbose: false,
|
637
|
+
color: color, force: force, noop: noop)
|
567
638
|
end
|
568
639
|
module_function :inject_into_file
|
569
640
|
|
@@ -582,11 +653,14 @@ module TTY
|
|
582
653
|
# when no substitutions were performed, true otherwise.
|
583
654
|
#
|
584
655
|
# @param [String, Pathname] relative_path
|
585
|
-
# @
|
586
|
-
# @option options [Symbol] :force
|
656
|
+
# @param [Boolean] force
|
587
657
|
# replace content even if present
|
588
|
-
# @
|
589
|
-
# log status
|
658
|
+
# @param [Boolean] verbose
|
659
|
+
# when true log status to stdout
|
660
|
+
# @param [Boolean] noop
|
661
|
+
# when true skip executing this action
|
662
|
+
# @param [Symbol] color
|
663
|
+
# the name of the color used for displaying action
|
590
664
|
#
|
591
665
|
# @example
|
592
666
|
# replace_in_file("Gemfile", /gem 'rails'/, "gem 'hanami'")
|
@@ -600,18 +674,18 @@ module TTY
|
|
600
674
|
# true when replaced content, false otherwise
|
601
675
|
#
|
602
676
|
# @api public
|
603
|
-
def replace_in_file(relative_path, *args,
|
677
|
+
def replace_in_file(relative_path, *args, verbose: true, color: :green,
|
678
|
+
noop: false, force: true, &block)
|
604
679
|
check_path(relative_path)
|
605
680
|
contents = ::File.read(relative_path)
|
606
681
|
replacement = (block ? block[] : args[1..-1].join).gsub('\0', "")
|
607
682
|
match = Regexp.escape(replacement)
|
608
683
|
status = nil
|
609
684
|
|
610
|
-
log_status(:replace, relative_path,
|
611
|
-
|
612
|
-
return false if options[:noop]
|
685
|
+
log_status(:replace, relative_path, verbose: verbose, color: color)
|
686
|
+
return false if noop
|
613
687
|
|
614
|
-
if
|
688
|
+
if force || !(contents =~ /^#{match}(\r?\n)*/m)
|
615
689
|
status = contents.gsub!(*args, &block)
|
616
690
|
if !status.nil?
|
617
691
|
::File.open(relative_path, "w") do |file|
|
@@ -629,29 +703,27 @@ module TTY
|
|
629
703
|
# Remove a file or a directory at specified relative path.
|
630
704
|
#
|
631
705
|
# @param [String, Pathname] relative_path
|
632
|
-
# @param [
|
633
|
-
#
|
634
|
-
#
|
635
|
-
#
|
636
|
-
#
|
637
|
-
#
|
638
|
-
#
|
639
|
-
#
|
640
|
-
# for secure removing
|
706
|
+
# @param [Boolean] noop
|
707
|
+
# when true pretend to remove file
|
708
|
+
# @param [Boolean] force
|
709
|
+
# when true remove file ignoring errors
|
710
|
+
# @param [Boolean] verbose
|
711
|
+
# when true log status
|
712
|
+
# @param [Boolean] secure
|
713
|
+
# when true check for secure removing
|
641
714
|
#
|
642
715
|
# @example
|
643
716
|
# remove_file "doc/README.md"
|
644
717
|
#
|
645
718
|
# @api public
|
646
|
-
def remove_file(relative_path, *args,
|
719
|
+
def remove_file(relative_path, *args, verbose: true, color: :red, noop: false,
|
720
|
+
force: nil, secure: true)
|
647
721
|
relative_path = relative_path.to_s
|
648
|
-
log_status(:remove, relative_path,
|
649
|
-
options.fetch(:color, :red))
|
722
|
+
log_status(:remove, relative_path, verbose: verbose, color: color)
|
650
723
|
|
651
|
-
return if
|
724
|
+
return if noop || !::File.exist?(relative_path)
|
652
725
|
|
653
|
-
::FileUtils.rm_r(relative_path, force:
|
654
|
-
secure: options.fetch(:secure, true))
|
726
|
+
::FileUtils.rm_r(relative_path, force: force, secure: secure)
|
655
727
|
end
|
656
728
|
module_function :remove_file
|
657
729
|
|
@@ -659,44 +731,44 @@ module TTY
|
|
659
731
|
#
|
660
732
|
# @param [String, Pathname] relative_path
|
661
733
|
# the relative path to a file
|
662
|
-
#
|
663
|
-
# @param [Integer] num_lines
|
734
|
+
# @param [Integer] lines
|
664
735
|
# the number of lines to return from file
|
736
|
+
# @param [Integer] chunk_size
|
737
|
+
# the size of the chunk to read
|
665
738
|
#
|
666
739
|
# @example
|
667
740
|
# tail_file "filename"
|
668
741
|
# # => ["line 19", "line20", ... ]
|
669
742
|
#
|
670
743
|
# @example
|
671
|
-
# tail_file "filename", 15
|
744
|
+
# tail_file "filename", lines: 15
|
672
745
|
# # => ["line 19", "line20", ... ]
|
673
746
|
#
|
674
747
|
# @return [Array[String]]
|
675
748
|
#
|
676
749
|
# @api public
|
677
|
-
def tail_file(relative_path,
|
678
|
-
file
|
679
|
-
|
680
|
-
|
681
|
-
lines = []
|
750
|
+
def tail_file(relative_path, lines: 10, chunk_size: 512, &block)
|
751
|
+
file = ::File.open(relative_path)
|
752
|
+
line_sep = $/
|
753
|
+
output = []
|
682
754
|
newline_count = 0
|
683
755
|
|
684
756
|
ReadBackwardFile.new(file, chunk_size).each_chunk do |chunk|
|
685
757
|
# look for newline index counting from right of chunk
|
686
758
|
while (nl_index = chunk.rindex(line_sep, (nl_index || chunk.size) - 1))
|
687
759
|
newline_count += 1
|
688
|
-
break if newline_count >
|
760
|
+
break if newline_count > lines || nl_index.zero?
|
689
761
|
end
|
690
762
|
|
691
|
-
if newline_count >
|
692
|
-
|
763
|
+
if newline_count > lines
|
764
|
+
output.insert(0, chunk[(nl_index + 1)..-1])
|
693
765
|
break
|
694
766
|
else
|
695
|
-
|
767
|
+
output.insert(0, chunk)
|
696
768
|
end
|
697
769
|
end
|
698
770
|
|
699
|
-
|
771
|
+
output.join.split(line_sep).each(&block).to_a
|
700
772
|
end
|
701
773
|
module_function :tail_file
|
702
774
|
|
@@ -741,7 +813,7 @@ module TTY
|
|
741
813
|
# Log file operation
|
742
814
|
#
|
743
815
|
# @api private
|
744
|
-
def log_status(cmd, message, verbose, color
|
816
|
+
def log_status(cmd, message, verbose: true, color: false)
|
745
817
|
return unless verbose
|
746
818
|
|
747
819
|
cmd = cmd.to_s.rjust(12)
|
@@ -773,7 +845,7 @@ module TTY
|
|
773
845
|
tempfile << object
|
774
846
|
tempfile.rewind
|
775
847
|
|
776
|
-
block[tempfile]
|
848
|
+
block[tempfile, ::File.basename(tempfile)]
|
777
849
|
|
778
850
|
unless tempfile.nil?
|
779
851
|
tempfile.close
|
@@ -782,5 +854,15 @@ module TTY
|
|
782
854
|
end
|
783
855
|
end
|
784
856
|
private_module_function :open_tempfile_if_missing
|
857
|
+
|
858
|
+
# Check if IO is attached to a terminal
|
859
|
+
#
|
860
|
+
# return [Boolean]
|
861
|
+
#
|
862
|
+
# @api public
|
863
|
+
def tty?
|
864
|
+
@output.respond_to?(:tty?) && @output.tty?
|
865
|
+
end
|
866
|
+
private_module_function :tty?
|
785
867
|
end # File
|
786
868
|
end # TTY
|