class Dry::Files

File manipulations

@since 0.1.0 @api public

Constants

CLOSE_BLOCK

@since 0.1.0 @api private

CONTENT_OFFSET

@since 0.1.0 @api private

INDENTATION

@since 0.1.0 @api private

INLINE_CLOSE_BLOCK

@since 0.1.0 @api private

INLINE_OPEN_BLOCK_MATCHER

@since 0.1.0 @api private

NEW_LINE

@since 0.1.0 @api private

SPACE

@since 0.1.0 @api private

SPACE_MATCHER

@since 0.1.0 @api private

VERSION

Attributes

adapter[R]

@since 0.1.0 @api private

Public Class Methods

new(memory: false, adapter: Adapter.call(memory: memory)) click to toggle source

Creates a new instance

Memory file system is experimental

@param memory [TrueClass,FalseClass] use in-memory, ephemeral file system @param adapter [Dry::FileSystem]

@return [Dry::Files] a new files instance

@since 0.1.0 @api public

# File lib/dry/files.rb, line 28
def initialize(memory: false, adapter: Adapter.call(memory: memory))
  @adapter = adapter
end

Public Instance Methods

append(path, contents) click to toggle source

Adds a new line at the bottom of the file

@param path [String,Pathname] the path to file @param contents [String] the contents to add

@raise [Dry::Files::IOError] in case of I/O error

@see unshift

@since 0.1.0 @api public

# File lib/dry/files.rb, line 308
def append(path, contents)
  mkdir_p(path)

  content = adapter.readlines(path)
  content << newline unless newline?(content.last)
  content << newline(contents)

  write(path, content)
end
chdir(path, &blk) click to toggle source

Temporary changes the current working directory of the process to the given path and yield the given block.

@param path [String,Pathname] the target directory @param blk [Proc] the code to execute with the target directory

@raise [Dry::Files::IOError] in case of I/O error

@since 0.1.0

# File lib/dry/files.rb, line 123
def chdir(path, &blk)
  adapter.chdir(path, &blk)
end
cp(source, destination) click to toggle source

Copies source into destination. All the intermediate directories are created. If the destination already exists, it overrides the contents.

@param source [String,Pathname] the path to the source file @param destination [String,Pathname] the path to the destination file

@raise [Dry::Files::IOError] in case of I/O error

@since 0.1.0 @api public

# File lib/dry/files.rb, line 191
def cp(source, destination)
  adapter.cp(source, destination)
end
delete(path) click to toggle source

Deletes given path (file).

@param path [String,Pathname] the path to file

@raise [Dry::Files::IOError] in case of I/O error

@since 0.1.0 @api public

# File lib/dry/files.rb, line 203
def delete(path)
  adapter.rm(path)
end
delete_directory(path) click to toggle source

Deletes given path (directory).

@param path [String,Pathname] the path to file

@raise [Dry::Files::IOError] in case of I/O error

@since 0.1.0 @api public

# File lib/dry/files.rb, line 215
def delete_directory(path)
  adapter.rm_rf(path)
end
directory?(path) click to toggle source

Checks if `path` is a directory

@param path [String,Pathname] the path to directory

@return [TrueClass,FalseClass] the result of the check

@since 0.1.0 @api public

@example

require "dry/files"

Dry::Files.new.directory?(__dir__)  # => true
Dry::Files.new.directory?(__FILE__) # => false

Dry::Files.new.directory?("missing_directory") # => false
# File lib/dry/files.rb, line 255
def directory?(path)
  adapter.directory?(path)
end
executable?(path) click to toggle source

Checks if `path` is an executable

@param path [String,Pathname] the path to file

@return [TrueClass,FalseClass] the result of the check

@since 0.1.0 @api public

@example

require "dry/files"

Dry::Files.new.executable?("/path/to/ruby") # => true
Dry::Files.new.executable?(__FILE__)        # => false

Dry::Files.new.directory?("missing_file") # => false
# File lib/dry/files.rb, line 275
def executable?(path)
  adapter.executable?(path)
end
exist?(path) click to toggle source

Checks if `path` exist

@param path [String,Pathname] the path to file

@return [TrueClass,FalseClass] the result of the check

@since 0.1.0 @api public

@example

require "dry/files"

Dry::Files.new.exist?(__FILE__) # => true
Dry::Files.new.exist?(__dir__)  # => true

