Class: Dry::Files

Inherits:
Object
  • Object
show all
Defined in:
lib/dry/files.rb,
lib/dry/files/path.rb,
lib/dry/files/error.rb,
lib/dry/files/adapter.rb,
lib/dry/files/version.rb,
lib/dry/files/file_system.rb,
lib/dry/files/memory_file_system.rb,
lib/dry/files/memory_file_system/node.rb

Overview

File manipulations

Since:

  • 0.1.0

Defined Under Namespace

Modules: Path Classes: Adapter, Delimiter, Error, FileSystem, IOError, MemoryFileSystem, MissingTargetError, NotMemoryFileError, UnknownMemoryNodeError

Constant Summary collapse

OPEN_MODE =

Since:

  • 0.3.0

::File::RDWR
WRITE_MODE =

Since:

  • 0.3.0

(::File::CREAT | ::File::WRONLY | ::File::TRUNC).freeze
VERSION =

Since:

  • 0.1.0

"1.0.1"

Instance Method Summary collapse

Constructor Details

#initialize(memory: false, adapter: Adapter.call(memory: memory)) ⇒ Dry::Files

Creates a new instance

Memory file system is experimental

Parameters:

  • memory (TrueClass, FalseClass) (defaults to: false)

    use in-memory, ephemeral file system

  • adapter (Dry::FileSystem) (defaults to: Adapter.call(memory: memory))

Since:

  • 0.1.0



36
37
38
# File 'lib/dry/files.rb', line 36

def initialize(memory: false, adapter: Adapter.call(memory: memory))
  @adapter = adapter
end

Instance Method Details

#append(path, contents) ⇒ Object

Adds a new line at the bottom of the file

Parameters:

  • path (String, Pathname)

    the path to file

  • contents (String)

    the contents to add

Raises:

See Also:

Since:

  • 0.1.0



332
333
334
335
336
337
338
339
340
341
# File 'lib/dry/files.rb', line 332

def append(path, contents)
  mkdir_p(path)
  touch(path)

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

  write(path, content)
end

#chdir(path, &blk) ⇒ Object

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

Parameters:

  • path (String, Pathname)

    the target directory

  • blk (Proc)

    the code to execute with the target directory

Raises:

Since:

  • 0.1.0



147
148
149
# File 'lib/dry/files.rb', line 147

def chdir(path, &blk)
  adapter.chdir(path, &blk)
end

#cp(source, destination) ⇒ Object

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

Parameters:

  • source (String, Pathname)

    the path to the source file

  • destination (String, Pathname)

    the path to the destination file

Raises:

Since:

  • 0.1.0



215
216
217
# File 'lib/dry/files.rb', line 215

def cp(source, destination)
  adapter.cp(source, destination)
end

#delete(path) ⇒ Object

Deletes given path (file).

Parameters:

  • path (String, Pathname)

    the path to file

Raises:

Since:

  • 0.1.0



227
228
229
# File 'lib/dry/files.rb', line 227

def delete(path)
  adapter.rm(path)
end

#delete_directory(path) ⇒ Object

Deletes given path (directory).

Parameters:

  • path (String, Pathname)

    the path to file

Raises:

Since:

  • 0.1.0



239
240
241
# File 'lib/dry/files.rb', line 239

def delete_directory(path)
  adapter.rm_rf(path)
end

#directory?(path) ⇒ TrueClass, FalseClass

Checks if ‘path` is a directory

Examples:

require "dry/files"

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

Dry::Files.new.directory?("missing_directory") # => false

Parameters:

  • path (String, Pathname)

    the path to directory

Returns:

  • (TrueClass, FalseClass)

    the result of the check

Since:

  • 0.1.0



279
280
281
# File 'lib/dry/files.rb', line 279

def directory?(path)
  adapter.directory?(path)
end

#executable?(path) ⇒ TrueClass, FalseClass

Checks if ‘path` is an executable

Examples:

require "dry/files"

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

Dry::Files.new.directory?("missing_file") # => false

Parameters:

  • path (String, Pathname)

    the path to file

Returns:

  • (TrueClass, FalseClass)

    the result of the check

Since:

  • 0.1.0



299
300
301
# File 'lib/dry/files.rb', line 299

def executable?(path)
  adapter.executable?(path)
end

#exist?(path) ⇒ TrueClass, FalseClass

