tty-file 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|