Module: TTY::File

Defined in:
lib/tty/file.rb,
lib/tty/file/differ.rb,
lib/tty/file/version.rb,
lib/tty/file/create_file.rb,
lib/tty/file/digest_file.rb,
lib/tty/file/compare_files.rb,
lib/tty/file/download_file.rb

Defined Under Namespace

Classes: CompareFiles, CreateFile, Differ, DigestFile, DownloadFile

Constant Summary collapse

InvalidPathError =

Invalid path erorr

Class.new(ArgumentError)
U_R =

File permissions

0400
U_W =
0200
U_X =
0100
G_R =
0040
G_W =
0020
G_X =
0010
O_R =
0004
O_W =
0002
O_X =
0001
A_R =
0444
A_W =
0222
A_X =
0111
VERSION =
"0.10.0"
DownloadError =
Class.new(StandardError)

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.add_fileObject

Create new file if doesn't exist

Examples:

create_file("doc/README.md", "# Title header")
create_file "doc/README.md" do
  "# Title Header"
end

Parameters:

  • relative_path (String, Pathname)
  • content (String|nil)

    the content to add to file

  • context (Object)

    the binding to use for the template

  • color (Symbol)

    the color name to use for logging

  • force (Boolean)

    forces ovewrite if conflict present

  • verbose (Boolean)

    when true log the action status to stdout

  • noop (Boolean)

    when true do not execute the action

  • skip (Boolean)

    when true skip the action

  • quiet (Boolean)

    when true leaves prompt output, otherwise clears


260
261
262
263
264
265
266
267
268
# File 'lib/tty/file.rb', line 260

def create_file(relative_path, *args, context: nil, force: false, skip: false,
                verbose: true, color: :green, noop: false, quiet: true, &block)
  relative_path = relative_path.to_s
  content = block_given? ? block[] : args.join

  CreateFile.new(self, relative_path, content, context: context, force: force,
                 skip: skip, verbose: verbose, color: color, noop: noop,
                 quiet: quiet).call
end

.add_to_fileObject

Append to a file

Examples:

append_to_file("Gemfile", "gem 'tty'")
append_to_file("Gemfile") do
  "gem 'tty'"
end

Parameters:

  • relative_path (String, Pathname)
  • content (Array[String])

    the content to append to file


582
583
584
585
586
587
# File 'lib/tty/file.rb', line 582

def append_to_file(relative_path, *args, verbose: true, color: :green,
                   force: true, noop: false, &block)
  log_status(:append, relative_path, verbose: verbose, color: color)
  inject_into_file(relative_path, *args, after: /\z/, verbose: false,
                   force: force, noop: noop, color: color, &block)
end

.append_to_file(relative_path, *args, verbose: true, color: :green, force: true, noop: false, &block) ⇒ Object

Append to a file

Examples:

append_to_file("Gemfile", "gem 'tty'")
append_to_file("Gemfile") do
  "gem 'tty'"
end

Parameters:

  • relative_path (String, Pathname)
  • content (Array[String])

    the content to append to file


574
575
576
577
578
579
# File 'lib/tty/file.rb', line 574

def append_to_file(relative_path, *args, verbose: true, color: :green,
                   force: true, noop: false, &block)
  log_status(:append, relative_path, verbose: verbose, color: color)
  inject_into_file(relative_path, *args, after: /\z/, verbose: false,
                   force: force, noop: noop, color: color, &block)
end

.binary?(relative_path) ⇒ Boolean

Check if file is binary

Examples:

binary?("Gemfile") # => false
binary?("image.jpg") # => true

Parameters:

  • relative_path (String, Pathname)

    the path to file to check

Returns:

  • (Boolean)

    Returns `true` if the file is binary, `false` otherwise


53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/tty/file.rb', line 53

def binary?(relative_path)
  bytes = ::File.new(relative_path).size
  bytes = 2**12 if bytes > 2**12
  buffer = read_to_char(relative_path, bytes, 0)

  begin
    buffer !~ /\A[\s[[:print:]]]*\z/m
  rescue ArgumentError => error
    return true if error.message =~ /invalid byte sequence/
    raise
  end
end

.checksum_file(source, *args, noop: false) ⇒ String

Create checksum for a file, io or string objects

Examples:

checksum_file("/path/to/file")
checksum_file("Some string content", "md5")

Parameters:

  • source (File, IO, String, Pathname)

    the source to generate checksum for

  • mode (String)
  • noop (Boolean) (defaults to: false)

    when true skip this action

Returns:

  • (String)

    the generated hex value


114
115
116
117
118
# File 'lib/tty/file.rb', line 114

