module TTY::File

Constants

A_R
A_W
A_X
DownloadError
G_R
G_W
G_X
InvalidPathError

Invalid path erorr

O_R
O_W
O_X
U_R

File permissions

U_W
U_X
VERSION

Public Class Methods

add_file(relative_path, *args, context: nil, force: false, skip: false, verbose: true, color: :green, noop: false, quiet: true, &block)
Alias for: create_file
add_to_file(relative_path, *args, verbose: true, color: :green, force: true, noop: false, &block)
Alias for: append_to_file
append_to_file(relative_path, *args, verbose: true, color: :green, force: true, noop: false, &block) click to toggle source

Append to a file

@param [String, Pathname] relative_path @param [Array] content

the content to append to file

@example

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

@example

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

@api public

# 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
Also aliased as: add_to_file
binary?(relative_path) click to toggle source

Check if file is binary

@param [String, Pathname] relative_path

the path to file to check

@example

binary?("Gemfile") # => false

@example

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

@return [Boolean]

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

@api public

# 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) click to toggle source

Create checksum for a file, io or string objects

@param [File, IO, String, Pathname] source

the source to generate checksum for

@param [String] mode @param [Boolean] noop

when true skip this action

@example

checksum_file("/path/to/file")

@example

checksum_file("Some string content", "md5")

@return [String]

the generated hex value

@api public

# 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) click to toggle source

Change file permissions

@param [String, Pathname] relative_path

the string or path to a file

@param [Integer,String] permisssions

the string or octal number for permissoins

@param [Boolean] noop

when true skips this action

@param [Boolean] verbose

when true displays logging information

@param [Symbol] color

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

@example

chmod("Gemfile", 0755)

@example

chmod("Gemilfe", TTY::File::U_R | TTY::File::U_W)

@example

chmod("Gemfile", "u+x,g+x")

@api public

# 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_dir(source_path, *args, context: nil, force: false, skip: false, verbose: true, color: :green, noop: false, preserve: nil, recursive: true, exclude: nil, &block)
Alias for: copy_directory
copy_directory(source_path, *args, context: nil, force: false, skip: false, verbose: true, color: :green, noop: false, preserve: nil, recursive: true, exclude: nil, &block) click to toggle source

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

@example

copy_directory("app", "new_app", recursive: false)

@example

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

@param [String, Pathname] source_path

the source directory to copy files from

@param [Boolean] preserve

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

@param [Boolean] recursive

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

@param [Regexp] exclude

a regex that specifies files to ignore when copying

@api public

# 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
Also aliased as: copy_dir
copy_file(source_path, *args, context: nil, force: false, skip: false, verbose: true, color: :green, noop: false, preserve: nil, &block) click to toggle source

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

@example

copy_file "templates/test.rb", "app/test.rb"

@example

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

@param [String, Pathname] source_path

the file path to copy file from

@param [Object] context

the binding to use for the template

@param [Boolean] preserve

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

@param [Boolean] noop

when true does not execute the action

@param [Boolean] verbose

when true log the action status to stdout

@param [Symbol] color

the color name to use for logging

@api public

# 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

  copy_metadata(source_path, dest_path, verbose: verbose, noop: noop,
                color: color)
end
copy_metadata(src_path, dest_path, **options) click to toggle source

Copy file metadata

@param [String] src_path

the source file path

@param [String] dest_path

the destination file path

@api public