Dry::Files.new.exist?("missing_file") # => false
# File lib/dry/files.rb, line 235
def exist?(path)
  adapter.exist?(path)
end
expand_path(path, dir = pwd) click to toggle source

Converts a path to an absolute path.

Relative paths are referenced from the current working directory of the process unless `dir` is given.

@param path [String,Pathname] the path to the file @param dir [String,Pathname] the base directory

@return [String] the expanded path

@since 0.1.0

# File lib/dry/files.rb, line 101
def expand_path(path, dir = pwd)
  adapter.expand_path(path, dir)
end
inject_line_after(path, target, contents) click to toggle source

Inject `contents` in `path` after `target`.

@param path [String,Pathname] the path to file @param target [String,Regexp] the target to replace @param contents [String] the contents to inject

@raise [Dry::Files::IOError] in case of I/O error @raise [Dry::Files::MissingTargetError] if `target` cannot be found in `path`

@see inject_line_before @see inject_line_before_last @see inject_line_after_last

@since 0.1.0 @api public

# File lib/dry/files.rb, line 411
def inject_line_after(path, target, contents)
  _inject_line_after(path, target, contents, method(:index))
end
inject_line_after_last(path, target, contents) click to toggle source

Inject `contents` in `path` after last `target`.

@param path [String,Pathname] the path to file @param target [String,Regexp] the target to replace @param contents [String] the contents to inject

@raise [Dry::Files::IOError] in case of I/O error @raise [Dry::Files::MissingTargetError] if `target` cannot be found in `path`

@see inject_line_before @see inject_line_after @see inject_line_before_last

@since 0.1.0 @api public

# File lib/dry/files.rb, line 430
def inject_line_after_last(path, target, contents)
  _inject_line_after(path, target, contents, method(:rindex))
end
inject_line_at_block_bottom(path, target, *contents) click to toggle source

Inject `contents` in `path` within the first Ruby block that matches `target`. The given `contents` will appear at the BOTTOM of the Ruby block.

@param path [String,Pathname] the path to file @param target [String,Regexp] the target matcher for Ruby block @param contents [String,Array<String>] the contents to inject

@raise [Dry::Files::IOError] in case of I/O error @raise [Dry::Files::MissingTargetError] if `target` cannot be found in `path`

@since 0.1.0 @api public

@example Inject a single line

require "dry/files"

files = Dry::Files.new
path = "config/application.rb"

File.read(path)
# # frozen_string_literal: true
#
# class Application
#   configure do
#     root __dir__
#   end
# end

# inject a single line
files.inject_line_at_block_bottom(path, /configure/, %(load_path.unshift("lib")))

File.read(path)
# # frozen_string_literal: true
#
# class Application
#   configure do
#     root __dir__
#     load_path.unshift("lib")
#   end
# end

@example Inject multiple lines

require "dry/files"

files = Dry::Files.new
path = "config/application.rb"

File.read(path)
# # frozen_string_literal: true
#
# class Application
#   configure do
#     root __dir__
#   end
# end

# inject multiple lines
files.inject_line_at_block_bottom(path,
                                  /configure/,
                                  [%(load_path.unshift("lib")), "settings.load!"])

File.read(path)
# # frozen_string_literal: true
#
# class Application
#   configure do
#     root __dir__
#     load_path.unshift("lib")
#     settings.load!
#   end
# end

@example Inject a block

require "dry/files"

files = Dry::Files.new
path = "config/application.rb"

File.read(path)
# # frozen_string_literal: true
#
# class Application
#   configure do
#     root __dir__
#   end
# end

# inject a block
block = <<~BLOCK
  settings do
    load!
  end
BLOCK
files.inject_line_at_block_bottom(path, /configure/, block)

File.read(path)
# # frozen_string_literal: true
#
# class Application
#   configure do
#     root __dir__
#     settings do
#       load!
#     end
#   end
# end
# File lib/dry/files.rb, line 658
def inject_line_at_block_bottom(path, target, *contents)
  content  = adapter.readlines(path)
  starting = index(content, path, target)
  line     = content[starting]
  size     = line[SPACE_MATCHER].bytesize
  closing  = (SPACE * size) +
             (target.match?(INLINE_OPEN_BLOCK_MATCHER) ? INLINE_CLOSE_BLOCK : CLOSE_BLOCK)
  ending   = starting + index(content[starting..-CONTENT_OFFSET], path, closing)
  offset   = SPACE * (content[ending][SPACE_MATCHER].bytesize + INDENTATION)

  contents = Array(contents).flatten
  contents = _offset_block_lines(contents, offset)

  content.insert(ending, contents)
  write(path, content)