Checks if ‘path` exist

Examples:

require "dry/files"

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

Dry::Files.new.exist?("missing_file") # => false

Parameters:

  • path (String, Pathname)

    the path to file

Returns:

  • (TrueClass, FalseClass)

    the result of the check

Since:

  • 0.1.0



259
260
261
# File 'lib/dry/files.rb', line 259

def exist?(path)
  adapter.exist?(path)
end

#expand_path(path, dir = pwd) ⇒ String

Converts a path to an absolute path.

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

Parameters:

  • path (String, Pathname)

    the path to the file

  • dir (String, Pathname) (defaults to: pwd)

    the base directory

Returns:

  • (String)

    the expanded path

Since:

  • 0.1.0



109
110
111
# File 'lib/dry/files.rb', line 109

def expand_path(path, dir = pwd)
  adapter.expand_path(path, dir)
end

#inject_line_after(path, target, contents) ⇒ Object

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

Parameters:

  • path (String, Pathname)

    the path to file

  • target (String, Regexp)

    the target to replace

  • contents (String)

    the contents to inject

Raises:

See Also:

Since:

  • 0.1.0



436
437
438
# File 'lib/dry/files.rb', line 436

def inject_line_after(path, target, contents)
  _inject_line_after(path, target, contents, method(:index))
end

#inject_line_after_last(path, target, contents) ⇒ Object

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

Parameters:

  • path (String, Pathname)

    the path to file

  • target (String, Regexp)

    the target to replace

  • contents (String)

    the contents to inject

Raises:

See Also:

Since:

  • 0.1.0



455
456
457
# File 'lib/dry/files.rb', line 455

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) ⇒ Object

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

Examples:

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

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

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

Parameters:

  • path (String, Pathname)

    the path to file

  • target (String, Regexp)

    the target matcher for Ruby block

  • contents (String, Array<String>)

    the contents to inject

Raises:

Since:

  • 0.1.0



683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
# File 'lib/dry/files.rb', line 683

def inject_line_at_block_bottom(path, target, *contents)
  content   = adapter.readlines(path)
  starting  = index(content, path, target)
  line      = content[starting]
  delimiter = if line.match?(INLINE_OPEN_BLOCK_MATCHER)
                INLINE_BLOCK_DELIMITER
              else
                BLOCK_DELIMITER
              end
  target    = content[starting..]
  ending    = closing_block_index(target, starting, path, line, delimiter)
  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) ⇒ Object

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

Examples:

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

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

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

Parameters:

  • path (String, Pathname)

    the path to file

  • target (String, Regexp)

    the target matcher for Ruby block

  • contents (String, Array<String>)

    the contents to inject

Raises:

Since:

  • 0.1.0



565
566
567
568
569
570
571
572
573
574
575
# File 'lib/dry/files.rb', line 565

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_at_class_bottom(path, target, *contents) ⇒ Object

Inject ‘contents` in `path` at the bottom of the Ruby class that matches `target`. The given `contents` will appear at the BOTTOM of the Ruby class.

Examples:

Inject a single line

require "dry/files"

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

File.read(path)
# # frozen_string_literal: true
#
# class Application
# end

# inject a single line
files.inject_line_at_class_bottom(path, /Application/, %(attr_accessor :name))

File.read(path)
# # frozen_string_literal: true
#
# class Application
#   attr_accessor :name
# end

Inject multiple lines

require "dry/files"

files = Dry::Files.new
path = "math.rb"

File.read(path)
# # frozen_string_literal: true
#
# class Math
# end

# inject multiple lines
files.inject_line_at_class_bottom(path,
                                  /Math/,
                                  ["def sum(a, b)", "  a + b", "end"])

File.read(path)
# # frozen_string_literal: true
#
# class Math
#   def sum(a, b)
#     a + b
#   end
# end

Parameters:

  • path (String, Pathname)

    the path to file

  • target (String, Regexp)

    the target matcher for Ruby class

  • contents (String, Array<String>)

    the contents to inject

Raises:

Since:

  • 0.4.0



763
764
765
766
767
768
769
770
771
772
773
774
775
776
# File 'lib/dry/files.rb', line 763

def inject_line_at_class_bottom(path, target, *contents)
  content   = adapter.readlines(path)
  starting  = index(content, path, target)
  line      = content[starting]
  target    = content[starting..]
  ending    = closing_class_index(target, starting, path, line, BLOCK_DELIMITER)
  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_before(path, target, contents) ⇒ Object

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

Parameters:

  • path (String, Pathname)

    the path to file

  • target (String, Regexp)

    the target to replace

  • contents (String)

    the contents to inject

Raises:

See Also:

Since:

  • 0.1.0



398
399
400
# File 'lib/dry/files.rb', line 398

def inject_line_before(path, target, contents)
  _inject_line_before(path, target, contents, method(:index))
end

#inject_line_before_last(path, target, contents) ⇒ Object

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

Parameters:

  • path (String, Pathname)

    the path to file

  • target (String, Regexp)

    the target to replace

  • contents (String)

    the contents to inject