def checksum_file(source, *args, noop: false)
  mode     = args.size.zero? ? "sha256" : args.pop
  digester = DigestFile.new(source, mode)
  digester.call unless noop
end

.chmod(relative_path, permissions, verbose: true, color: :green, noop: false) ⇒ Object

Change file permissions

Examples:

chmod("Gemfile", 0755)
chmod("Gemilfe", TTY::File::U_R | TTY::File::U_W)
chmod("Gemfile", "u+x,g+x")

Parameters:

  • relative_path (String, Pathname)

    the string or path to a file

  • permisssions (Integer, String)

    the string or octal number for permissoins

  • noop (Boolean) (defaults to: false)

    when true skips this action

  • verbose (Boolean) (defaults to: true)

    when true displays logging information

  • color (Symbol) (defaults to: :green)

    the name for the color to format display message, :green by default


144
145
146
147
# File 'lib/tty/file.rb', line 144

def chmod(relative_path, permissions, verbose: true, color: :green, noop: false)
  log_status(:chmod, relative_path, verbose: verbose, color: color)
  ::FileUtils.chmod_R(permissions, relative_path) unless noop
end

.copy_dirObject

Copy directory recursively from source to destination path

Any files names wrapped within % sign will be expanded by executing corresponding method and inserting its value. Assuming the following directory structure:

app/
  %name%.rb
  command.rb.erb
  README.md

Invoking:
  copy_directory("app", "new_app")
The following directory structure should be created where
name resolves to "cli" value:

new_app/
  cli.rb
  command.rb
  README

Examples:

copy_directory("app", "new_app", recursive: false)
copy_directory("app", "new_app", exclude: /docs/)

Parameters:

  • source_path (String, Pathname)

    the source directory to copy files from

  • preserve (Boolean)

    when true, the owner, group, permissions and modified time are preserved on the copied file, defaults to false.

  • recursive (Boolean)

    when false, copies only top level files, defaults to true

  • exclude (Regexp)

    a regex that specifies files to ignore when copying


396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/tty/file.rb', line 396

def copy_directory(source_path, *args, context: nil, force: false, skip: false,
                   verbose: true, color: :green, noop: false, preserve: nil,
                   recursive: true, exclude: nil, &block)
  source_path = source_path.to_s
  check_path(source_path)
  source = escape_glob_path(source_path)
  dest_path = (args.first || source).to_s
  pattern = recursive ? ::File.join(source, "**") : source
  glob_pattern = ::File.join(pattern, "*")

  Dir.glob(glob_pattern, ::File::FNM_DOTMATCH).sort.each do |file_source|
    next if ::File.directory?(file_source)
    next if exclude && file_source.match(exclude)

    dest = ::File.join(dest_path, file_source.gsub(source_path, "."))
    file_dest = ::Pathname.new(dest).cleanpath.to_s

    copy_file(file_source, file_dest, context: context, force: force,
              skip: skip, verbose: verbose, color: color, noop: noop,
              preserve: preserve, &block)
  end
end

.copy_directory(source_path, *args, context: nil, force: false, skip: false, verbose: true, color: :green, noop: false, preserve: nil, recursive: true, exclude: nil, &block) ⇒ Object

Copy directory recursively from source to destination path

Any files names wrapped within % sign will be expanded by executing corresponding method and inserting its value. Assuming the following directory structure:

app/
  %name%.rb
  command.rb.erb
  README.md

Invoking:
  copy_directory("app", "new_app")
The following directory structure should be created where
name resolves to "cli" value:

new_app/
  cli.rb
  command.rb
  README

Examples:

copy_directory("app", "new_app", recursive: false)
copy_directory("app", "new_app", exclude: /docs/)

Parameters:

  • source_path (String, Pathname)

    the source directory to copy files from

  • preserve (Boolean) (defaults to: nil)

    when true, the owner, group, permissions and modified time are preserved on the copied file, defaults to false.

  • recursive (Boolean) (defaults to: true)

    when false, copies only top level files, defaults to true

  • exclude (Regexp) (defaults to: nil)

    a regex that specifies files to ignore when copying


372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/tty/file.rb', line 372

def copy_directory(source_path, *args, context: nil, force: false, skip: false,
                   verbose: true, color: :green, noop: false, preserve: nil,
                   recursive: true, exclude: nil, &block)
  source_path = source_path.to_s
  check_path(source_path)
  source = escape_glob_path(source_path)
  dest_path = (args.first || source).to_s
  pattern = recursive ? ::File.join(source, "**") : source
  glob_pattern = ::File.join(pattern, "*")

  Dir.glob(glob_pattern, ::File::FNM_DOTMATCH).sort.each do |file_source|
    next if ::File.directory?(file_source)
    next if exclude && file_source.match(exclude)

    dest = ::File.join(dest_path, file_source.gsub(source_path, "."))
    file_dest = ::Pathname.new(dest).cleanpath.to_s

    copy_file(file_source, file_dest, context: context, force: force,
              skip: skip, verbose: verbose, color: color, noop: noop,
              preserve: preserve, &block)
  end
