tty-file 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +9 -9
- data/CHANGELOG.md +18 -0
- data/Gemfile +3 -3
- data/README.md +133 -23
- data/lib/tty/file/create_file.rb +14 -13
- data/lib/tty/file/digest_file.rb +38 -0
- data/lib/tty/file/version.rb +1 -1
- data/lib/tty/file.rb +206 -38
- data/tty-file.gemspec +3 -3
- metadata +9 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24159965082063c18c0538a86d536db913ac05ee
|
4
|
+
data.tar.gz: 4b751ed2950b3f4725333011ce9351dd3d1a1afe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e08fc675ca19202aeaa3bbbba1c4236253e8f10355354889e384b41f63cc47e71160ecaa3945fcec062cad473fd9f401fcd7b8c71a2c406a5c28a2c625a2c8c0
|
7
|
+
data.tar.gz: 79793d79c75630d2e81228fbbc78ee023d81ba2c425d1dcd4896ea2f72813c363c58b0c39f6b0947f83edcd7f156cb292209bb7ccfbebe294f294195e9aa715f
|
data/.travis.yml
CHANGED
@@ -2,24 +2,24 @@
|
|
2
2
|
language: ruby
|
3
3
|
sudo: false
|
4
4
|
cache: bundler
|
5
|
-
script: "bundle exec rake ci"
|
5
|
+
script: "bundle update && bundle exec rake ci"
|
6
6
|
rvm:
|
7
7
|
- 2.0.0
|
8
8
|
- 2.1.10
|
9
9
|
- 2.2.5
|
10
10
|
- 2.3.1
|
11
|
+
- 2.4.0
|
11
12
|
- ruby-head
|
12
|
-
-
|
13
|
+
- jruby-9.1.1.0
|
14
|
+
- rbx-3
|
15
|
+
env:
|
16
|
+
global:
|
17
|
+
- JRUBY_OPTS=''
|
13
18
|
matrix:
|
14
|
-
include:
|
15
|
-
- rvm: jruby
|
16
|
-
env: JRUBY_OPTS='--server -Xcompile.invokedynamic=false -Xcompat.version=2.0'
|
17
|
-
- rvm: jruby-9.1.2.0
|
18
|
-
env: JRUBY_OPTS='--server -Xcompile.invokedynamic=false'
|
19
19
|
allow_failures:
|
20
20
|
- rvm: ruby-head
|
21
|
-
- rvm:
|
22
|
-
- rvm:
|
21
|
+
- rvm: rbx-3
|
22
|
+
- rvm: jruby-9.1.1.0
|
23
23
|
fast_finish: true
|
24
24
|
branches:
|
25
25
|
only: master
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,25 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
+
## [v0.2.0] - 2017-01-22
|
4
|
+
|
5
|
+
### Added
|
6
|
+
* Add #checksum_file to generate checksum for a file, IO object or String
|
7
|
+
* Add #create_dir to create directory structure with directories and files
|
8
|
+
* Add #copy_dir to copy directory recurisvely
|
9
|
+
* Add #escape_glob_path for escaping glob characters in a path
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
* Change #binary? to only read max 4Kb of file
|
13
|
+
* Change CreateFile to accept context in constructor
|
14
|
+
* Change to separate config_options from utility options
|
15
|
+
|
16
|
+
### Fixed
|
17
|
+
* Fix all aliases being incorrectly defined
|
18
|
+
* Fix #copy_file to stop appending to source paths
|
19
|
+
|
3
20
|
## [v0.1.0] - 2016-11-06
|
4
21
|
|
5
22
|
* Initial implementation and release
|
6
23
|
|
24
|
+
[v0.2.0]: https://github.com/piotrmurach/tty-file/compare/v0.1.0...v0.2.0
|
7
25
|
[v0.1.0]: https://github.com/piotrmurach/tty-file/compare/v0.1.0
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -39,16 +39,19 @@ Or install it yourself as:
|
|
39
39
|
* [1. Usage](#1-usage)
|
40
40
|
* [2. Interface](#2-interface)
|
41
41
|
* [2.1. binary?](#21-binary)
|
42
|
-
* [2.2.
|
43
|
-
* [2.3.
|
44
|
-
* [2.4.
|
45
|
-
* [2.5.
|
46
|
-
* [2.6.
|
47
|
-
* [2.7.
|
48
|
-
* [2.8.
|
49
|
-
* [2.9.
|
50
|
-
* [2.10.
|
51
|
-
* [2.11.
|
42
|
+
* [2.2. checksum_file](#22-checksum_file)
|
43
|
+
* [2.3. chmod](#23-chmod)
|
44
|
+
* [2.4. copy_file](#24-copy_file)
|
45
|
+
* [2.5. create_file](#25-create_file)
|
46
|
+
* [2.6. copy_dir](#26-copy_dir)
|
47
|
+
* [2.7. create_dir](#27-create_dir)
|
48
|
+
* [2.8. diff](#28-diff)
|
49
|
+
* [2.9. download_file](#29-download_file)
|
50
|
+
* [2.10. inject_into_file](#210-inject_into_file)
|
51
|
+
* [2.11. replace_in_file](#211-replace_in_file)
|
52
|
+
* [2.12. append_to_file](#212-apend_to_file)
|
53
|
+
* [2.13. prepend_to_file](#213-prepend_to_file)
|
54
|
+
* [2.14. remove_file](#214-remove_file)
|
52
55
|
|
53
56
|
## 1. Usage
|
54
57
|
|
@@ -70,7 +73,23 @@ To check whether a file is a binary file, i.e. image, executable etc. do:
|
|
70
73
|
TTY::File.binary?('image.png') # => true
|
71
74
|
```
|
72
75
|
|
73
|
-
### 2.2.
|
76
|
+
### 2.2. checksum_file
|
77
|
+
|
78
|
+
To generate checksum for a file, IO object or String use `checksum_file`. By default `MD5` algorithm is used which can be changed by passing second argument.
|
79
|
+
|
80
|
+
Among supported message digest algorithms are:
|
81
|
+
|
82
|
+
* `sha`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`
|
83
|
+
* `md2`, `md4`, `md5`
|
84
|
+
|
85
|
+
For example, to create digest for string using `SHA1` do:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
TTY::File.checksum_file("Some content\nThe end", 'sha1')
|
89
|
+
# => "289388f187404135e6c15b21460442cf867180dd"
|
90
|
+
```
|
91
|
+
|
92
|
+
### 2.3. chmod
|
74
93
|
|
75
94
|
To change file modes use `chmod` like so:
|
76
95
|
|
@@ -92,7 +111,7 @@ TTY::File.chmod('filename.rb', 'u=wrx,g+x')
|
|
92
111
|
|
93
112
|
The `u`, `g`, and `o` specify the user, group, and other parts of the mode bits. The `a` symbol is equivalent to `ugo`.
|
94
113
|
|
95
|
-
### 2.
|
114
|
+
### 2.4. copy_file
|
96
115
|
|
97
116
|
Copies a file content from relative source to relative destination.
|
98
117
|
|
@@ -141,7 +160,7 @@ If you wish to preserve original owner, group, permission and modified time use
|
|
141
160
|
TTY::File.copy_file 'docs/README.md', 'app', preserve: true
|
142
161
|
```
|
143
162
|
|
144
|
-
### 2.
|
163
|
+
### 2.5. create_file
|
145
164
|
|
146
165
|
To create a file at a given destination with the given content use `create_file`:
|
147
166
|
|
@@ -153,9 +172,92 @@ On collision with already existing file, a menu is displayed:
|
|
153
172
|
|
154
173
|
You can force to always overwrite file with `:force` option or always skip by providing `:skip`.
|
155
174
|
|
156
|
-
### 2.
|
175
|
+
### 2.6. copy_dir
|
176
|
+
|
177
|
+
To recursively copy a directory of files from source to destination location use `copy_directory` or its alias 'copy_dir'.
|
178
|
+
|
179
|
+
Assuming you have the following directory structure:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
# doc/
|
183
|
+
# subcommands/
|
184
|
+
# command.rb.erb
|
185
|
+
# README.md
|
186
|
+
# %name%.rb
|
187
|
+
```
|
188
|
+
|
189
|
+
you can copy `doc` folder to `docs` by invoking:
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
TTY::File.copy_directory('doc', 'docs', context: ...)
|
193
|
+
```
|
194
|
+
|
195
|
+
The `context` needs to respond to `name` message and given it returns `foo` value the following directory gets created:
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
# docs/
|
199
|
+
# subcommands/
|
200
|
+
# command.rb
|
201
|
+
# README.md
|
202
|
+
# foo.rb
|
203
|
+
```
|
204
|
+
|
205
|
+
If you only need to copy top level files use option `recursive: false`:
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
TTY::File.copy_directory('doc', 'docs', recursive: false)
|
209
|
+
```
|
210
|
+
|
211
|
+
By passing `:exclude` option you can instruct the method to ignore any files including the given pattern:
|
157
212
|
|
158
|
-
|
213
|
+
```ruby
|
214
|
+
TTY::File.copy_directory('doc', 'docs', exclude: 'subcommands')
|
215
|
+
```
|
216
|
+
|
217
|
+
### 2.7. create_dir
|
218
|
+
|
219
|
+
To create directory use `create_directory` or its alias `create_dir` passing as a first argument file path:
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
TTY::File.create_dir(/path/to/directory)
|
223
|
+
```
|
224
|
+
|
225
|
+
or a data structure describing the directory tree including any files with or without content:
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
tree =
|
229
|
+
'app' => [
|
230
|
+
'README.md',
|
231
|
+
['Gemfile', "gem 'tty-file'"],
|
232
|
+
'lib' => [
|
233
|
+
'cli.rb',
|
234
|
+
['file_utils.rb', "require 'tty-file'"]
|
235
|
+
]
|
236
|
+
'spec' => []
|
237
|
+
]
|
238
|
+
```
|
239
|
+
|
240
|
+
```ruby
|
241
|
+
TTY::File.create_dir(tree)
|
242
|
+
# =>
|
243
|
+
# app
|
244
|
+
# app/README.md
|
245
|
+
# app/Gemfile
|
246
|
+
# app/lib
|
247
|
+
# app/lib/cli.rb
|
248
|
+
# app/lib/file_utils.rb
|
249
|
+
# app/spec
|
250
|
+
```
|
251
|
+
|
252
|
+
As a second argument you can provide a parent directory, otherwise current directory will be assumed:
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
TTy::File.create_dir(tree, '/path/to/parent/dir')
|
256
|
+
```
|
257
|
+
|
258
|
+
### 2.8. diff
|
259
|
+
|
260
|
+
To compare files line by line in a system independent way use `diff`, or `diff_files`:
|
159
261
|
|
160
262
|
```ruby
|
161
263
|
TTY::File.diff('file_a', 'file_b')
|
@@ -194,7 +296,7 @@ Equally, you can perform a comparison between a file content and a string conten
|
|
194
296
|
TTY::File.diff('/path/to/file', 'some long text')
|
195
297
|
```
|
196
298
|
|
197
|
-
### 2.
|
299
|
+
### 2.9. download_file
|
198
300
|
|
199
301
|
To download a content from a given address and to save at a given relative location do:
|
200
302
|
|
@@ -217,7 +319,7 @@ TTY::File.download_file("https://gist.github.com/4701967", "doc/README.md", limi
|
|
217
319
|
# => raises TTY::File::DownloadError
|
218
320
|
```
|
219
321
|
|
220
|
-
### 2.
|
322
|
+
### 2.10. inject_into_file
|
221
323
|
|
222
324
|
Inject content into a file at a given location
|
223
325
|
|
@@ -229,15 +331,23 @@ end
|
|
229
331
|
|
230
332
|
You can also use Regular Expressions in `:after` or `:before` to match file location. The `append_to_file` and `prepend_to_file` allow you to add content at the end and the begging of a file.
|
231
333
|
|
232
|
-
### 2.
|
334
|
+
### 2.11. replace_in_file
|
233
335
|
|
234
|
-
Replace content of a file matching condition
|
336
|
+
Replace content of a file matching condition by calling `replace_in_file` or `gsub_file`
|
235
337
|
|
236
338
|
```ruby
|
237
339
|
TTY::File.replace_in_file 'filename.rb', /matching condition/, 'replacement'
|
238
340
|
```
|
239
341
|
|
240
|
-
|
342
|
+
The replacement content can be provided in a block
|
343
|
+
|
344
|
+
```ruby
|
345
|
+
TTY::File.gsub_file 'filename.rb', /matching condition/ do
|
346
|
+
'replacement'
|
347
|
+
end
|
348
|
+
```
|
349
|
+
|
350
|
+
### 2.12. append_to_file
|
241
351
|
|
242
352
|
Appends text to a file. You can provide the text as a second argument:
|
243
353
|
|
@@ -253,7 +363,7 @@ TTY::File.append_to_file('Gemfile') do
|
|
253
363
|
end
|
254
364
|
```
|
255
365
|
|
256
|
-
### 2.
|
366
|
+
### 2.13. prepend_to_file
|
257
367
|
|
258
368
|
Prepends text to a file. You can provide the text as a second argument:
|
259
369
|
|
@@ -269,7 +379,7 @@ TTY::File.prepend_to_file('Gemfile') do
|
|
269
379
|
end
|
270
380
|
```
|
271
381
|
|
272
|
-
### 2.
|
382
|
+
### 2.14. remove_file
|
273
383
|
|
274
384
|
To remove a file do:
|
275
385
|
|
@@ -299,4 +409,4 @@ The gem is available as open source under the terms of the [MIT License](http://
|
|
299
409
|
|
300
410
|
## Copyright
|
301
411
|
|
302
|
-
Copyright (c) 2016 Piotr Murach. See LICENSE for further details.
|
412
|
+
Copyright (c) 2016-2017 Piotr Murach. See LICENSE for further details.
|
data/lib/tty/file/create_file.rb
CHANGED
@@ -1,17 +1,28 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
require 'forwardable'
|
4
|
+
|
3
5
|
module TTY
|
4
6
|
module File
|
5
7
|
class CreateFile
|
8
|
+
extend Forwardable
|
9
|
+
|
6
10
|
attr_reader :relative_path, :content, :options, :prompt
|
7
11
|
|
8
|
-
|
12
|
+
def_delegators "@base", :log_status
|
13
|
+
|
14
|
+
def initialize(base, relative_path, content, options = {})
|
15
|
+
@base = base
|
9
16
|
@content = content
|
10
17
|
@options = options
|
11
18
|
@relative_path = convert_encoded_path(relative_path)
|
12
19
|
@prompt = TTY::Prompt.new
|
13
20
|
end
|
14
21
|
|
22
|
+
def context
|
23
|
+
options[:context] || @base
|
24
|
+
end
|
25
|
+
|
15
26
|
def exist?
|
16
27
|
::File.exist?(relative_path)
|
17
28
|
end
|
@@ -20,10 +31,6 @@ module TTY
|
|
20
31
|
::File.binread(relative_path) == content
|
21
32
|
end
|
22
33
|
|
23
|
-
def log_status(*args)
|
24
|
-
TTY::File.log_status(*args)
|
25
|
-
end
|
26
|
-
|
27
34
|
# Create a file
|
28
35
|
#
|
29
36
|
# @api public
|
@@ -37,10 +44,6 @@ module TTY
|
|
37
44
|
|
38
45
|
protected
|
39
46
|
|
40
|
-
def context
|
41
|
-
options[:context]
|
42
|
-
end
|
43
|
-
|
44
47
|
def convert_encoded_path(filename)
|
45
48
|
filename.gsub(/%(.*?)%/) do |match|
|
46
49
|
method = $1.strip
|
@@ -69,8 +72,7 @@ module TTY
|
|
69
72
|
end
|
70
73
|
else
|
71
74
|
log_status(:create, relative_path, options.fetch(:verbose, true), :green)
|
72
|
-
|
73
|
-
yield
|
75
|
+
yield unless options[:noop]
|
74
76
|
end
|
75
77
|
end
|
76
78
|
|
@@ -81,8 +83,7 @@ module TTY
|
|
81
83
|
choices = [
|
82
84
|
{ key: 'y', name: 'yes, overwrite', value: :yes },
|
83
85
|
{ key: 'n', name: 'no, do not overwrite', value: :no },
|
84
|
-
{ key: 'q', name: 'quit, abort', value: :quit }
|
85
|
-
{ key: 'd', name: 'diff, compare files line by line', value: :diff }
|
86
|
+
{ key: 'q', name: 'quit, abort', value: :quit }
|
86
87
|
]
|
87
88
|
answer = prompt.expand("Overwrite #{relative_path}?", choices)
|
88
89
|
interpret_answer(answer)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
require 'openssl'
|
5
|
+
|
6
|
+
module TTY
|
7
|
+
module File
|
8
|
+
class DigestFile
|
9
|
+
attr_reader :source
|
10
|
+
|
11
|
+
def initialize(source, mode, options)
|
12
|
+
@source = source
|
13
|
+
@digest = OpenSSL::Digest.new(mode)
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
if ::FileTest.file?(source.to_s)
|
18
|
+
::File.open(source, 'rb') { |f| checksum_io(f, @digest) }
|
19
|
+
else
|
20
|
+
non_file = source
|
21
|
+
if non_file.is_a?(String)
|
22
|
+
non_file = StringIO.new(non_file)
|
23
|
+
end
|
24
|
+
if non_file.is_a?(StringIO)
|
25
|
+
checksum_io(non_file, @digest)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def checksum_io(io, digest)
|
31
|
+
while (chunk = io.read(1024 * 8))
|
32
|
+
digest << chunk
|
33
|
+
end
|
34
|
+
digest.hexdigest
|
35
|
+
end
|
36
|
+
end # DigestFile
|
37
|
+
end # File
|
38
|
+
end # TTY
|
data/lib/tty/file/version.rb
CHANGED
data/lib/tty/file.rb
CHANGED
@@ -4,8 +4,10 @@ require 'pastel'
|
|
4
4
|
require 'tty-prompt'
|
5
5
|
require 'erb'
|
6
6
|
require 'tempfile'
|
7
|
+
require 'pathname'
|
7
8
|
|
8
9
|
require 'tty/file/create_file'
|
10
|
+
require 'tty/file/digest_file'
|
9
11
|
require 'tty/file/download_file'
|
10
12
|
require 'tty/file/differ'
|
11
13
|
require 'tty/file/version'
|
@@ -16,6 +18,8 @@ module TTY
|
|
16
18
|
module_function(method)
|
17
19
|
private_class_method(method)
|
18
20
|
end
|
21
|
+
# Invalid path erorr
|
22
|
+
InvalidPathError = Class.new(ArgumentError)
|
19
23
|
|
20
24
|
# File permissions
|
21
25
|
U_R = 0400
|
@@ -43,13 +47,14 @@ module TTY
|
|
43
47
|
# binary?('image.jpg') # => true
|
44
48
|
#
|
45
49
|
# @return [Boolean]
|
46
|
-
# Returns `true` if the file is binary
|
50
|
+
# Returns `true` if the file is binary, `false` otherwise
|
47
51
|
#
|
48
52
|
# @api public
|
49
53
|
def binary?(relative_path)
|
50
54
|
bytes = ::File.stat(relative_path).blksize
|
51
55
|
bytes = 4096 if bytes > 4096
|
52
|
-
buffer = ::File.read(relative_path) || ''
|
56
|
+
buffer = ::File.read(relative_path, bytes, 0) || ''
|
57
|
+
buffer = buffer.force_encoding(Encoding.default_external)
|
53
58
|
begin
|
54
59
|
return buffer !~ /\A[\s[[:print:]]]*\z/m
|
55
60
|
rescue ArgumentError => error
|
@@ -59,11 +64,40 @@ module TTY
|
|
59
64
|
end
|
60
65
|
module_function :binary?
|
61
66
|
|
67
|
+
# Create checksum for a file, io or string objects
|
68
|
+
#
|
69
|
+
# @param [File,IO,String] source
|
70
|
+
# the source to generate checksum for
|
71
|
+
# @param [String] mode
|
72
|
+
# @param [Hash[Symbol]] options
|
73
|
+
# @option options [String] :noop
|
74
|
+
# No operation
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# checksum_file('/path/to/file')
|
78
|
+
#
|
79
|
+
# @example
|
80
|
+
# checksum_file('Some string content', 'md5')
|
81
|
+
#
|
82
|
+
# @return [String]
|
83
|
+
# the generated hex value
|
84
|
+
#
|
85
|
+
# @api public
|
86
|
+
def checksum_file(source, *args, **options)
|
87
|
+
mode = args.size.zero? ? 'sha256' : args.pop
|
88
|
+
digester = DigestFile.new(source, mode, options)
|
89
|
+
digester.call unless options[:noop]
|
90
|
+
end
|
91
|
+
module_function :checksum_file
|
92
|
+
|
62
93
|
# Change file permissions
|
63
94
|
#
|
64
95
|
# @param [String] relative_path
|
65
96
|
# @param [Integer,String] permisssions
|
66
97
|
# @param [Hash[Symbol]] options
|
98
|
+
# @option options [Symbol] :noop
|
99
|
+
# @option options [Symbol] :verbose
|
100
|
+
# @option options [Symbol] :force
|
67
101
|
#
|
68
102
|
# @example
|
69
103
|
# chmod('Gemfile', 0755)
|
@@ -75,7 +109,7 @@ module TTY
|
|
75
109
|
# chmod('Gemfile', 'u+x,g+x')
|
76
110
|
#
|
77
111
|
# @api public
|
78
|
-
def chmod(relative_path, permissions, options
|
112
|
+
def chmod(relative_path, permissions, **options)
|
79
113
|
mode = ::File.lstat(relative_path).mode
|
80
114
|
if permissions.to_s =~ /\d+/
|
81
115
|
mode = permissions
|
@@ -93,6 +127,58 @@ module TTY
|
|
93
127
|
end
|
94
128
|
module_function :chmod
|
95
129
|
|
130
|
+
# Create directory structure
|
131
|
+
#
|
132
|
+
# @param [String, Hash] destination
|
133
|
+
# the path or data structure describing directory tree
|
134
|
+
#
|
135
|
+
# @example
|
136
|
+
# create_directory('/path/to/dir')
|
137
|
+
#
|
138
|
+
# @example
|
139
|
+
# tree =
|
140
|
+
# 'app' => [
|
141
|
+
# 'README.md',
|
142
|
+
# ['Gemfile', "gem 'tty-file'"],
|
143
|
+
# 'lib' => [
|
144
|
+
# 'cli.rb',
|
145
|
+
# ['file_utils.rb', "require 'tty-file'"]
|
146
|
+
# ]
|
147
|
+
# 'spec' => []
|
148
|
+
# ]
|
149
|
+
#
|
150
|
+
# create_directory(tree)
|
151
|
+
#
|
152
|
+
# @return [void]
|
153
|
+
#
|
154
|
+
# @api public
|
155
|
+
def create_directory(destination, *args, **options)
|
156
|
+
parent = args.size.nonzero? ? args.pop : nil
|
157
|
+
if destination.is_a?(String)
|
158
|
+
destination = { destination => [] }
|
159
|
+
end
|
160
|
+
|
161
|
+
destination.each do |dir, files|
|
162
|
+
path = parent.nil? ? dir : ::File.join(parent, dir)
|
163
|
+
unless ::File.exist?(path)
|
164
|
+
::FileUtils.mkdir_p(path)
|
165
|
+
log_status(:create, path, options.fetch(:verbose, true), :green)
|
166
|
+
end
|
167
|
+
|
168
|
+
files.each do |filename, contents|
|
169
|
+
if filename.respond_to?(:each_pair)
|
170
|
+
create_directory(filename, path, options)
|
171
|
+
else
|
172
|
+
create_file(::File.join(path, filename), contents, options)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
module_function :create_directory
|
178
|
+
|
179
|
+
alias create_dir create_directory
|
180
|
+
module_function :create_dir
|
181
|
+
|
96
182
|
# Create new file if doesn't exist
|
97
183
|
#
|
98
184
|
# @param [String] relative_path
|
@@ -111,21 +197,27 @@ module TTY
|
|
111
197
|
# end
|
112
198
|
#
|
113
199
|
# @api public
|
114
|
-
def create_file(relative_path, *args, &block)
|
115
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
116
|
-
|
200
|
+
def create_file(relative_path, *args, **options, &block)
|
117
201
|
content = block_given? ? block[] : args.join
|
118
202
|
|
119
|
-
CreateFile.new(relative_path, content, options).call
|
203
|
+
CreateFile.new(self, relative_path, content, options).call
|
120
204
|
end
|
121
205
|
module_function :create_file
|
122
206
|
|
207
|
+
alias add_file create_file
|
208
|
+
module_function :add_file
|
209
|
+
|
123
210
|
# Copy file from the relative source to the relative
|
124
211
|
# destination running it through ERB.
|
125
212
|
#
|
126
213
|
# @example
|
127
214
|
# copy_file 'templates/test.rb', 'app/test.rb'
|
128
215
|
#
|
216
|
+
# @example
|
217
|
+
# vars = OpenStruct.new
|
218
|
+
# vars[:name] = 'foo'
|
219
|
+
# copy_file 'templates/%name%.rb', 'app/%name%.rb', context: vars
|
220
|
+
#
|
129
221
|
# @param [Hash] options
|
130
222
|
# @option options [Symbol] :context
|
131
223
|
# the binding to use for the template
|
@@ -138,13 +230,8 @@ module TTY
|
|
138
230
|
# If true log the action status to stdout
|
139
231
|
#
|
140
232
|
# @api public
|
141
|
-
def copy_file(source_path, *args, &block)
|
142
|
-
|
143
|
-
dest_path = args.first || source_path.sub(/\.erb$/, '')
|
144
|
-
|
145
|
-
if ::File.directory?(dest_path)
|
146
|
-
dest_path = ::File.join(dest_path, ::File.basename(source_path))
|
147
|
-
end
|
233
|
+
def copy_file(source_path, *args, **options, &block)
|
234
|
+
dest_path = (args.first || source_path).sub(/\.erb$/, '')
|
148
235
|
|
149
236
|
ctx = if (vars = options[:context])
|
150
237
|
vars.instance_eval('binding')
|
@@ -152,7 +239,6 @@ module TTY
|
|
152
239
|
instance_eval('binding')
|
153
240
|
end
|
154
241
|
|
155
|
-
options[:context] ||= self
|
156
242
|
create_file(dest_path, options) do
|
157
243
|
template = ERB.new(::File.binread(source_path), nil, "-", "@output_buffer")
|
158
244
|
content = template.result(ctx)
|
@@ -166,14 +252,77 @@ module TTY
|
|
166
252
|
|
167
253
|
# Copy file metadata
|
168
254
|
#
|
255
|
+
# @param [String] src_path
|
256
|
+
# the source file path
|
257
|
+
# @param [String] dest_path
|
258
|
+
# the destination file path
|
259
|
+
#
|
169
260
|
# @api public
|
170
|
-
def copy_metadata(src_path, dest_path, options
|
261
|
+
def copy_metadata(src_path, dest_path, **options)
|
171
262
|
stats = ::File.lstat(src_path)
|
172
263
|
::File.utime(stats.atime, stats.mtime, dest_path)
|
173
264
|
chmod(dest_path, stats.mode, options)
|
174
265
|
end
|
175
266
|
module_function :copy_metadata
|
176
267
|
|
268
|
+
# Copy directory recursively from source to destination path
|
269
|
+
#
|
270
|
+
# Any files names wrapped within % sign will be expanded by
|
271
|
+
# executing corresponding method and inserting its value.
|
272
|
+
# Assuming the following directory structure:
|
273
|
+
#
|
274
|
+
# app/
|
275
|
+
# %name%.rb
|
276
|
+
# command.rb.erb
|
277
|
+
# README.md
|
278
|
+
#
|
279
|
+
# Invoking:
|
280
|
+
# copy_directory("app", "new_app")
|
281
|
+
# The following directory structure should be created where
|
282
|
+
# name resolves to 'cli' value:
|
283
|
+
#
|
284
|
+
# new_app/
|
285
|
+
# cli.rb
|
286
|
+
# command.rb
|
287
|
+
# README
|
288
|
+
#
|
289
|
+
# @param [Hash[Symbol]] options
|
290
|
+
# @option options [Symbol] :preserve
|
291
|
+
# If true, the owner, group, permissions and modified time
|
292
|
+
# are preserved on the copied file, defaults to false.
|
293
|
+
# @option options [Symbol] :recursive
|
294
|
+
# If false, copies only top level files, defaults to true.
|
295
|
+
# @option options [Symbol] :exclude
|
296
|
+
# A regex that specifies files to ignore when copying.
|
297
|
+
#
|
298
|
+
# @example
|
299
|
+
# copy_directory("app", "new_app", recursive: false)
|
300
|
+
# copy_directory("app", "new_app", exclude: /docs/)
|
301
|
+
#
|
302
|
+
# @api public
|
303
|
+
def copy_directory(source_path, *args, **options, &block)
|
304
|
+
check_path(source_path)
|
305
|
+
source = escape_glob_path(source_path)
|
306
|
+
dest_path = args.first || source
|
307
|
+
opts = {recursive: true}.merge(options)
|
308
|
+
pattern = opts[:recursive] ? ::File.join(source, '**') : source
|
309
|
+
glob_pattern = ::File.join(pattern, '*')
|
310
|
+
|
311
|
+
Dir.glob(glob_pattern, ::File::FNM_DOTMATCH).sort.each do |file_source|
|
312
|
+
next if ::File.directory?(file_source)
|
313
|
+
next if opts[:exclude] && file_source.match(opts[:exclude])
|
314
|
+
|
315
|
+
dest = ::File.join(dest_path, file_source.gsub(source_path, '.'))
|
316
|
+
file_dest = ::Pathname.new(dest).cleanpath.to_s
|
317
|
+
|
318
|
+
copy_file(file_source, file_dest, **options, &block)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
module_function :copy_directory
|
322
|
+
|
323
|
+
alias copy_dir copy_directory
|
324
|
+
module_function :copy_dir
|
325
|
+
|
177
326
|
# Diff files line by line
|
178
327
|
#
|
179
328
|
# @param [String] path_a
|
@@ -190,7 +339,7 @@ module TTY
|
|
190
339
|
# diff(file_a, file_b, format: :old)
|
191
340
|
#
|
192
341
|
# @api public
|
193
|
-
def diff(path_a, path_b, options
|
342
|
+
def diff(path_a, path_b, **options)
|
194
343
|
threshold = options[:threshold] || 10_000_000
|
195
344
|
output = ''
|
196
345
|
|
@@ -224,7 +373,9 @@ module TTY
|
|
224
373
|
output
|
225
374
|
end
|
226
375
|
module_function :diff
|
376
|
+
|
227
377
|
alias diff_files diff
|
378
|
+
module_function :diff_files
|
228
379
|
|
229
380
|
# Download the content from a given address and
|
230
381
|
# save at the given relative destination. If block
|
@@ -249,8 +400,7 @@ module TTY
|
|
249
400
|
# end
|
250
401
|
#
|
251
402
|
# @api public
|
252
|
-
def download_file(uri, *args, &block)
|
253
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
403
|
+
def download_file(uri, *args, **options, &block)
|
254
404
|
dest_path = args.first || ::File.basename(uri)
|
255
405
|
|
256
406
|
unless uri =~ %r{^https?\://}
|
@@ -261,13 +411,16 @@ module TTY
|
|
261
411
|
content = DownloadFile.new(uri, dest_path, options).call
|
262
412
|
|
263
413
|
if block_given?
|
264
|
-
content = (block.arity
|
414
|
+
content = (block.arity.nonzero? ? block[content] : block[])
|
265
415
|
end
|
266
416
|
|
267
417
|
create_file(dest_path, content, options)
|
268
418
|
end
|
269
419
|
module_function :download_file
|
270
420
|
|
421
|
+
alias get_file download_file
|
422
|
+
module_function :get_file
|
423
|
+
|
271
424
|
# Prepend to a file
|
272
425
|
#
|
273
426
|
# @param [String] relative_path
|
@@ -283,8 +436,7 @@ module TTY
|
|
283
436
|
# end
|
284
437
|
#
|
285
438
|
# @api public
|
286
|
-
def prepend_to_file(relative_path, *args, &block)
|
287
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
439
|
+
def prepend_to_file(relative_path, *args, **options, &block)
|
288
440
|
log_status(:prepend, relative_path, options.fetch(:verbose, true), :green)
|
289
441
|
options.merge!(before: /\A/, verbose: false)
|
290
442
|
inject_into_file(relative_path, *(args << options), &block)
|
@@ -306,14 +458,15 @@ module TTY
|
|
306
458
|
# end
|
307
459
|
#
|
308
460
|
# @api public
|
309
|
-
def append_to_file(relative_path, *args, &block)
|
310
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
461
|
+
def append_to_file(relative_path, *args, **options, &block)
|
311
462
|
log_status(:append, relative_path, options.fetch(:verbose, true), :green)
|
312
463
|
options.merge!(after: /\z/, verbose: false)
|
313
464
|
inject_into_file(relative_path, *(args << options), &block)
|
314
465
|
end
|
315
466
|
module_function :append_to_file
|
467
|
+
|
316
468
|
alias add_to_file append_to_file
|
469
|
+
module_function :add_to_file
|
317
470
|
|
318
471
|
# Inject content into file at a given location
|
319
472
|
#
|
@@ -341,9 +494,7 @@ module TTY
|
|
341
494
|
# end
|
342
495
|
#
|
343
496
|
# @api public
|
344
|
-
def inject_into_file(relative_path, *args, &block)
|
345
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
346
|
-
|
497
|
+
def inject_into_file(relative_path, *args, **options, &block)
|
347
498
|
replacement = block_given? ? block[] : args.join
|
348
499
|
|
349
500
|
flag, match = if options.key?(:after)
|
@@ -364,7 +515,9 @@ module TTY
|
|
364
515
|
log_status(:inject, relative_path, options.fetch(:verbose, true), :green)
|
365
516
|
end
|
366
517
|
module_function :inject_into_file
|
518
|
+
|
367
519
|
alias insert_into_file inject_into_file
|
520
|
+
module_function :insert_into_file
|
368
521
|
|
369
522
|
# Replace content of a file matching string
|
370
523
|
#
|
@@ -383,12 +536,9 @@ module TTY
|
|
383
536
|
# end
|
384
537
|
#
|
385
538
|
# @api public
|
386
|
-
def replace_in_file(relative_path, *args, &block)
|
539
|
+
def replace_in_file(relative_path, *args, **options, &block)
|
387
540
|
check_path(relative_path)
|
388
|
-
|
389
|
-
|
390
|
-
contents = IO.read(relative_path)
|
391
|
-
|
541
|
+
contents = IO.read(relative_path)
|
392
542
|
replacement = (block ? block[] : args[1..-1].join).gsub('\0', '')
|
393
543
|
|
394
544
|
log_status(:replace, relative_path, options.fetch(:verbose, true), :green)
|
@@ -400,13 +550,15 @@ module TTY
|
|
400
550
|
find = args[0]
|
401
551
|
raise "#{find.inspect} not found in #{relative_path}"
|
402
552
|
end
|
403
|
-
::File.open(relative_path, '
|
553
|
+
::File.open(relative_path, 'wb') do |file|
|
404
554
|
file.write(contents)
|
405
555
|
end
|
406
556
|
end
|
407
557
|
end
|
408
558
|
module_function :replace_in_file
|
559
|
+
|
409
560
|
alias gsub_file replace_in_file
|
561
|
+
module_function :gsub_file
|
410
562
|
|
411
563
|
# Remove a file or a directory at specified relative path.
|
412
564
|
#
|
@@ -422,9 +574,7 @@ module TTY
|
|
422
574
|
# remove_file 'doc/README.md'
|
423
575
|
#
|
424
576
|
# @api public
|
425
|
-
def remove_file(relative_path, *args)
|
426
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
427
|
-
|
577
|
+
def remove_file(relative_path, *args, **options)
|
428
578
|
log_status(:remove, relative_path, options.fetch(:verbose, true), :red)
|
429
579
|
|
430
580
|
return if options[:noop]
|
@@ -433,14 +583,32 @@ module TTY
|
|
433
583
|
end
|
434
584
|
module_function :remove_file
|
435
585
|
|
586
|
+
# Escape glob character in a path
|
587
|
+
#
|
588
|
+
# @param [String] path
|
589
|
+
# the path to escape
|
590
|
+
#
|
591
|
+
# @example
|
592
|
+
# escape_glob_path("foo[bar]") => "foo\\[bar\\]"
|
593
|
+
#
|
594
|
+
# @return [String]
|
595
|
+
#
|
596
|
+
# @api public
|
597
|
+
def escape_glob_path(path)
|
598
|
+
path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\" + x }
|
599
|
+
end
|
600
|
+
module_function :escape_glob_path
|
601
|
+
|
436
602
|
# Check if path exists
|
437
603
|
#
|
604
|
+
# @param [String] path
|
605
|
+
#
|
438
606
|
# @raise [ArgumentError]
|
439
607
|
#
|
440
608
|
# @api private
|
441
609
|
def check_path(path)
|
442
610
|
return if ::File.exist?(path)
|
443
|
-
raise
|
611
|
+
raise InvalidPathError, "File path \"#{path}\" does not exist."
|
444
612
|
end
|
445
613
|
private_module_function :check_path
|
446
614
|
|
@@ -467,7 +635,7 @@ module TTY
|
|
467
635
|
@output.print(message)
|
468
636
|
@output.flush
|
469
637
|
end
|
470
|
-
|
638
|
+
private_module_function :log_status
|
471
639
|
|
472
640
|
# If content is not a path to a file, create a
|
473
641
|
# tempfile and open it instead.
|
data/tty-file.gemspec
CHANGED
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
-
spec.add_dependency 'pastel', '~> 0.
|
23
|
-
spec.add_dependency 'tty-prompt', '~> 0.
|
24
|
-
spec.add_dependency 'diff-lcs', '~> 1.
|
22
|
+
spec.add_dependency 'pastel', '~> 0.7.0'
|
23
|
+
spec.add_dependency 'tty-prompt', '~> 0.10.0'
|
24
|
+
spec.add_dependency 'diff-lcs', '~> 1.3.0'
|
25
25
|
|
26
26
|
spec.add_development_dependency 'bundler', '>= 1.5.0', '< 2.0'
|
27
27
|
spec.add_development_dependency 'rake', '~> 10.0'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tty-file
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Murach
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pastel
|
@@ -16,42 +16,42 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.7.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.7.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: tty-prompt
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 0.10.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
40
|
+
version: 0.10.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: diff-lcs
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.
|
47
|
+
version: 1.3.0
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.
|
54
|
+
version: 1.3.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: bundler
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +122,7 @@ files:
|
|
122
122
|
- lib/tty/file.rb
|
123
123
|
- lib/tty/file/create_file.rb
|
124
124
|
- lib/tty/file/differ.rb
|
125
|
+
- lib/tty/file/digest_file.rb
|
125
126
|
- lib/tty/file/download_file.rb
|
126
127
|
- lib/tty/file/version.rb
|
127
128
|
- tasks/console.rake
|