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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ad05816965df5179021ae1e70048eac3ae58db78
4
- data.tar.gz: 35e7da73870c9bbb6d01a35584bb0537c110903e
3
+ metadata.gz: 24159965082063c18c0538a86d536db913ac05ee
4
+ data.tar.gz: 4b751ed2950b3f4725333011ce9351dd3d1a1afe
5
5
  SHA512:
6
- metadata.gz: 3dbfc53a540c8f43c130274c02fcf3c2a9cfb0d50cab7b7d0a1e1ba2ab8c92073a61b0c4bf4c0108d005abfc353d65e10190169c689b772c54becfee96bc12e6
7
- data.tar.gz: 92a918fd84a1e846d04c374644781f940f28ea0705df7f9ab0da5df7e87f9d0e39c2bd08621b021eed86da4cb7b2e4703ba5430b64860c571c5fe856ba7308cc
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
- - rbx
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: jruby
22
- - rvm: rbx
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
@@ -3,7 +3,7 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :test do
6
- gem 'simplecov', '~> 0.10.0'
7
- gem 'coveralls', '~> 0.8.2'
8
- gem 'webmock', '~> 2.1.0'
6
+ gem 'simplecov', '~> 0.12.0'
7
+ gem 'coveralls', '~> 0.8.17'
8
+ gem 'webmock', '~> 2.3.0'
9
9
  end
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. chmod](#22-chmod)
43
- * [2.3. copy_file](#23-copy_file)
44
- * [2.4. create_file](#24-create_file)
45
- * [2.5. diff](#25-diff)
46
- * [2.6. download_file](#26-download_file)
47
- * [2.7. inject_into_file](#27-inject_into_file)
48
- * [2.8. replace_in_file](#28-replace_in_file)
49
- * [2.9. append_to_file](#29-apend_to_file)
50
- * [2.10. prepend_to_file](#30-prepend_to_file)
51
- * [2.11. remove_file](#211-remove_file)
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. chmod
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.3. copy_file
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.4. create_file
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.5. diff
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
- To compare files line by line in a system independent way use `diff`:
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.6. download_file
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.7. inject_into_file
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.8. replace_in_file
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
- ### 2.9. append_to_file
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.10. prepend_to_file
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.11. remove_file
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.
@@ -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
- def initialize(relative_path, content, options = {})
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
- return if options[:noop]
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module TTY
4
4
  module File
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end # File
7
7
  end # TTY
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
- options = args.last.is_a?(Hash) ? args.pop : {}
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 == 1 ? block[content] : block[])
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
- options = args.last.is_a?(Hash) ? args.pop : {}
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, 'w') do |file|
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 ArgumentError, "File path #{path} does not exist."
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
- module_function :log_status
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.6.0'
23
- spec.add_dependency 'tty-prompt', '~> 0.7.0'
24
- spec.add_dependency 'diff-lcs', '~> 1.2.5'
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.1.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: 2016-11-06 00:00:00.000000000 Z
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.6.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.6.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.7.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.7.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.2.5
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.2.5
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