end
inject_line_at_block_top(path, target, *contents) click to toggle source

Inject `contents` in `path` within the first Ruby block that matches `target`. The given `contents` will appear at the TOP of the Ruby block.

@param path [String,Pathname] the path to file @param target [String,Regexp] the target matcher for Ruby block @param contents [String,Array<String>] the contents to inject

@raise [Dry::Files::IOError] in case of I/O error @raise [Dry::Files::MissingTargetError] if `target` cannot be found in `path`

@since 0.1.0 @api public

@example Inject a single line

require "dry/files"

files = Dry::Files.new
path = "config/application.rb"

File.read(path)
# # frozen_string_literal: true
#
# class Application
#   configure do
#     root __dir__
#   end
# end

# inject a single line
files.inject_line_at_block_top(path, /configure/, %(load_path.unshift("lib")))

File.read(path)
# # frozen_string_literal: true
#
# class Application
#   configure do
#     load_path.unshift("lib")
#     root __dir__
#   end
# end

@example Inject multiple lines

require "dry/files"

files = Dry::Files.new
path = "config/application.rb"

File.read(path)
# # frozen_string_literal: true
#
# class Application
#   configure do
#     root __dir__
#   end
# end

# inject multiple lines
files.inject_line_at_block_top(path,
                               /configure/,
                               [%(load_path.unshift("lib")), "settings.load!"])

File.read(path)
# # frozen_string_literal: true
#
# class Application
#   configure do
#     load_path.unshift("lib")
#     settings.load!
#     root __dir__
#   end
# end

@example Inject a block

require "dry/files"

files = Dry::Files.new
path = "config/application.rb"

File.read(path)
# # frozen_string_literal: true
#
# class Application
#   configure do
#     root __dir__
#   end
# end

# inject a block
block = <<~BLOCK
  settings do
    load!
  end
BLOCK
files.inject_line_at_block_top(path, /configure/, block)

File.read(path)
# # frozen_string_literal: true
#
# class Application
#   configure do
#     settings do
#       load!
#     end
#     root __dir__
#   end
# end
# File lib/dry/files.rb, line 540
def inject_line_at_block_top(path, target, *contents)
  content  = adapter.readlines(path)
  starting = index(content, path, target)
  offset   = SPACE * (content[starting][SPACE_MATCHER].bytesize + INDENTATION)

  contents = Array(contents).flatten
  contents = _offset_block_lines(contents, offset)

  content.insert(starting + CONTENT_OFFSET, contents)
  write(path, content)
end
inject_line_before(path, target, contents) click to toggle source

Inject `contents` in `path` before `target`.

@param path [String,Pathname] the path to file @param target [String,Regexp] the target to replace @param contents [String] the contents to inject

@raise [Dry::Files::IOError] in case of I/O error @raise [Dry::Files::MissingTargetError] if `target` cannot be found in `path`

@see inject_line_after @see inject_line_before_last @see inject_line_after_last

@since 0.1.0 @api public

# File lib/dry/files.rb, line 373
def inject_line_before(path, target, contents)
  _inject_line_before(path, target, contents, method(:index))
end
inject_line_before_last(path, target, contents) click to toggle source

Inject `contents` in `path` after last `target`.

@param path [String,Pathname] the path to file @param target [String,Regexp] the target to replace @param contents [String] the contents to inject

@raise [Dry::Files::IOError] in case of I/O error @raise [Dry::Files::MissingTargetError] if `target` cannot be found in `path`

@see inject_line_before @see inject_line_after @see inject_line_after_last

@since 0.1.0 @api public

# File lib/dry/files.rb, line 392
def inject_line_before_last(path, target, contents)
  _inject_line_before(path, target, contents, method(:rindex))
end
join(*path) click to toggle source

Returns a new string formed by joining the strings using Operating System path separator

@param path [Array<String,Pathname>] path tokens

@return [String] the joined path

@since 0.1.0 @api public