end

.copy_file(source_path, *args, context: nil, force: false, skip: false, verbose: true, color: :green, noop: false, preserve: nil, &block) ⇒ Object

Copy file from the relative source to the relative destination running it through ERB.

Examples:

copy_file "templates/test.rb", "app/test.rb"
vars = OpenStruct.new
vars[:name] = "foo"
copy_file "templates/%name%.rb", "app/%name%.rb", context: vars

Parameters:

  • source_path (String, Pathname)

    the file path to copy file from

  • context (Object) (defaults to: nil)

    the binding to use for the template

  • preserve (Boolean) (defaults to: nil)

    when true, the owner, group, permissions and modified time are preserved on the copied file, defaults to false

  • noop (Boolean) (defaults to: false)

    when true does not execute the action

  • verbose (Boolean) (defaults to: true)

    when true log the action status to stdout

  • color (Symbol) (defaults to: :green)

    the color name to use for logging


289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/tty/file.rb', line 289

def copy_file(source_path, *args, context: nil, force: false, skip: false,
              verbose: true, color: :green, noop: false, preserve: nil, &block)
  source_path = source_path.to_s
  dest_path = (args.first || source_path).to_s.sub(/\.erb$/, "")

  ctx = if context
          context.instance_eval("binding")
        else
          instance_eval("binding")
        end

  create_file(dest_path, context: context, force: force, skip: skip,
              verbose: verbose, color: color, noop: noop) do
    version = ERB.version.scan(/\d+\.\d+\.\d+/)[0]
    template = if version.to_f >= 2.2
                ERB.new(::File.binread(source_path), trim_mode: "-", eoutvar: "@output_buffer")
               else
                ERB.new(::File.binread(source_path), nil, "-", "@output_buffer")
               end
    content = template.result(ctx)
    content = block[content] if block
    content
  end
  return unless preserve

  (source_path, dest_path, verbose: verbose, noop: noop,
                color: color)
end

.copy_metadata(src_path, dest_path, **options) ⇒ Object

Copy file metadata

Parameters:

  • src_path (String)

    the source file path

  • dest_path (String)

    the destination file path


327
328
329
330
331
# File 'lib/tty/file.rb', line 327

def (src_path, dest_path, **options)
  stats = ::File.lstat(src_path)
  ::File.utime(stats.atime, stats.mtime, dest_path)
  chmod(dest_path, stats.mode, **options)
end

.create_dirvoid

This method returns an undefined value.

Create directory structure

Examples:

create_directory("/path/to/dir")
tree =
  "app" => [
    "README.md",
    ["Gemfile", "gem "tty-file""],
    "lib" => [
      "cli.rb",
      ["file_utils.rb", "require "tty-file""]
    ]
    "spec" => []
  ]

create_directory(tree)

Parameters:

  • destination (String, Pathname, Hash)

    the path or data structure describing directory tree

  • context (Object)

    the context for template evaluation

  • quiet (Boolean)

    when true leaves prompt output, otherwise clears

  • force (Boolean)

    when true overwrites existing files, false by default

  • noop (Boolean)

    when true skips this action

  • verbose (Boolean)

    when true displays logging information

  • color (Symbol)

    the name for the color to format display message, :green by default


217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/tty/file.rb', line 217

def create_directory(destination, *args, context: nil, verbose: true,
                     color: :green, noop: false, force: false, skip: false,
                     quiet: true)
  parent = args.size.nonzero? ? args.pop : nil
  if destination.is_a?(String) || destination.is_a?(Pathname)
    destination = { destination.to_s => [] }
  end

  destination.each do |dir, files|
    path = parent.nil? ? dir : ::File.join(parent, dir)
    unless ::File.exist?(path)
      ::FileUtils.mkdir_p(path)
      log_status(:create, path, verbose: verbose, color: color)
    end

    files.each do |filename, contents|
      if filename.respond_to?(:each_pair)
        create_directory(filename, path, context: context,
                         verbose: verbose, color: color, noop: noop,
                         force: force, skip: skip, quiet: quiet)
      else
        create_file(::File.join(path, filename), contents, context: context,
                    verbose: verbose, color: color, noop: noop, force: force,
                    skip: skip, quiet: quiet)
      end
    end
  end
