tty-file 0.9.0 → 0.10.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.
@@ -1 +1 @@
1
- require_relative 'tty/file'
1
+ require_relative "tty/file"
@@ -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
- return buffer !~ /\A[\s[[:print:]]]*\z/m
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 [Hash[Symbol]] options
102
- # @option options [String] :noop
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, **options)
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 options[:noop]
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
- # @param [Hash[Symbol]] options
127
- # @option options [Symbol] :noop
128
- # @option options [Symbol] :verbose
129
- # @option options [Symbol] :force
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, **options)
142
- log_status(:chmod, relative_path, options.fetch(:verbose, true),
143
- options.fetch(:color, :green))
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, **options)
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, options.fetch(:verbose, true),
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, **options)
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, **options)
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 [Hash] options
207
- # @option options [Symbol] :force
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, **options, &block)
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, options).call
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
- # @param [Hash] options
243
- # @option options [Symbol] :context
275
+ # the file path to copy file from
276
+ # @param [Object] context
244
277
  # the binding to use for the template
245
- # @option options [Symbol] :preserve
246
- # If true, the owner, group, permissions and modified time
247
- # are preserved on the copied file, defaults to false.
248
- # @option options [Symbol] :noop
249
- # If true do not execute the action.
250
- # @option options [Symbol] :verbose
251
- # If true log the action status to stdout
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, **options, &block)
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 (vars = options[:context])
259
- vars.instance_eval("binding")
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, **options) do
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 options[:preserve]
312
+ return unless preserve
276
313
 
277
- copy_metadata(source_path, dest_path, **options)
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, **options, &block)
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
- opts = { recursive: true }.merge(options)
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 opts[:exclude] && file_source.match(opts[:exclude])
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, **options, &block)
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
- # @param [Hash[Symbol]] options
361
- # @option options [Symbol] :format
404
+ # the path to a new file
405
+ # @param [Symbol] format
362
406
  # the diffining output format
363
- # @option options [Symbol] :context_lines
407
+ # @param [Intger] lines
364
408
  # the number of extra lines for the context
365
- # @option options [Symbol] :threshold
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, **options)
373
- threshold = options[:threshold] || 10_000_000
374
- output = []
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
- open_tempfile_if_missing(path_a) do |file_a|
377
- if ::File.size(file_a) > threshold
378
- raise ArgumentError, "(file size of #{file_a.path} exceeds #{threshold} bytes, diff output suppressed)"
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
- log_status(:diff, "#{file_a.path} - #{file_b.path}",
392
- options.fetch(:verbose, true), options.fetch(:color, :green))
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
- block_size = file_a.lstat.blksize
396
- while !file_a.eof? && !file_b.eof?
397
- output << Differ.new(file_a.read(block_size),
398
- file_b.read(block_size),
399
- options).call
400
- end
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 [Hash[Symbol]] options
420
- # @param options [Symbol] :limit
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, **options, &block)
471
- log_status(:prepend, relative_path, options.fetch(:verbose, true),
472
- options.fetch(:color, :green))
473
- options.merge!(before: /\A/, verbose: false)
474
- inject_into_file(relative_path, *args, **options, &block)
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, **options, &block)
502
- log_status(:append, relative_path, options.fetch(:verbose, true),
503
- options.fetch(:color, :green))
504
- options.merge!(after: /\z/, verbose: false)
505
- inject_into_file(relative_path, *args, **options, &block)
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
- # @option options [Symbol] :after
598
+ # @param [String] after
528
599
  # the matching line to insert content after
529
- # @option options [Symbol] :force
600
+ # @param [Boolean] force
530
601
  # insert content more than once
531
- # @option options [Symbol] :verbose
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, **options, &block)
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 = if options.key?(:after)
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, options.fetch(:verbose, true),
564
- options.fetch(:color, :green))
565
- replace_in_file(relative_path, /#{match}/, content,
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
- # @options [Hash[String]] options
586
- # @option options [Symbol] :force
656
+ # @param [Boolean] force
587
657
  # replace content even if present
588
- # @option options [Symbol] :verbose
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, **options, &block)
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, options.fetch(:verbose, true),
611
- options.fetch(:color, :green))
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 options.fetch(:force, true) || !(contents =~ /^#{match}(\r?\n)*/m)
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 [Hash[:Symbol]] options
633
- # @option options [Symbol] :noop
634
- # pretend removing file
635
- # @option options [Symbol] :force
636
- # remove file ignoring errors
637
- # @option options [Symbol] :verbose
638
- # log status
639
- # @option options [Symbol] :secure
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, **options)
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, options.fetch(:verbose, true),
649
- options.fetch(:color, :red))
722
+ log_status(:remove, relative_path, verbose: verbose, color: color)
650
723
 
651
- return if options[:noop] || !::File.exist?(relative_path)
724
+ return if noop || !::File.exist?(relative_path)
652
725
 
653
- ::FileUtils.rm_r(relative_path, force: options[: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, num_lines = 10, **options, &block)
678
- file = ::File.open(relative_path)
679
- chunk_size = options.fetch(:chunk_size, 512)
680
- line_sep = $/
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 > num_lines || nl_index.zero?
760
+ break if newline_count > lines || nl_index.zero?
689
761
  end
690
762
 
691
- if newline_count > num_lines
692
- lines.insert(0, chunk[(nl_index + 1)..-1])
763
+ if newline_count > lines
764
+ output.insert(0, chunk[(nl_index + 1)..-1])
693
765
  break
694
766
  else
695
- lines.insert(0, chunk)
767
+ output.insert(0, chunk)
696
768
  end
697
769
  end
698
770
 
699
- lines.join.split(line_sep).each(&block).to_a
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 = false)
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