# File lib/dry/files.rb, line 86
def join(*path)
  adapter.join(*path)
end
mkdir(path) click to toggle source

Creates a directory for the given path. It assumes that all the tokens in `path` are meant to be a directory. All the intermediate directories are created.

@param path [String,Pathname] the path to directory

@raise [Dry::Files::IOError] in case of I/O error

@since 0.1.0 @api public

@see mkdir_p

@example

require "dry/files"

Dry::Files.new.mkdir("path/to/directory")
  # => creates the `path/to/directory` directory

# WRONG this isn't probably what you want, check `.mkdir_p`
Dry::Files.new.mkdir("path/to/file.rb")
  # => creates the `path/to/file.rb` directory
# File lib/dry/files.rb, line 149
def mkdir(path)
  adapter.mkdir(path)
end
mkdir_p(path) click to toggle source

Creates a directory for the given path. It assumes that all the tokens, but the last, in `path` are meant to be a directory, whereas the last is meant to be a file. All the intermediate directories are created.

@param path [String,Pathname] the path to directory

@raise [Dry::Files::IOError] in case of I/O error

@since 0.1.0 @api public

@see mkdir

@example

require "dry/files"

Dry::Files.new.mkdir_p("path/to/file.rb")
  # => creates the `path/to` directory, but NOT `file.rb`

# WRONG it doesn't create the last directory, check `.mkdir`
Dry::Files.new.mkdir_p("path/to/directory")
  # => creates the `path/to` directory
# File lib/dry/files.rb, line 176
def mkdir_p(path)
  adapter.mkdir_p(path)
end
pwd() click to toggle source

Returns the name of the current working directory.

@return [String] the current working directory.

@since 0.1.0

# File lib/dry/files.rb, line 110
def pwd
  adapter.pwd
end
read(path) click to toggle source

Read file content

@param path [String,Pathname] the path to file

@return [String] the file contents

@raise [Dry::Files::IOError] in case of I/O error

@since 0.1.0 @api public

TODO: allow buffered read

# File lib/dry/files.rb, line 44
def read(path)
  adapter.read(path)
end
remove_block(path, target) click to toggle source

Removes `target` block from `path`

@param path [String,Pathname] the path to file @param target [String] the target block to remove

@raise [Dry::Files::IOError] in case of I/O error @raise [Dry::Files::MissingTargetError] if `target` cannot be found in `path`

@since 0.1.0 @api public

@example

require "dry/files"

puts File.read("app.rb")

# class App
#   configure do
#     root __dir__
#   end
# end

Dry::Files.new.remove_block("app.rb", "configure")

puts File.read("app.rb")

# class App
# end
# File lib/dry/files.rb, line 721
def remove_block(path, target)
  content  = adapter.readlines(path)
  starting = index(content, path, target)
  line     = content[starting]
  size     = line[SPACE_MATCHER].bytesize
  closing  = (SPACE * size) +
             (target.match?(INLINE_OPEN_BLOCK_MATCHER) ? INLINE_CLOSE_BLOCK : CLOSE_BLOCK)
  ending   = starting + index(content[starting..-CONTENT_OFFSET], path, closing)

  content.slice!(starting..ending)
  write(path, content)

  remove_block(path, target) if match?(content, target)
end
remove_line(path, target) click to toggle source

Removes line from `path`, matching `target`.

@param path [String,Pathname] the path to file @param target [String,Regexp] the target to remove

@raise [Dry::Files::IOError] in case of I/O error @raise [Dry::Files::MissingTargetError] if `target` cannot be found in `path`

@since 0.1.0 @api public

# File lib/dry/files.rb, line 685
def remove_line(path, target)
  content = adapter.readlines(path)
  i       = index(content, path, target)

  content.delete_at(i)
  write(path, content)
end
replace_first_line(path, target, replacement) click to toggle source

Replace first line in `path` that contains `target` with `replacement`.

@param path [String,Pathname] the path to file @param target [String,Regexp] the target to replace @param replacement [String] the replacement

@raise [Dry::Files::IOError] in case of I/O error @raise [Dry::Files::MissingTargetError] if `target` cannot be found in `path`

@see replace_last_line

@since 0.1.0 @api public

# File lib/dry/files.rb, line 331
def replace_first_line(path, target, replacement)
  content = adapter.readlines(path)
  content[index(content, path, target)] = newline(replacement)

  write(path, content)