end

.create_directory(destination, *args, context: nil, verbose: true, color: :green, noop: false, force: false, skip: false, quiet: true) ⇒ void

This method returns an undefined value.

Create directory structure

Examples:

create_directory("/path/to/dir")
tree =
  "app" => [
    "README.md",
    ["Gemfile", "gem "tty-file""],
    "lib" => [
      "cli.rb",
      ["file_utils.rb", "require "tty-file""]
    ]
    "spec" => []
  ]

create_directory(tree)

Parameters:

  • destination (String, Pathname, Hash)

    the path or data structure describing directory tree

  • context (Object) (defaults to: nil)

    the context for template evaluation

  • quiet (Boolean) (defaults to: true)

    when true leaves prompt output, otherwise clears

  • force (Boolean) (defaults to: false)

    when true overwrites existing files, false by default

  • noop (Boolean) (defaults to: false)

    when true skips this action

  • verbose (Boolean) (defaults to: true)

    when true displays logging information

  • color (Symbol) (defaults to: :green)

    the name for the color to format display message, :green by default


187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/tty/file.rb', line 187

def create_directory(destination, *args, context: nil, verbose: true,
                     color: :green, noop: false, force: false, skip: false,
                     quiet: true)
  parent = args.size.nonzero? ? args.pop : nil
  if destination.is_a?(String) || destination.is_a?(Pathname)
    destination = { destination.to_s => [] }
  end

  destination.each do |dir, files|
    path = parent.nil? ? dir : ::File.join(parent, dir)
    unless ::File.exist?(path)
      ::FileUtils.mkdir_p(path)
      log_status(:create, path, verbose: verbose, color: color)
    end

    files.each do |filename, contents|
      if filename.respond_to?(:each_pair)
        create_directory(filename, path, context: context,
                         verbose: verbose, color: color, noop: noop,
                         force: force, skip: skip, quiet: quiet)
      else
        create_file(::File.join(path, filename), contents, context: context,
                    verbose: verbose, color: color, noop: noop, force: force,
                    skip: skip, quiet: quiet)
      end
    end
  end
end

.create_file(relative_path, *args, context: nil, force: false, skip: false, verbose: true, color: :green, noop: false, quiet: true, &block) ⇒ Object

Create new file if doesn't exist

Examples:

create_file("doc/README.md", "# Title header")
create_file "doc/README.md" do
  "# Title Header"
end

Parameters:

  • relative_path (String, Pathname)
  • content (String|nil)

    the content to add to file

  • context (Object) (defaults to: nil)

    the binding to use for the template

  • color (Symbol) (defaults to: :green)

    the color name to use for logging

  • force (Boolean) (defaults to: false)

    forces ovewrite if conflict present

  • verbose (Boolean) (defaults to: true)

    when true log the action status to stdout

  • noop (Boolean) (defaults to: false)

    when true do not execute the action

  • skip (Boolean) (defaults to: false)

    when true skip the action

  • quiet (Boolean) (defaults to: true)

    when true leaves prompt output, otherwise clears


249
250
251
252
253
254
255
256
257
# File 'lib/tty/file.rb', line 249

def create_file(relative_path, *args, context: nil, force: false, skip: false,
                verbose: true, color: :green, noop: false, quiet: true, &block)
  relative_path = relative_path.to_s
  content = block_given? ? block[] : args.join

  CreateFile.new(self, relative_path, content, context: context, force: force,
                 skip: skip, verbose: verbose, color: color, noop: noop,
                 quiet: quiet).call
end

.diff(path_a, path_b, threshold: 10_000_000, format: :unified, lines: 3, header: true, verbose: true, color: :green, noop: false) ⇒ Object

Diff files line by line

Examples:

diff(file_a, file_b, format: :old)

Parameters:

  • path_a (String, Pathname)

    the path to the original file

  • path_b (String, Pathname)

    the path to a new file

  • format (Symbol) (defaults to: :unified)

    the diffining output format

  • lines (Intger) (defaults to: 3)

    the number of extra lines for the context

  • threshold (Integer) (defaults to: 10_000_000)

    maximum file size in bytes


416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'lib/tty/file.rb', line 416