Raises:

See Also:

Since:

  • 0.1.0



417
418
419
# File 'lib/dry/files.rb', line 417

def inject_line_before_last(path, target, contents)
  _inject_line_before(path, target, contents, method(:rindex))
end

#join(*path) ⇒ String

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

Parameters:

  • path (Array<String,Pathname>)

    path tokens

Returns:

  • (String)

    the joined path

Since:

  • 0.1.0



94
95
96
# File 'lib/dry/files.rb', line 94

def join(*path)
  adapter.join(*path)
end

#mkdir(path) ⇒ Object

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.

Examples:

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

Parameters:

  • path (String, Pathname)

    the path to directory

Raises:

See Also:

Since:

  • 0.1.0



173
174
175
# File 'lib/dry/files.rb', line 173

def mkdir(path)
  adapter.mkdir(path)
end

#mkdir_p(path) ⇒ Object

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.

Examples:

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

Parameters:

  • path (String, Pathname)

    the path to directory

Raises:

See Also:

Since:

  • 0.1.0



200
201
202
# File 'lib/dry/files.rb', line 200

def mkdir_p(path)
  adapter.mkdir_p(path)
end

#open(path, mode = OPEN_MODE, *args, &blk) {|the| ... } ⇒ File, Dry::Files::MemoryFileSystem::Node

Opens (or creates) a new file for both read/write operations

Parameters:

  • path (String)

    the target file

  • mode (String, Integer) (defaults to: OPEN_MODE)

    Ruby file open mode

  • args (Array<Object>)

    ::File.open args

  • blk (Proc)

    the block to yield

Yield Parameters:

Returns:

Raises:

Since:

  • 0.1.0



134
135
136
# File 'lib/dry/files.rb', line 134

def open(path, mode = OPEN_MODE, *args, &blk)
  adapter.open(path, mode, *args, &blk)
end

#pwdString

Returns the name of the current working directory.

Returns:

  • (String)

    the current working directory.

Since:

  • 0.1.0



118
119
120
# File 'lib/dry/files.rb', line 118

def pwd
  adapter.pwd
end

#read(path) ⇒ String

Read file content

TODO: allow buffered read

Parameters:

  • path (String, Pathname)

    the path to file

Returns:

  • (String)

    the file contents

Raises:

Since:

  • 0.1.0



52
53
54
# File 'lib/dry/files.rb', line 52

def read(path)
  adapter.read(path)
end

#remove_block(path, target) ⇒ Object

Removes ‘target` block from `path`

Examples:

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

Parameters:

  • path (String, Pathname)

    the path to file

  • target (String)

    the target block to remove

Raises:

Since:

  • 0.1.0



824
825
826
827
828
829
830
831
832
833
834
835
836
837
# File 'lib/dry/files.rb', line 824

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) ⇒ Object

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

Parameters:

  • path (String, Pathname)

    the path to file

  • target (String, Regexp)

    the target to remove

Raises:

Since:

  • 0.1.0



788
789
790
791
792
793
794
# File 'lib/dry/files.rb', line 788

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) ⇒ Object

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

Parameters:

  • path (String, Pathname)

    the path to file

  • target (String, Regexp)

    the target to replace

  • replacement (String)

    the replacement

Raises:

See Also:

Since:

  • 0.1.0



356
357
358
359
360
361
# File 'lib/dry/files.rb', line 356

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) ⇒ Object

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

Parameters:

  • path (String, Pathname)

    the path to file

  • target (String, Regexp)

    the target to replace

  • replacement (String)

    the replacement

Raises:

See Also:

Since:

  • 0.1.0



376
377
378
379
380
381
# File 'lib/dry/files.rb', line 376

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) ⇒ Object

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

Parameters:

  • path (String, Pathname)

    the path to file

Raises:

Since:

  • 0.1.0



66
67
68
# File 'lib/dry/files.rb', line 66

def touch(path)
  adapter.touch(path)
end

#unshift(path, line) ⇒ Object

Adds a new line at the top of the file

Parameters:

  • path (String, Pathname)

    the path to file

  • line (String)

    the line to add

Raises:

See Also:

Since:

  • 0.1.0



314
315
316
317
318
319
# File 'lib/dry/files.rb', line 314

def unshift(path, line)
  content = adapter.readlines(path)
  content.unshift(newline(line))

  write(path, content)
end

#write(path, *content) ⇒ Object

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

Parameters:

  • path (String, Pathname)

    the path to file

  • content (String, Array<String>)

    the content to write

Raises:

Since:

  • 0.1.0



81
82
83
# File 'lib/dry/files.rb', line 81

def write(path, *content)
  adapter.write(path, *content)
end