# File lib/tty/file.rb, line 327
def copy_metadata(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_dir(destination, *args, context: nil, verbose: true, color: :green, noop: false, force: false, skip: false, quiet: true)
Alias for: create_directory
create_directory(destination, *args, context: nil, verbose: true, color: :green, noop: false, force: false, skip: false, quiet: true) click to toggle source

Create directory structure

@param [String, Pathname, Hash] destination

the path or data structure describing directory tree

@param [Object] context

the context for template evaluation

@param [Boolean] quiet

when true leaves prompt output, otherwise clears

@param [Boolean] force

when true overwrites existing files, false by default

@param [Boolean] noop

when true skips this action

@param [Boolean] verbose

when true displays logging information

@param [Symbol] color

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

@example

create_directory("/path/to/dir")

@example

tree =
  "app" => [
    "README.md",
    ["Gemfile", "gem "tty-file""],
    "lib" => [
      "cli.rb",
      ["file_utils.rb", "require "tty-file""]
    ]
    "spec" => []
  ]

create_directory(tree)

@return [void]

@api public

# 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
Also aliased as: create_dir
create_file(relative_path, *args, context: nil, force: false, skip: false, verbose: true, color: :green, noop: false, quiet: true, &block) click to toggle source

Create new file if doesn't exist

@param [String, Pathname] relative_path @param [String|nil] content

the content to add to file

@param [Object] context

the binding to use for the template

@param [Symbol] color

the color name to use for logging

@param [Boolean] force

forces ovewrite if conflict present

@param [Boolean] verbose

when true log the action status to stdout

@param [Boolean] noop

when true do not execute the action

@param [Boolean] skip

when true skip the action

@param [Boolean] quiet

when true leaves prompt output, otherwise clears

@example

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

@example

create_file "doc/README.md" do
  "# Title Header"
end

@api public

# 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
Also aliased as: add_file
diff(path_a, path_b, threshold: 10_000_000, format: :unified, lines: 3, header: true, verbose: true, color: :green, noop: false) click to toggle source

Diff files line by line

@param [String, Pathname] path_a

the path to the original file

@param [String, Pathname] path_b

the path to a new file

@param [Symbol] format

the diffining output format

@param [Intger] lines

the number of extra lines for the context

@param [Integer] threshold

maximum file size in bytes

@example

diff(file_a, file_b, format: :old)

@api public

# 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
Also aliased as: diff_files
diff_files(path_a, path_b, threshold: 10_000_000, format: :unified, lines: 3, header: true, verbose: true, color: :green, noop: false)
Alias for: diff
download_file(uri, *args, **options, &block) click to toggle source

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.

@param [String, Pathname] uri

the URI address

@param [String, Pathname] dest

the relative path to save

@param [Integer] limit

the number of maximium redirects

@example

download_file("https://gist.github.com/4701967",
              "doc/benchmarks")

@example

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

@api public

# 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
Also aliased as: get_file
escape_glob_path(path) click to toggle source

Escape glob character in a path

@param [String] path

the path to escape

@example

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

@return [String]

@api public

# File lib/tty/file.rb, line 786
def escape_glob_path(path)
  path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\" + x }
end
get_file(uri, *args, **options, &block)
Alias for: download_file
gsub_file(relative_path, *args, verbose: true, color: :green, noop: false, force: true, &block)
Alias for: replace_in_file
inject_into_file(relative_path, *args, verbose: true, color: :green, after: nil, before: nil, force: true, noop: false, &block) click to toggle source

Inject content into file at a given location

@param [String, Pathname] relative_path @param [String] before

the matching line to insert content before

@param [String] after

the matching line to insert content after

@param [Boolean] force

insert content more than once

@param [Boolean] verbose

when true log status

@param [Symbol] color

the color name used in displaying this action

@param [Boolean] noop

when true skip perfomring this action

@example

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

@example

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

@example

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

@api public

# 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
Also aliased as: insert_into_file
insert_into_file(relative_path, *args, verbose: true, color: :green, after: nil, before: nil, force: true, noop: false, &block)
Alias for: inject_into_file
prepend_to_file(relative_path, *args, verbose: true, color: :green, force: true, noop: false, &block) click to toggle source

Prepend to a file

@param [String, Pathname] relative_path @param [Array] content

the content to preped to file

@example

prepend_to_file("Gemfile", "gem "tty"")

@example

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

@api public

# 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) click to toggle source
# 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) click to toggle source

Read bytes from a file up to valid character

@param [String, Pathname] relative_path

the path to file

@param [Integer] bytes

@example

TTY::File.read_to_char()

@return [String]

@api public

# 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) click to toggle source

Remove a file or a directory at specified relative path.

@param [String, Pathname] relative_path @param [Boolean] noop

when true pretend to remove file

@param [Boolean] force

when true remove file ignoring errors

@param [Boolean] verbose

when true log status

@param [Boolean] secure

when true check for secure removing

@example

remove_file "doc/README.md"

@api public

# 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) click to toggle source

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

@param [String, Pathname] relative_path @param [Boolean] force

replace content even if present

@param [Boolean] verbose

when true log status to stdout

@param [Boolean] noop

when true skip executing this action

@param [Symbol] color

the name of the color used for displaying action

@example

replace_in_file("Gemfile", /gem 'rails'/, "gem 'hanami'")

@example

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

@return [Boolean]

true when replaced content, false otherwise

@api public

# 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
Also aliased as: gsub_file
safe_append_to_file(relative_path, *args, **options, &block) click to toggle source

Safely append to file checking if content is not already present

@api public

# 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) click to toggle source

Safely prepend to file checking if content is not already present

@api public

# 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) click to toggle source

Safely prepend to file checking if content is not already present

@api public

# 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) click to toggle source

Provide the last number of lines from a file

@param [String, Pathname] relative_path

the relative path to a file

@param [Integer] lines

the number of lines to return from file

@param [Integer] chunk_size

the size of the chunk to read

@example

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

@example

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

@return [Array]

@api public

# 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

Public Instance Methods

check_binary_or_large(file, threshold) click to toggle source

Check if file is binary or exceeds threshold size

@api private

# 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) click to toggle source

Check if path exists

@param [String] path

@raise [ArgumentError]

@api private

# 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) click to toggle source
# File lib/tty/file.rb, line 808
def decorate(message, color)
  @pastel.send(color, message)
end
diff_colors() click to toggle source
# 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) click to toggle source

@api private

# 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) click to toggle source

Log file operation

@api private

# 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) click to toggle source

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

@param [String] object

a path to file or content

@api private

# 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?() click to toggle source

Check if IO is attached to a terminal

return [Boolean]

@api public

# File lib/tty/file.rb, line 863
def tty?
  @output.respond_to?(:tty?) && @output.tty?
end

Private Instance Methods

add_file(relative_path, *args, context: nil, force: false, skip: false, verbose: true, color: :green, noop: false, quiet: true, &block)
Alias for: create_file
add_to_file(relative_path, *args, verbose: true, color: :green, force: true, noop: false, &block)
Alias for: append_to_file
append_to_file(relative_path, *args, verbose: true, color: :green, force: true, noop: false, &block) click to toggle source

Append to a file

@param [String, Pathname] relative_path @param [Array] content

the content to append to file

@example

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

@example

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

@api public

# 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
Also aliased as: add_to_file
binary?(relative_path) click to toggle source

Check if file is binary

@param [String, Pathname] relative_path

the path to file to check

@example

binary?("Gemfile") # => false

@example

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

@return [Boolean]

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

@api public

# 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) click to toggle source

Create checksum for a file, io or string objects

@param [File, IO, String, Pathname] source

the source to generate checksum for

@param [String] mode @param [Boolean] noop

when true skip this action

@example

checksum_file("/path/to/file")

@example

checksum_file("Some string content", "md5")

@return [String]

the generated hex value

@api public

# 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) click to toggle source

Change file permissions

@param [String, Pathname] relative_path

the string or path to a file

@param [Integer,String] permisssions

the string or octal number for permissoins

@param [Boolean] noop

when true skips this action

@param [Boolean] verbose

when true displays logging information

@param [Symbol] color

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

@example

chmod("Gemfile", 0755)

@example

chmod("Gemilfe", TTY::File::U_R | TTY::File::U_W)

@example

chmod("Gemfile", "u+x,g+x")

@api public

# 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_dir(source_path, *args, context: nil, force: false, skip: false, verbose: true, color: :green, noop: false, preserve: nil, recursive: true, exclude: nil, &block)
Alias for: copy_directory
copy_directory(source_path, *args, context: nil, force: false, skip: false, verbose: true, color: :green, noop: false, preserve: nil, recursive: true, exclude: nil, &block) click to toggle source

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

@example

copy_directory("app", "new_app", recursive: false)

@example

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

@param [String, Pathname] source_path

the source directory to copy files from

@param [Boolean] preserve

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

@param [Boolean] recursive

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

@param [Regexp] exclude

a regex that specifies files to ignore when copying

@api public

# 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
Also aliased as: copy_dir
copy_file(source_path, *args, context: nil, force: false, skip: false, verbose: true, color: :green, noop: false, preserve: nil, &block) click to toggle source

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

@example

copy_file "templates/test.rb", "app/test.rb"

@example

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

@param [String, Pathname] source_path

the file path to copy file from

@param [Object] context

the binding to use for the template

@param [Boolean] preserve

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

@param [Boolean] noop

when true does not execute the action

@param [Boolean] verbose

when true log the action status to stdout

@param [Symbol] color

the color name to use for logging

@api public

# 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

  copy_metadata(source_path, dest_path, verbose: verbose, noop: noop,
                color: color)
end
copy_metadata(src_path, dest_path, **options) click to toggle source

Copy file metadata

@param [String] src_path

the source file path

@param [String] dest_path

the destination file path

@api public

# File lib/tty/file.rb, line 327
def copy_metadata(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_dir(destination, *args, context: nil, verbose: true, color: :green, noop: false, force: false, skip: false, quiet: true)
Alias for: create_directory
create_directory(destination, *args, context: nil, verbose: true, color: :green, noop: false, force: false, skip: false, quiet: true) click to toggle source

Create directory structure

@param [String, Pathname, Hash] destination

the path or data structure describing directory tree

@param [Object] context

the context for template evaluation

@param [Boolean] quiet

when true leaves prompt output, otherwise clears

@param [Boolean] force

when true overwrites existing files, false by default

@param [Boolean] noop

when true skips this action

@param [Boolean] verbose

when true displays logging information

@param [Symbol] color

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

@example

create_directory("/path/to/dir")

@example

tree =
  "app" => [
    "README.md",
    ["Gemfile", "gem "tty-file""],
    "lib" => [
      "cli.rb",
      ["file_utils.rb", "require "tty-file""]
    ]
    "spec" => []
  ]

create_directory(tree)

@return [void]

@api public

# 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
Also aliased as: create_dir
create_file(relative_path, *args, context: nil, force: false, skip: false, verbose: true, color: :green, noop: false, quiet: true, &block) click to toggle source

Create new file if doesn't exist

@param [String, Pathname] relative_path @param [String|nil] content

the content to add to file

@param [Object] context

the binding to use for the template

@param [Symbol] color

the color name to use for logging

@param [Boolean] force

forces ovewrite if conflict present

@param [Boolean] verbose

when true log the action status to stdout

@param [Boolean] noop

when true do not execute the action

@param [Boolean] skip

when true skip the action

@param [Boolean] quiet

when true leaves prompt output, otherwise clears

@example

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

@example

create_file "doc/README.md" do
  "# Title Header"
end

@api public

# 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
Also aliased as: add_file
diff(path_a, path_b, threshold: 10_000_000, format: :unified, lines: 3, header: true, verbose: true, color: :green, noop: false) click to toggle source

Diff files line by line

@param [String, Pathname] path_a

the path to the original file

@param [String, Pathname] path_b

the path to a new file

@param [Symbol] format

the diffining output format

@param [Intger] lines

the number of extra lines for the context

@param [Integer] threshold

maximum file size in bytes

@example

diff(file_a, file_b, format: :old)

@api public

# 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
Also aliased as: diff_files
diff_files(path_a, path_b, threshold: 10_000_000, format: :unified, lines: 3, header: true, verbose: true, color: :green, noop: false)
Alias for: diff
download_file(uri, *args, **options, &block) click to toggle source

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.

@param [String, Pathname] uri

the URI address

@param [String, Pathname] dest

the relative path to save

@param [Integer] limit

the number of maximium redirects

@example

download_file("https://gist.github.com/4701967",
              "doc/benchmarks")

@example

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

@api public

# 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
Also aliased as: get_file
escape_glob_path(path) click to toggle source

Escape glob character in a path

@param [String] path

the path to escape

@example

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

@return [String]

@api public

# File lib/tty/file.rb, line 786
def escape_glob_path(path)
  path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\" + x }
end
get_file(uri, *args, **options, &block)
Alias for: download_file
gsub_file(relative_path, *args, verbose: true, color: :green, noop: false, force: true, &block)
Alias for: replace_in_file
inject_into_file(relative_path, *args, verbose: true, color: :green, after: nil, before: nil, force: true, noop: false, &block) click to toggle source

Inject content into file at a given location

@param [String, Pathname] relative_path @param [String] before

the matching line to insert content before

@param [String] after

the matching line to insert content after

@param [Boolean] force

insert content more than once

@param [Boolean] verbose

when true log status

@param [Symbol] color

the color name used in displaying this action

@param [Boolean] noop

when true skip perfomring this action

@example

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

@example

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

@example

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

@api public

# 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
Also aliased as: insert_into_file
insert_into_file(relative_path, *args, verbose: true, color: :green, after: nil, before: nil, force: true, noop: false, &block)
Alias for: inject_into_file
prepend_to_file(relative_path, *args, verbose: true, color: :green, force: true, noop: false, &block) click to toggle source

Prepend to a file

@param [String, Pathname] relative_path @param [Array] content

the content to preped to file

@example

prepend_to_file("Gemfile", "gem "tty"")

@example

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

@api public

# 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
read_to_char(relative_path, bytes = nil, offset = nil) click to toggle source

Read bytes from a file up to valid character

@param [String, Pathname] relative_path

the path to file

@param [Integer] bytes

@example

TTY::File.read_to_char()

@return [String]

@api public

# 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) click to toggle source

Remove a file or a directory at specified relative path.

@param [String, Pathname] relative_path @param [Boolean] noop

when true pretend to remove file

@param [Boolean] force

when true remove file ignoring errors

@param [Boolean] verbose

when true log status

@param [Boolean] secure

when true check for secure removing

@example

remove_file "doc/README.md"

@api public

# 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) click to toggle source

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

@param [String, Pathname] relative_path @param [Boolean] force

replace content even if present

@param [Boolean] verbose

when true log status to stdout

@param [Boolean] noop

when true skip executing this action

@param [Symbol] color

the name of the color used for displaying action

@example

replace_in_file("Gemfile", /gem 'rails'/, "gem 'hanami'")

@example

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

@return [Boolean]

true when replaced content, false otherwise

@api public

# 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
Also aliased as: gsub_file
safe_append_to_file(relative_path, *args, **options, &block) click to toggle source

Safely append to file checking if content is not already present

@api public

# 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) click to toggle source

Safely prepend to file checking if content is not already present

@api public

# 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) click to toggle source

Safely prepend to file checking if content is not already present

@api public

# 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) click to toggle source

Provide the last number of lines from a file

@param [String, Pathname] relative_path

the relative path to a file

@param [Integer] lines

the number of lines to return from file

@param [Integer] chunk_size

the size of the chunk to read

@example

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

@example

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

@return [Array]

@api public

# 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