def diff(path_a, path_b, threshold: 10_000_000, format: :unified, lines: 3,
         header: true, verbose: true, color: :green, noop: false)
  open_tempfile_if_missing(path_a) do |file_a, temp_a|
    message = check_binary_or_large(file_a, threshold)
    return message if message

    open_tempfile_if_missing(path_b) do |file_b, temp_b|
      message = check_binary_or_large(file_b, threshold)
      return message if message

      file_a_path, file_b_path = *diff_paths(file_a, file_b, temp_a, temp_b)
                                  .map { |path| ::File.join(*path) }

      log_status(:diff, "#{file_a_path} and #{file_b_path}",
                 verbose: verbose, color: color)

      return "" if noop

      diff_files = CompareFiles.new(format: format, context_lines: lines,
                                    header: header, verbose: verbose,
                                    color: color, noop: noop,
                                    diff_colors: diff_colors)

      return diff_files.call(file_a, file_b, file_a_path, file_b_path)
    end
  end
end

.diff_filesObject

Diff files line by line

Examples:

diff(file_a, file_b, format: :old)

Parameters:

  • path_a (String, Pathname)

    the path to the original file

  • path_b (String, Pathname)

    the path to a new file

  • format (Symbol)

    the diffining output format

  • lines (Intger)

    the number of extra lines for the context

  • threshold (Integer)

    maximum file size in bytes


445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# File 'lib/tty/file.rb', line 445

def diff(path_a, path_b, threshold: 10_000_000, format: :unified, lines: 3,
         header: true, verbose: true, color: :green, noop: false)
  open_tempfile_if_missing(path_a) do |file_a, temp_a|
    message = check_binary_or_large(file_a, threshold)
    return message if message

    open_tempfile_if_missing(path_b) do |file_b, temp_b|
      message = check_binary_or_large(file_b, threshold)
      return message if message

      file_a_path, file_b_path = *diff_paths(file_a, file_b, temp_a, temp_b)
                                  .map { |path| ::File.join(*path) }

      log_status(:diff, "#{file_a_path} and #{file_b_path}",
                 verbose: verbose, color: color)

      return "" if noop

      diff_files = CompareFiles.new(format: format, context_lines: lines,
                                    header: header, verbose: verbose,
                                    color: color, noop: noop,
                                    diff_colors: diff_colors)

      return diff_files.call(file_a, file_b, file_a_path, file_b_path)
    end
  end
end

.download_file(uri, *args, **options, &block) ⇒ Object

Download the content from a given address and save at the given relative destination. If block is provided in place of destination, the content of of the uri is yielded.

Examples:

download_file("https://gist.github.com/4701967",
              "doc/benchmarks")
download_file("https://gist.github.com/4701967") do |content|
  content.gsub("\n", " ")
end

Parameters:

  • uri (String, Pathname)

    the URI address

  • dest (String, Pathname)

    the relative path to save

  • limit (Integer)

    the number of maximium redirects


506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
# File 'lib/tty/file.rb', line 506

