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.1.0"

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



351
352
353
354
355
356
357
358
359
360
# File 'lib/dry/files.rb', line 351

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



166
167
168
# File 'lib/dry/files.rb', line 166

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

#chmod(path, mode) ⇒ Object

Sets UNIX permissions of the file at the given path.

Accepts permissions in numeric mode only, best provided as octal numbers matching the standard UNIX octal permission modes, such as ‘0o544` for a file writeable by its owner and readable by others, or `0o755` for a file writeable by its owner and executable by everyone.

Parameters:

  • path (String, Pathname)

    the path to the file

  • mode (Integer)

    the UNIX permissions mode

Raises:

Since:

  • 1.1.0



98
99
100
101
102
# File 'lib/dry/files.rb', line 98

def chmod(path, mode)
  raise Dry::Files::Error, "mode should be an integer (e.g. 0o755)" unless mode.is_a?(Integer)

  adapter.chmod(path, mode)
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



234
235
236
# File 'lib/dry/files.rb', line 234

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



246
247
248
# File 'lib/dry/files.rb', line 246

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



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

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



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

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



318
319
320
# File 'lib/dry/files.rb', line 318

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



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

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



128
129
130
# File 'lib/dry/files.rb', line 128

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



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

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



474
475
476
# File 'lib/dry/files.rb', line 474

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



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

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



584
585
586
587
588
589
590
591
592
593
594
# File 'lib/dry/files.rb', line 584

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



782
783
784
785
786
787
788
789
790
791
792
793
794
795
# File 'lib/dry/files.rb', line 782

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



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

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



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

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



113
114
115
# File 'lib/dry/files.rb', line 113

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



192
193
194
# File 'lib/dry/files.rb', line 192

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



219
220
221
# File 'lib/dry/files.rb', line 219

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



153
154
155
# File 'lib/dry/files.rb', line 153

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



137
138
139
# File 'lib/dry/files.rb', line 137

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



843
844
845
846
847
848
849
850
851
852
853
854
855
856
# File 'lib/dry/files.rb', line 843

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



807
808
809
810
811
812
813
# File 'lib/dry/files.rb', line 807

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



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

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



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

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



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

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