README.md
<div align="center">
<a href="https://ttytoolkit.org"><img width="130" src="https://github.com/piotrmurach/tty/raw/master/images/tty.png" alt="TTY Toolkit logo" /></a>
</div>
# TTY::File [![Gitter](https://badges.gitter.im/Join%20Chat.svg)][gitter]
[![Gem Version](https://badge.fury.io/rb/tty-file.svg)][gem]
[![Actions CI](https://github.com/piotrmurach/tty-file/workflows/CI/badge.svg?branch=master)][gh_actions_ci]
[![Build status](https://ci.appveyor.com/api/projects/status/og69rn550s4mt1q3?svg=true)][appveyor]
[![Maintainability](https://api.codeclimate.com/v1/badges/9ce2d164ea4835901ccd/maintainability)][codeclimate]
[![Coverage Status](https://coveralls.io/repos/github/piotrmurach/tty-file/badge.svg)][coverage]
[![Inline docs](http://inch-ci.org/github/piotrmurach/tty-file.svg?branch=master)][inchpages]
[gitter]: https://gitter.im/piotrmurach/tty
[gem]: http://badge.fury.io/rb/tty-file
[gh_actions_ci]: https://github.com/piotrmurach/tty-file/actions?query=workflow%3ACI
[appveyor]: https://ci.appveyor.com/project/piotrmurach/tty-file
[codeclimate]: https://codeclimate.com/github/piotrmurach/tty-file/maintainability
[coverage]: https://coveralls.io/github/piotrmurach/tty-file
[inchpages]: http://inch-ci.org/github/piotrmurach/tty-file
> File manipulation utility methods.
**TTY::File** provides independent file utilities component for [TTY](https://github.com/piotrmurach/tty) toolkit.
## Motivation
Though Ruby's `File` and `FileUtils` libraries provide very robust apis for dealing with files, this library aims to provide a level of abstraction that is much more convenient, with useful logging capabilities.
## Installation
Add this line to your application's Gemfile:
```ruby
gem "tty-file"
```
And then execute:
$ bundle
Or install it yourself as:
$ gem install tty-file
## Contents
* [1. Usage](#1-usage)
* [2. Interface](#2-interface)
* [2.1. binary?](#21-binary)
* [2.2. checksum_file](#22-checksum_file)
* [2.3. chmod](#23-chmod)
* [2.4. copy_file](#24-copy_file)
* [2.5. create_file](#25-create_file)
* [2.6. copy_dir](#26-copy_dir)
* [2.7. create_dir](#27-create_dir)
* [2.8. diff_files](#28-diff_files)
* [2.9. download_file](#29-download_file)
* [2.10. inject_into_file](#210-inject_into_file)
* [2.11. replace_in_file](#211-replace_in_file)
* [2.12. append_to_file](#212-append_to_file)
* [2.13. prepend_to_file](#213-prepend_to_file)
* [2.14. remove_file](#214-remove_file)
* [2.15. tail_file](#215-tail_file)
## 1. Usage
```ruby
TTY::File.replace_in_file("Gemfile", /gem 'rails'/, "gem 'hanami'")
```
## 2. Interface
The following methods are available for creating and manipulating files.
If you wish to silence verbose output use `verbose: false`. Similarly if you wish to run action without actually triggering any action use `noop: true`.
### 2.1. binary?
To check whether a file is a binary file, i.e. image, executable etc. do:
```ruby
TTY::File.binary?("image.png") # => true
```
### 2.2. checksum_file
To generate a checksum for a file, IO object, or String, use `checksum_file`. By default the `MD5` algorithm is used, which can be changed by passing a second argument.
Among the supported message digest algorithms are:
* `sha`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`
* `md2`, `md4`, `md5`
For example, to create a digest for a string using `SHA1` do:
```ruby
TTY::File.checksum_file("Some content\nThe end", "sha1")
# => "289388f187404135e6c15b21460442cf867180dd"
```
### 2.3. chmod
To change file modes use `chmod`, like so:
```ruby
TTY::File.chmod("filename.rb", 0777)
```
There are a number of constants available to represent common mode bits such as `TTY::File::U_R` and `TTY::File::O_X`, and they can be used as follows:
```ruby
TTY::File.chmod("filename.rb", TTY::File::U_R | TTY::File::O_X)
```
Apart from traditional octal number definition for file permissions, you can use the more convenient permission notation used by the Unix `chmod` command:
```ruby
TTY::File.chmod("filename.rb", "u=wrx,g+x")
```
The `u`, `g`, and `o` specify the user, group, and other parts of the mode bits. The `a` symbol is equivalent to `ugo`.
### 2.4. copy_file
Copies a file's contents from a relative source to a relative destination.
```ruby
TTY::File.copy_file "Gemfile", "Gemfile.bak"
```
If you provide a block then the file content is yielded:
```ruby
TTY::File.copy_file("Gemfile", "app/Gemfile") do |content|
"https://rubygems.org\n" + content
end
```
If the source file is an `ERB` template then you can provide a `:context` in which the file gets evaluated, or if `TTY::File` gets included as a module then appropriate object context will be used by default. To use `:context` do:
```ruby
variables = OpenStruct.new
variables[:foo] = "bar"
TTY::File.copy_file("templates/application.html.erb", context: variables)
```
You can also specify the template name surrounding any dynamic variables with `%` to be evaluated:
```ruby
variables = OpenStruct.new
variables[:file_name] = "foo"
TTY::File.copy_file("templates/%file_name%.rb", context: variables)
# => Creates templates/foo.rb
```
If the destination is a directory, then copies source inside that directory.
```ruby
TTY::File.copy_file "docs/README.md", "app"
```
If the destination file already exists, a prompt menu will be displayed to enquire about action:
If you wish to preserve original owner, group, permission and modified time use `:preserve` option:
```ruby
TTY::File.copy_file "docs/README.md", "app", preserve: true
```
### 2.5. create_file
To create a file at a given destination with some content use `create_file`:
```ruby
TTY::File.create_file "file-a/README.md", content
```
On collision with already existing file, a menu gets displayed:
```
collision examples/file-a
Overwrite examples/file-a? (enter "h" for help) [y,d,n,q,h]
```
The `d` option allows to compare the changes:
```
--- a/examples/file-a
+++ b/examples/file-a
@@ -1,8 +1,9 @@
aaaaa
bbbbb
-ccccc
+xxxxx
+
ddddd
eeeee
fffff
-ggggg
+yyyyy
Overwrite examples/file-a? (enter "h" for help) [y,d,n,q,h]
````
You can force to always overwrite file with `:force` option or always skip by providing `:skip`.
There is [examples/overwrite.rb](examples/overwrite.rb) that demonstrates diffing file with new content.
### 2.6. copy_dir
To recursively copy a directory of files from source to destination location use `copy_directory` or its alias `copy_dir`.
Assuming you have the following directory structure:
```ruby
# doc/
# subcommands/
# command.rb.erb
# README.md
# %name%.rb
```
You can copy `doc` folder to `docs` by invoking:
```ruby
TTY::File.copy_directory("doc", "docs", context: ...)
```
The `context` needs to respond to `name` message and given it returns `foo` value the following directory gets created:
```ruby
# docs/
# subcommands/
# command.rb
# README.md
# foo.rb
```
If you only need to copy top level files use option `recursive: false`:
```ruby
TTY::File.copy_directory("doc", "docs", recursive: false)
```
By passing `:exclude` option you can instruct the method to ignore any files including the given pattern:
```ruby
TTY::File.copy_directory("doc", "docs", exclude: "subcommands")
```
### 2.7. create_dir
To create directory use `create_directory` or its alias `create_dir` passing as a first argument file path:
```ruby
TTY::File.create_dir("/path/to/directory")
```
Or a data structure describing the directory tree including any files with or without content:
```ruby
tree =
"app" => [
"README.md",
["Gemfile", "gem 'tty-file'"],
"lib" => [
"cli.rb",
["file_utils.rb", "require 'tty-file'"]
]
"spec" => []
]
```
```ruby
TTY::File.create_dir(tree)
# =>
# app
# app/README.md
# app/Gemfile
# app/lib
# app/lib/cli.rb
# app/lib/file_utils.rb
# app/spec
```
As a second argument you can provide a parent directory, otherwise current directory will be assumed:
```ruby
TTY::File.create_dir(tree, "/path/to/parent/dir")
```
### 2.8. diff_files
To compare files line by line in a system independent way use `diff`, or `diff_files`:
```ruby
print TTY::File.diff_files("file-a", "file-b")
```
Printing output to console would result in:
```
diff examples/file-a and examples/file-b
--- examples/file-a
+++ examples/file-b
@@ -1,8 +1,9 @@
aaaaa
bbbbb
-ccccc
+xxxxx
+
ddddd
eeeee
fffff
-ggggg
+yyyyy
```
You can also pass additional parameters such as:
* `:format` - accepted values are `:unified`, `:old`, `:context` and `:ed`. Defaults to `:unified` as seen in the output above - similar to git tool.
* `:lines` - how many extra lines to include in the output around the compared lines. Defaults to `3` lines.
* `:threshold` - set maximum file size in bytes. By default files larger than `10Mb` are no processed.
* `:header` - controls display of two-line files comparison. By default `true`.
Changing format to `:old`, removing context lines and skipping log output:
```ruby
TTY::File.diff_files("file_a", "file_b", format: :old, lines: 0, verbose: false)
```
Results in the following output:
```
<<< examples/file-a
>>> examples/file-b
3c3,4
< ccccc
---
> xxxxx
>
7c8
< ggggg
---
> yyyyy
```
In addition, you can perform a comparison between a file and a string or between two strings. For example, comparing file with content:
```ruby
TTY::File.diff_files("file-a", "new\nlong\ntext")
```
Will output:
```
diff a/examples/file-a and b/examples/file-a
--- a/examples/file-a
+++ b/examples/file-a
@@ -1,8 +1,4 @@
-aaaaa
-bbbbb
-ccccc
-ddddd
-eeeee
-fffff
-ggggg
+new
+long
+text
````
Please run [examples/diff.rb](examples/diff.rb) to see how output works.
### 2.9. download_file
To download a content from a given address and to save at a given relative location do:
```ruby
TTY::File.download_file("https://gist.github.com/4701967", "doc/README.md")
```
If you pass a block then the content will be yielded to allow modification:
```ruby
TTY::File.download_file("https://gist.github.com/4701967", "doc/README.md") do |content|
content.gsub("\n", " ")
end
```
By default `download_file` will follow maximum 3 redirects. This can be changed by passing `:limit` option:
```ruby
TTY::File.download_file("https://gist.github.com/4701967", "doc/README.md", limit: 5)
# => raises TTY::File::DownloadError
```
### 2.10. inject_into_file
Inject content into a file at a given location and return `true` when performed successfully, `false` otherwise.
```ruby
TTY::File.inject_into_file "filename.rb", "text to add", after: "Code below this line\n"
```
Or using a block:
```ruby
TTY::File.inject_into_file "filename.rb", after: "Code below this line\n" do
"text to add"
end
```
You can also use Regular Expressions in `:after` or `:before` to match file location.
By default, this method will always inject content into file, regardless whether it is already present or not. To change this pass `:force` set to `false` to perform check before actually inserting text:
```ruby
TTY::File.inject_into_file("filename.rb", "text to add", after: "Code below this line\n"
```
Alternatively, use `safe_inject_into_file` to check if the text can be safely inserted.
```ruby
TTY::File.safe_inject_into_file("Gemfile", "gem 'tty'")
```
The [append_to_file](#212-append_to_file) and [prepend_to_file](#213-prepend_to_file) allow you to add content at the end and the begging of a file.
### 2.11. replace_in_file
Replace content of a file matching condition by calling `replace_in_file` or `gsub_file`, which returns `true` when substitutions are performed successfully, `false` otherwise.
```ruby
TTY::File.replace_in_file "filename.rb", /matching condition/, "replacement"
```
The replacement content can be provided in a block
```ruby
TTY::File.gsub_file "filename.rb", /matching condition/ do
"replacement"
end
```
### 2.12. append_to_file
Appends text to a file and returns `true` when performed successfully, `false` otherwise. You can provide the text as a second argument:
```ruby
TTY::File.append_to_file("Gemfile", "gem 'tty'")
```
Or inside a block:
```ruby
TTY::File.append_to_file("Gemfile") do
"gem 'tty'"
end
```
By default, this method will always append content regardless whether it is already present or not. To change this pass `:force` set to `false` to perform check before actually appending:
```ruby
TTY::File.append_to_file("Gemfile", "gem 'tty'", force: false)
```
Alternatively, use `safe_append_to_file` to check if the text can be safely appended.
```ruby
TTY::File.safe_append_to_file("Gemfile", "gem 'tty'")
```
### 2.13. prepend_to_file
Prepends text to a file and returns `true` when performed successfully, `false` otherwise. You can provide the text as a second argument:
```ruby
TTY::File.prepend_to_file("Gemfile", "gem 'tty'")
```
Or inside a block:
```ruby
TTY::File.prepend_to_file("Gemfile") do
"gem 'tty'"
end
```
By default, this method will always prepend content regardless whether it is already present or not. To change this pass `:force` set to `false` to perform check before actually prepending:
```ruby
TTY::File.prepend_to_file("Gemfile", "gem 'tty'", force: false)
```
Alternatively, use `safe_prepend_to_file` to check if the text can be safely appended.
```ruby
TTY::File.safe_prepend_to_file("Gemfile", "gem 'tty'")
```
### 2.14. remove_file
To remove a file do:
```ruby
TTY::File.remove_file "doc/README.md"
```
You can also pass in `:force` to remove file ignoring any errors:
```ruby
TTY::File.remove_file "doc/README.md", force: true
```
### 2.15. tail_file
To read the last 10 lines from a file do:
```ruby
TTY::File.tail_file "doc/README.md"
# => ["## Copyright", "Copyright (c) 2016-2017", ...]
```
You can also pass a block:
```ruby
TTY::File.tail_file("doc/README.md") do |line|
puts line
end
```
To change how many lines are read pass a second argument:
```ruby
TTY::File.tail_file("doc/README.md", 15)
```
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/piotrmurach/tty-file. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
## License
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
## Copyright
Copyright (c) 2016 Piotr Murach. See LICENSE for further details.