def download_file(uri, *args, **options, &block)
  uri = uri.to_s
  dest_path = (args.first || ::File.basename(uri)).to_s

  unless uri =~ %r{^https?\://}
    copy_file(uri, dest_path, **options)
    return
  end

  content = DownloadFile.new(uri, dest_path, limit: options[:limit]).call

  if block_given?
    content = (block.arity.nonzero? ? block[content] : block[])
  end

  create_file(dest_path, content, **options)
end

.escape_glob_path(path) ⇒ String

Escape glob character in a path

Examples:

escape_glob_path("foo[bar]") => "foo\\[bar\\]"

Parameters:

  • path (String)

    the path to escape

Returns:

  • (String)

786
787
788
# File 'lib/tty/file.rb', line 786

def escape_glob_path(path)
  path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\" + x }
end

.get_fileObject

Download the content from a given address and save at the given relative destination. If block is provided in place of destination, the content of of the uri is yielded.

Examples:

download_file("https://gist.github.com/4701967",
              "doc/benchmarks")
download_file("https://gist.github.com/4701967") do |content|
  content.gsub("\n", " ")
end

Parameters:

  • uri (String, Pathname)

    the URI address

  • dest (String, Pathname)

    the relative path to save

  • limit (Integer)

    the number of maximium redirects


525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
# File 'lib/tty/file.rb', line 525

def download_file(uri, *args, **options, &block)
  uri = uri.to_s
  dest_path = (args.first || ::File.basename(uri)).to_s

  unless uri =~ %r{^https?\://}
    copy_file(uri, dest_path, **options)
    return
  end

  content = DownloadFile.new(uri, dest_path, limit: options[:limit]).call

  if block_given?
    content = (block.arity.nonzero? ? block[content] : block[])
  end

  create_file(dest_path, content, **options)
end

.gsub_fileBoolean

Replace content of a file matching string, returning false when no substitutions were performed, true otherwise.

Examples:

replace_in_file("Gemfile", /gem 'rails'/, "gem 'hanami'")
replace_in_file("Gemfile", /gem 'rails'/) do |match|
  match = "gem 'hanami'"
end

Parameters:

  • relative_path (String, Pathname)
  • force (Boolean)

    replace content even if present

  • verbose (Boolean)

    when true log status to stdout

  • noop (Boolean)

    when true skip executing this action

  • color (Symbol)

    the name of the color used for displaying action

Returns:

  • (Boolean)

    true when replaced content, false otherwise


700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
# File 'lib/tty/file.rb', line 700

def replace_in_file(relative_path, *args, verbose: true, color: :green,
                    noop: false, force: true, &block)
  check_path(relative_path)
  contents = ::File.read(relative_path)
  replacement = (block ? block[] : args[1..-1].join).gsub('\0', "")
  match = Regexp.escape(replacement)
  status = nil

  log_status(:replace, relative_path, verbose: verbose, color: color)
  return false if noop

  if force || !(contents =~ /^#{match}(\r?\n)*/m)
    status = contents.gsub!(*args, &block)
    if !status.nil?
      ::File.open(relative_path, "w") do |file|
        file.write(contents)
      end
    end
  end
  !status.nil?
end

.inject_into_file(relative_path, *args, verbose: true, color: :green, after: nil, before: nil, force: true, noop: false, &block) ⇒ Object

Inject content into file at a given location

Examples:

inject_into_file("Gemfile", "gem 'tty'", after: "gem 'rack'\n")
inject_into_file("Gemfile", "gem 'tty'\n", "gem 'loaf'", after: "gem 'rack'\n")
inject_into_file("Gemfile", after: "gem 'rack'\n") do
  "gem 'tty'\n"
end

Parameters:

  • relative_path (String, Pathname)
  • before (String) (defaults to: nil)

    the matching line to insert content before

  • after (String) (defaults to: nil)

    the matching line to insert content after

  • force (Boolean) (defaults to: true)

    insert content more than once

  • verbose (Boolean) (defaults to: true)

    when true log status

  • color (Symbol) (defaults to: :green)

    the color name used in displaying this action

  • noop (Boolean) (defaults to: false)

    when true skip perfomring this action


621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
# File 'lib/tty/file.rb', line 621

def inject_into_file(relative_path, *args, verbose: true, color: :green,
                     after: nil, before: nil, force: true, noop: false, &block)
  check_path(relative_path)
  replacement = block_given? ? block[] : args.join

  flag, match = after ? [:after, after] : [:before, before]

  match = match.is_a?(Regexp) ? match : Regexp.escape(match)
  content = if flag == :after
              '\0' + replacement
            else
              replacement + '\0'
            end

  log_status(:inject, relative_path, verbose: verbose, color: color)
  replace_in_file(relative_path, /#{match}/, content, verbose: false,
                  color: color, force: force, noop: noop)
end

.insert_into_fileObject

Inject content into file at a given location

Examples:

inject_into_file("Gemfile", "gem 'tty'", after: "gem 'rack'\n")
inject_into_file("Gemfile", "gem 'tty'\n", "gem 'loaf'", after: "gem 'rack'\n")
inject_into_file("Gemfile", after: "gem 'rack'\n") do
  "gem 'tty'\n"
end

Parameters:

  • relative_path (String, Pathname)
  • before (String)

    the matching line to insert content before

  • after (String)

    the matching line to insert content after

  • force (Boolean)

    insert content more than once

  • verbose (Boolean)

    when true log status

  • color (Symbol)

    the color name used in displaying this action

  • noop (Boolean)

    when true skip perfomring this action


641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
# File 'lib/tty/file.rb', line 641

def inject_into_file(relative_path, *args, verbose: true, color: :green,
                     after: nil, before: nil, force: true, noop: false, &block)
  check_path(relative_path)
  replacement = block_given? ? block[] : args.join

  flag, match = after ? [:after, after] : [:before, before]

  match = match.is_a?(Regexp) ? match : Regexp.escape(match)
  content = if flag == :after
              '\0' + replacement
            else
              replacement + '\0'
            end

  log_status(:inject, relative_path, verbose: verbose, color: color)
  replace_in_file(relative_path, /#{match}/, content, verbose: false,
                  color: color, force: force, noop: noop)
end

.prepend_to_file(relative_path, *args, verbose: true, color: :green, force: true, noop: false, &block) ⇒ Object

Prepend to a file

Examples:

prepend_to_file("Gemfile", "gem "tty"")
prepend_to_file("Gemfile") do
  "gem 'tty'"
end

Parameters:

  • relative_path (String, Pathname)
  • content (Array[String])

    the content to preped to file


543
544
545
546
547
548
# File 'lib/tty/file.rb', line 543

def prepend_to_file(relative_path, *args, verbose: true, color: :green,
                    force: true, noop: false, &block)
  log_status(:prepend, relative_path, verbose: verbose, color: color)
  inject_into_file(relative_path, *args, before: /\A/, verbose: false,
                   color: color, force: force, noop: noop, &block)
end

.private_module_function(method) ⇒ Object


17
18
19
20
# File 'lib/tty/file.rb', line 17

def self.private_module_function(method)
  module_function(method)
  private_class_method(method)
end

.read_to_char(relative_path, bytes = nil, offset = nil) ⇒ String

Read bytes from a file up to valid character

Examples:

TTY::File.read_to_char()

Parameters:

  • relative_path (String, Pathname)

    the path to file

  • bytes (Integer) (defaults to: nil)

Returns:

  • (String)

80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/tty/file.rb', line 80

def read_to_char(relative_path, bytes = nil, offset = nil)
  buffer = ""
  ::File.open(relative_path) do |file|
    buffer = file.read(bytes) || ""
    buffer = buffer.dup.force_encoding(Encoding.default_external)

    while !file.eof? && !buffer.valid_encoding? &&
          (buffer.bytesize < bytes + 10)

      buffer += file.read(1).force_encoding(Encoding.default_external)
    end
  end
  buffer
end

.remove_file(relative_path, *args, verbose: true, color: :red, noop: false, force: nil, secure: true) ⇒ Object

Remove a file or a directory at specified relative path.

Examples:

remove_file "doc/README.md"

Parameters:

  • relative_path (String, Pathname)
  • noop (Boolean) (defaults to: false)

    when true pretend to remove file

  • force (Boolean) (defaults to: nil)

    when true remove file ignoring errors

  • verbose (Boolean) (defaults to: true)

    when true log status

  • secure (Boolean) (defaults to: true)

    when true check for secure removing


719
720
721
722
723
724
725
726
727
# File 'lib/tty/file.rb', line 719

def remove_file(relative_path, *args, verbose: true, color: :red, noop: false,
                force: nil, secure: true)
  relative_path = relative_path.to_s
  log_status(:remove, relative_path, verbose: verbose, color: color)

  return if noop || !::File.exist?(relative_path)

  ::FileUtils.rm_r(relative_path, force: force, secure: secure)
end

.replace_in_file(relative_path, *args, verbose: true, color: :green, noop: false, force: true, &block) ⇒ Boolean

Replace content of a file matching string, returning false when no substitutions were performed, true otherwise.

Examples:

replace_in_file("Gemfile", /gem 'rails'/, "gem 'hanami'")
replace_in_file("Gemfile", /gem 'rails'/) do |match|
  match = "gem 'hanami'"
end

Parameters:

  • relative_path (String, Pathname)
  • force (Boolean) (defaults to: true)

    replace content even if present

  • verbose (Boolean) (defaults to: true)

    when true log status to stdout

  • noop (Boolean) (defaults to: false)

    when true skip executing this action

  • color (Symbol) (defaults to: :green)

    the name of the color used for displaying action

Returns:

  • (Boolean)

    true when replaced content, false otherwise


677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
# File 'lib/tty/file.rb', line 677

def replace_in_file(relative_path, *args, verbose: true, color: :green,
                    noop: false, force: true, &block)
  check_path(relative_path)
  contents = ::File.read(relative_path)
  replacement = (block ? block[] : args[1..-1].join).gsub('\0', "")
  match = Regexp.escape(replacement)
  status = nil

  log_status(:replace, relative_path, verbose: verbose, color: color)
  return false if noop

  if force || !(contents =~ /^#{match}(\r?\n)*/m)
    status = contents.gsub!(*args, &block)
    if !status.nil?
      ::File.open(relative_path, "w") do |file|
        file.write(contents)
      end
    end
  end
  !status.nil?
end

.safe_append_to_file(relative_path, *args, **options, &block) ⇒ Object

Safely append to file checking if content is not already present


588
589
590
# File 'lib/tty/file.rb', line 588

def safe_append_to_file(relative_path, *args, **options, &block)
  append_to_file(relative_path, *args, **(options.merge(force: false)), &block)
end

.safe_inject_into_file(relative_path, *args, **options, &block) ⇒ Object

Safely prepend to file checking if content is not already present


647
648
649
# File 'lib/tty/file.rb', line 647

def safe_inject_into_file(relative_path, *args, **options, &block)
  inject_into_file(relative_path, *args, **(options.merge(force: false)), &block)
end

.safe_prepend_to_file(relative_path, *args, **options, &block) ⇒ Object

Safely prepend to file checking if content is not already present


554
555
556
# File 'lib/tty/file.rb', line 554

def safe_prepend_to_file(relative_path, *args, **options, &block)
  prepend_to_file(relative_path, *args, **(options.merge(force: false)), &block)
end

.tail_file(relative_path, lines: 10, chunk_size: 512, &block) ⇒ Array[String]

Provide the last number of lines from a file

Examples:

tail_file "filename"
# =>  ["line 19", "line20", ... ]
tail_file "filename", lines: 15
# =>  ["line 19", "line20", ... ]

Parameters:

  • relative_path (String, Pathname)

    the relative path to a file

  • lines (Integer) (defaults to: 10)

    the number of lines to return from file

  • chunk_size (Integer) (defaults to: 512)

    the size of the chunk to read

Returns:

  • (Array[String])

750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
# File 'lib/tty/file.rb', line 750

def tail_file(relative_path, lines: 10, chunk_size: 512, &block)
  file = ::File.open(relative_path)
  line_sep = $/
  output = []
  newline_count = 0

  ReadBackwardFile.new(file, chunk_size).each_chunk do |chunk|
    # look for newline index counting from right of chunk
    while (nl_index = chunk.rindex(line_sep, (nl_index || chunk.size) - 1))
      newline_count += 1
      break if newline_count > lines || nl_index.zero?
    end

    if newline_count > lines
      output.insert(0, chunk[(nl_index + 1)..-1])
      break
    else
      output.insert(0, chunk)
    end
  end

  output.join.split(line_sep).each(&block).to_a
end

Instance Method Details

#check_binary_or_large(file, threshold) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Check if file is binary or exceeds threshold size


474
475
476
477
478
479
480
481
# File 'lib/tty/file.rb', line 474

def check_binary_or_large(file, threshold)
  if binary?(file)
    "#{file.path} is binary, diff output suppressed"
  elsif ::File.size(file) > threshold
    "file size of #{file.path} exceeds #{threshold} bytes, " \
    " diff output suppressed"
  end
end

#check_path(path) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Check if path exists

Parameters:

  • path (String)

Raises:

  • (ArgumentError)

798
799
800
801
802
# File 'lib/tty/file.rb', line 798

def check_path(path)
  return if ::File.exist?(path)

  raise InvalidPathError, "File path \"#{path}\" does not exist."
end

#decorate(message, color) ⇒ Object


808
809
810
# File 'lib/tty/file.rb', line 808

def decorate(message, color)
  @pastel.send(color, message)
end

#diff_colorsObject


462
463
464
465
466
467
468
# File 'lib/tty/file.rb', line 462

def diff_colors
  {
    green: @pastel.green.detach,
    red: @pastel.red.detach,
    cyan: @pastel.cyan.detach
  }
end

#diff_paths(file_a, file_b, temp_a, temp_b) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


449
450
451
452
453
454
455
456
457
458
459
# File 'lib/tty/file.rb', line 449

def diff_paths(file_a, file_b, temp_a, temp_b)
  if temp_a && !temp_b
    [["a", file_b.path], ["b", file_b.path]]
  elsif !temp_a && temp_b
    [["a", file_a.path], ["b", file_a.path]]
  elsif temp_a && temp_b
    [["a"], ["b"]]
  else
    [file_a.path, file_b.path]
  end
end

#log_status(cmd, message, verbose: true, color: false) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Log file operation


816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
# File 'lib/tty/file.rb', line 816

def log_status(cmd, message, verbose: true, color: false)
  return unless verbose

  cmd = cmd.to_s.rjust(12)
  if color
    i = cmd.index(/[a-z]/)
    cmd = cmd[0...i] + decorate(cmd[i..-1], color)
  end

  message = "#{cmd}  #{message}"
  message += "\n" unless message.end_with?("\n")

  @output.print(message)
  @output.flush
end

#open_tempfile_if_missing(object, &block) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

If content is not a path to a file, create a tempfile and open it instead.

Parameters:

  • object (String)

    a path to file or content


840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
# File 'lib/tty/file.rb', line 840

def open_tempfile_if_missing(object, &block)
  if ::FileTest.file?(object)
    ::File.open(object, &block)
  else
    tempfile = Tempfile.new("tty-file-diff")
    tempfile << object
    tempfile.rewind

    block[tempfile, ::File.basename(tempfile)]

    unless tempfile.nil?
      tempfile.close
      tempfile.unlink
    end
  end
end

#tty?Boolean

Check if IO is attached to a terminal

return [Boolean]

Returns:

  • (Boolean)

863
864
865
# File 'lib/tty/file.rb', line 863

def tty?
  @output.respond_to?(:tty?) && @output.tty?
end