end
replace_last_line(path, target, replacement) click to toggle source

Replace last line in `path` that contains `target` with `replacement`.

@param path [String,Pathname] the path to file @param target [String,Regexp] the target to replace @param replacement [String] the replacement

@raise [Dry::Files::IOError] in case of I/O error @raise [Dry::Files::MissingTargetError] if `target` cannot be found in `path`

@see replace_first_line

@since 0.1.0 @api public

# File lib/dry/files.rb, line 351
def replace_last_line(path, target, replacement)
  content = adapter.readlines(path)
  content[-index(content.reverse, path, target) - CONTENT_OFFSET] = newline(replacement)

  write(path, content)
end
touch(path) click to toggle source

Creates an empty file for the given path. All the intermediate directories are created. If the path already exists, it doesn't change the contents

@param path [String,Pathname] the path to file

@raise [Dry::Files::IOError] in case of I/O error

@since 0.1.0 @api public

# File lib/dry/files.rb, line 58
def touch(path)
  adapter.touch(path)
end
unshift(path, line) click to toggle source

Adds a new line at the top of the file

@param path [String,Pathname] the path to file @param line [String] the line to add

@raise [Dry::Files::IOError] in case of I/O error

@see append

@since 0.1.0 @api public

# File lib/dry/files.rb, line 290
def unshift(path, line)
  content = adapter.readlines(path)
  content.unshift(newline(line))

  write(path, content)
end
write(path, *content) click to toggle source

Creates a new file or rewrites the contents of an existing file for the given path and content All the intermediate directories are created.

@param path [String,Pathname] the path to file @param content [String, Array<String>] the content to write

@raise [Dry::Files::IOError] in case of I/O error

@since 0.1.0 @api public

# File lib/dry/files.rb, line 73
def write(path, *content)
  adapter.write(path, *content)
end

Private Instance Methods

_inject_line_after(path, target, contents, finder) click to toggle source

@since 0.1.0 @api private

# File lib/dry/files.rb, line 826
def _inject_line_after(path, target, contents, finder)
  content = adapter.readlines(path)
  i       = finder.call(content, path, target)

  content.insert(i + CONTENT_OFFSET, newline(contents))
  write(path, content)
end
_inject_line_before(path, target, contents, finder) click to toggle source

@since 0.1.0 @api private

# File lib/dry/files.rb, line 816
def _inject_line_before(path, target, contents, finder)
  content = adapter.readlines(path)
  i       = finder.call(content, path, target)

  content.insert(i, newline(contents))
  write(path, content)
end
_offset_block_lines(contents, offset) click to toggle source

@since 0.1.0 @api private

# File lib/dry/files.rb, line 836
def _offset_block_lines(contents, offset)
  contents.map do |line|
    if line.match?(NEW_LINE)
      line = line.split(NEW_LINE)
      _offset_block_lines(line, offset)
    else
      offset + line + NEW_LINE
    end
  end.join
end
index(content, path, target) click to toggle source

@since 0.1.0 @api private

# File lib/dry/files.rb, line 802
def index(content, path, target)
  line_number(content, target) or
    raise MissingTargetError.new(target, path)
end
line_number(content, target, finder: content.method(:index)) click to toggle source

@since 0.1.0 @api private

# File lib/dry/files.rb, line 849
def line_number(content, target, finder: content.method(:index))
  finder.call do |l|
    case target
    when ::String
      l.include?(target)
    when Regexp
      l =~ target
    end
  end
end
match?(content, target) click to toggle source

@since 0.1.0 @api private

# File lib/dry/files.rb, line 796
def match?(content, target)
  !line_number(content, target).nil?
end
newline(line = nil) click to toggle source

@since 0.1.0 @api private

# File lib/dry/files.rb, line 784
def newline(line = nil)
  "#{line}#{NEW_LINE}"
end
newline?(content) click to toggle source

@since 0.1.0 @api private

# File lib/dry/files.rb, line 790
def newline?(content)
  content.end_with?(NEW_LINE)
end
rindex(content, path, target) click to toggle source

@since 0.1.0 @api private

# File lib/dry/files.rb, line 809
def rindex(content, path, target)
  line_number(content, target, finder: content.method(:rindex)) or
    raise MissingTargetError.new(target, path)
end