tty-file 0.9.0 → 0.10.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/CHANGELOG.md +15 -0
- data/README.md +144 -75
- data/lib/tty-file.rb +1 -1
- data/lib/tty/file.rb +237 -155
- data/lib/tty/file/compare_files.rb +70 -0
- data/lib/tty/file/create_file.rb +28 -14
- data/lib/tty/file/differ.rb +40 -20
- data/lib/tty/file/download_file.rb +2 -2
- data/lib/tty/file/version.rb +1 -1
- metadata +10 -8
data/lib/tty-file.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require_relative
|
1
|
+
require_relative "tty/file"
|
data/lib/tty/file.rb
CHANGED
@@ -5,10 +5,10 @@ require "erb"
|
|
5
5
|
require "tempfile"
|
6
6
|
require "pathname"
|
7
7
|
|
8
|
+
require_relative "file/compare_files"
|
8
9
|
require_relative "file/create_file"
|
9
10
|
require_relative "file/digest_file"
|
10
11
|
require_relative "file/download_file"
|
11
|
-
require_relative "file/differ"
|
12
12
|
require_relative "file/read_backward_file"
|
13
13
|
require_relative "file/version"
|
14
14
|
|
@@ -56,7 +56,7 @@ module TTY
|
|
56
56
|
buffer = read_to_char(relative_path, bytes, 0)
|
57
57
|
|
58
58
|
begin
|
59
|
-
|
59
|
+
buffer !~ /\A[\s[[:print:]]]*\z/m
|
60
60
|
rescue ArgumentError => error
|
61
61
|
return true if error.message =~ /invalid byte sequence/
|
62
62
|
raise
|
@@ -98,9 +98,8 @@ module TTY
|
|
98
98
|
# @param [File, IO, String, Pathname] source
|
99
99
|
# the source to generate checksum for
|
100
100
|
# @param [String] mode
|
101
|
-
# @param [
|
102
|
-
#
|
103
|
-
# No operation
|
101
|
+
# @param [Boolean] noop
|
102
|
+
# when true skip this action
|
104
103
|
#
|
105
104
|
# @example
|
106
105
|
# checksum_file("/path/to/file")
|
@@ -112,21 +111,25 @@ module TTY
|
|
112
111
|
# the generated hex value
|
113
112
|
#
|
114
113
|
# @api public
|
115
|
-
def checksum_file(source, *args,
|
114
|
+
def checksum_file(source, *args, noop: false)
|
116
115
|
mode = args.size.zero? ? "sha256" : args.pop
|
117
116
|
digester = DigestFile.new(source, mode)
|
118
|
-
digester.call unless
|
117
|
+
digester.call unless noop
|
119
118
|
end
|
120
119
|
module_function :checksum_file
|
121
120
|
|
122
121
|
# Change file permissions
|
123
122
|
#
|
124
123
|
# @param [String, Pathname] relative_path
|
124
|
+
# the string or path to a file
|
125
125
|
# @param [Integer,String] permisssions
|
126
|
-
#
|
127
|
-
# @
|
128
|
-
#
|
129
|
-
# @
|
126
|
+
# the string or octal number for permissoins
|
127
|
+
# @param [Boolean] noop
|
128
|
+
# when true skips this action
|
129
|
+
# @param [Boolean] verbose
|
130
|
+
# when true displays logging information
|
131
|
+
# @param [Symbol] color
|
132
|
+
# the name for the color to format display message, :green by default
|
130
133
|
#
|
131
134
|
# @example
|
132
135
|
# chmod("Gemfile", 0755)
|
@@ -138,10 +141,9 @@ module TTY
|
|
138
141
|
# chmod("Gemfile", "u+x,g+x")
|
139
142
|
#
|
140
143
|
# @api public
|
141
|
-
def chmod(relative_path, permissions,
|
142
|
-
log_status(:chmod, relative_path,
|
143
|
-
|
144
|
-
::FileUtils.chmod_R(permissions, relative_path) unless options[:noop]
|
144
|
+
def chmod(relative_path, permissions, verbose: true, color: :green, noop: false)
|
145
|
+
log_status(:chmod, relative_path, verbose: verbose, color: color)
|
146
|
+
::FileUtils.chmod_R(permissions, relative_path) unless noop
|
145
147
|
end
|
146
148
|
module_function :chmod
|
147
149
|
|
@@ -149,6 +151,18 @@ module TTY
|
|
149
151
|
#
|
150
152
|
# @param [String, Pathname, Hash] destination
|
151
153
|
# the path or data structure describing directory tree
|
154
|
+
# @param [Object] context
|
155
|
+
# the context for template evaluation
|
156
|
+
# @param [Boolean] quiet
|
157
|
+
# when true leaves prompt output, otherwise clears
|
158
|
+
# @param [Boolean] force
|
159
|
+
# when true overwrites existing files, false by default
|
160
|
+
# @param [Boolean] noop
|
161
|
+
# when true skips this action
|
162
|
+
# @param [Boolean] verbose
|
163
|
+
# when true displays logging information
|
164
|
+
# @param [Symbol] color
|
165
|
+
# the name for the color to format display message, :green by default
|
152
166
|
#
|
153
167
|
# @example
|
154
168
|
# create_directory("/path/to/dir")
|
@@ -170,7 +184,9 @@ module TTY
|
|
170
184
|
# @return [void]
|
171
185
|
#
|
172
186
|
# @api public
|
173
|
-
def create_directory(destination, *args,
|
187
|
+
def create_directory(destination, *args, context: nil, verbose: true,
|
188
|
+
color: :green, noop: false, force: false, skip: false,
|
189
|
+
quiet: true)
|
174
190
|
parent = args.size.nonzero? ? args.pop : nil
|
175
191
|
if destination.is_a?(String) || destination.is_a?(Pathname)
|
176
192
|
destination = { destination.to_s => [] }
|
@@ -180,15 +196,18 @@ module TTY
|
|
180
196
|
path = parent.nil? ? dir : ::File.join(parent, dir)
|
181
197
|
unless ::File.exist?(path)
|
182
198
|
::FileUtils.mkdir_p(path)
|
183
|
-
log_status(:create, path,
|
184
|
-
options.fetch(:color, :green))
|
199
|
+
log_status(:create, path, verbose: verbose, color: color)
|
185
200
|
end
|
186
201
|
|
187
202
|
files.each do |filename, contents|
|
188
203
|
if filename.respond_to?(:each_pair)
|
189
|
-
create_directory(filename, path,
|
204
|
+
create_directory(filename, path, context: context,
|
205
|
+
verbose: verbose, color: color, noop: noop,
|
206
|
+
force: force, skip: skip, quiet: quiet)
|
190
207
|
else
|
191
|
-
create_file(::File.join(path, filename), contents,
|
208
|
+
create_file(::File.join(path, filename), contents, context: context,
|
209
|
+
verbose: verbose, color: color, noop: noop, force: force,
|
210
|
+
skip: skip, quiet: quiet)
|
192
211
|
end
|
193
212
|
end
|
194
213
|
end
|
@@ -203,9 +222,20 @@ module TTY
|
|
203
222
|
# @param [String, Pathname] relative_path
|
204
223
|
# @param [String|nil] content
|
205
224
|
# the content to add to file
|
206
|
-
# @param [
|
207
|
-
#
|
225
|
+
# @param [Object] context
|
226
|
+
# the binding to use for the template
|
227
|
+
# @param [Symbol] color
|
228
|
+
# the color name to use for logging
|
229
|
+
# @param [Boolean] force
|
208
230
|
# forces ovewrite if conflict present
|
231
|
+
# @param [Boolean] verbose
|
232
|
+
# when true log the action status to stdout
|
233
|
+
# @param [Boolean] noop
|
234
|
+
# when true do not execute the action
|
235
|
+
# @param [Boolean] skip
|
236
|
+
# when true skip the action
|
237
|
+
# @param [Boolean] quiet
|
238
|
+
# when true leaves prompt output, otherwise clears
|
209
239
|
#
|
210
240
|
# @example
|
211
241
|
# create_file("doc/README.md", "# Title header")
|
@@ -216,11 +246,14 @@ module TTY
|
|
216
246
|
# end
|
217
247
|
#
|
218
248
|
# @api public
|
219
|
-
def create_file(relative_path, *args,
|
249
|
+
def create_file(relative_path, *args, context: nil, force: false, skip: false,
|
250
|
+
verbose: true, color: :green, noop: false, quiet: true, &block)
|
220
251
|
relative_path = relative_path.to_s
|
221
252
|
content = block_given? ? block[] : args.join
|
222
253
|
|
223
|
-
CreateFile.new(self, relative_path, content,
|
254
|
+
CreateFile.new(self, relative_path, content, context: context, force: force,
|
255
|
+
skip: skip, verbose: verbose, color: color, noop: noop,
|
256
|
+
quiet: quiet).call
|
224
257
|
end
|
225
258
|
module_function :create_file
|
226
259
|
|
@@ -239,29 +272,33 @@ module TTY
|
|
239
272
|
# copy_file "templates/%name%.rb", "app/%name%.rb", context: vars
|
240
273
|
#
|
241
274
|
# @param [String, Pathname] source_path
|
242
|
-
#
|
243
|
-
# @
|
275
|
+
# the file path to copy file from
|
276
|
+
# @param [Object] context
|
244
277
|
# the binding to use for the template
|
245
|
-
# @
|
246
|
-
#
|
247
|
-
# are preserved on the copied file, defaults to false
|
248
|
-
# @
|
249
|
-
#
|
250
|
-
# @
|
251
|
-
#
|
278
|
+
# @param [Boolean] preserve
|
279
|
+
# when true, the owner, group, permissions and modified time
|
280
|
+
# are preserved on the copied file, defaults to false
|
281
|
+
# @param [Boolean] noop
|
282
|
+
# when true does not execute the action
|
283
|
+
# @param [Boolean] verbose
|
284
|
+
# when true log the action status to stdout
|
285
|
+
# @param [Symbol] color
|
286
|
+
# the color name to use for logging
|
252
287
|
#
|
253
288
|
# @api public
|
254
|
-
def copy_file(source_path, *args,
|
289
|
+
def copy_file(source_path, *args, context: nil, force: false, skip: false,
|
290
|
+
verbose: true, color: :green, noop: false, preserve: nil, &block)
|
255
291
|
source_path = source_path.to_s
|
256
292
|
dest_path = (args.first || source_path).to_s.sub(/\.erb$/, "")
|
257
293
|
|
258
|
-
ctx = if
|
259
|
-
|
294
|
+
ctx = if context
|
295
|
+
context.instance_eval("binding")
|
260
296
|
else
|
261
297
|
instance_eval("binding")
|
262
298
|
end
|
263
299
|
|
264
|
-
create_file(dest_path,
|
300
|
+
create_file(dest_path, context: context, force: force, skip: skip,
|
301
|
+
verbose: verbose, color: color, noop: noop) do
|
265
302
|
version = ERB.version.scan(/\d+\.\d+\.\d+/)[0]
|
266
303
|
template = if version.to_f >= 2.2
|
267
304
|
ERB.new(::File.binread(source_path), trim_mode: "-", eoutvar: "@output_buffer")
|
@@ -272,9 +309,10 @@ module TTY
|
|
272
309
|
content = block[content] if block
|
273
310
|
content
|
274
311
|
end
|
275
|
-
return unless
|
312
|
+
return unless preserve
|
276
313
|
|
277
|
-
copy_metadata(source_path, dest_path,
|
314
|
+
copy_metadata(source_path, dest_path, verbose: verbose, noop: noop,
|
315
|
+
color: color)
|
278
316
|
end
|
279
317
|
module_function :copy_file
|
280
318
|
|
@@ -314,38 +352,43 @@ module TTY
|
|
314
352
|
# command.rb
|
315
353
|
# README
|
316
354
|
#
|
317
|
-
# @param [String, Pathname] source_path
|
318
|
-
# @param [Hash[Symbol]] options
|
319
|
-
# @option options [Symbol] :preserve
|
320
|
-
# If true, the owner, group, permissions and modified time
|
321
|
-
# are preserved on the copied file, defaults to false.
|
322
|
-
# @option options [Symbol] :recursive
|
323
|
-
# If false, copies only top level files, defaults to true.
|
324
|
-
# @option options [Symbol] :exclude
|
325
|
-
# A regex that specifies files to ignore when copying.
|
326
|
-
#
|
327
355
|
# @example
|
328
356
|
# copy_directory("app", "new_app", recursive: false)
|
357
|
+
#
|
358
|
+
# @example
|
329
359
|
# copy_directory("app", "new_app", exclude: /docs/)
|
330
360
|
#
|
361
|
+
# @param [String, Pathname] source_path
|
362
|
+
# the source directory to copy files from
|
363
|
+
# @param [Boolean] preserve
|
364
|
+
# when true, the owner, group, permissions and modified time
|
365
|
+
# are preserved on the copied file, defaults to false.
|
366
|
+
# @param [Boolean] recursive
|
367
|
+
# when false, copies only top level files, defaults to true
|
368
|
+
# @param [Regexp] exclude
|
369
|
+
# a regex that specifies files to ignore when copying
|
370
|
+
#
|
331
371
|
# @api public
|
332
|
-
def copy_directory(source_path, *args,
|
372
|
+
def copy_directory(source_path, *args, context: nil, force: false, skip: false,
|
373
|
+
verbose: true, color: :green, noop: false, preserve: nil,
|
374
|
+
recursive: true, exclude: nil, &block)
|
333
375
|
source_path = source_path.to_s
|
334
376
|
check_path(source_path)
|
335
377
|
source = escape_glob_path(source_path)
|
336
378
|
dest_path = (args.first || source).to_s
|
337
|
-
|
338
|
-
pattern = opts[:recursive] ? ::File.join(source, "**") : source
|
379
|
+
pattern = recursive ? ::File.join(source, "**") : source
|
339
380
|
glob_pattern = ::File.join(pattern, "*")
|
340
381
|
|
341
382
|
Dir.glob(glob_pattern, ::File::FNM_DOTMATCH).sort.each do |file_source|
|
342
383
|
next if ::File.directory?(file_source)
|
343
|
-
next if
|
384
|
+
next if exclude && file_source.match(exclude)
|
344
385
|
|
345
386
|
dest = ::File.join(dest_path, file_source.gsub(source_path, "."))
|
346
387
|
file_dest = ::Pathname.new(dest).cleanpath.to_s
|
347
388
|
|
348
|
-
copy_file(file_source, file_dest,
|
389
|
+
copy_file(file_source, file_dest, context: context, force: force,
|
390
|
+
skip: skip, verbose: verbose, color: color, noop: noop,
|
391
|
+
preserve: preserve, &block)
|
349
392
|
end
|
350
393
|
end
|
351
394
|
module_function :copy_directory
|
@@ -356,57 +399,88 @@ module TTY
|
|
356
399
|
# Diff files line by line
|
357
400
|
#
|
358
401
|
# @param [String, Pathname] path_a
|
402
|
+
# the path to the original file
|
359
403
|
# @param [String, Pathname] path_b
|
360
|
-
#
|
361
|
-
# @
|
404
|
+
# the path to a new file
|
405
|
+
# @param [Symbol] format
|
362
406
|
# the diffining output format
|
363
|
-
# @
|
407
|
+
# @param [Intger] lines
|
364
408
|
# the number of extra lines for the context
|
365
|
-
# @
|
409
|
+
# @param [Integer] threshold
|
366
410
|
# maximum file size in bytes
|
367
411
|
#
|
368
412
|
# @example
|
369
413
|
# diff(file_a, file_b, format: :old)
|
370
414
|
#
|
371
415
|
# @api public
|
372
|
-
def diff(path_a, path_b,
|
373
|
-
|
374
|
-
|
416
|
+
def diff(path_a, path_b, threshold: 10_000_000, format: :unified, lines: 3,
|
417
|
+
header: true, verbose: true, color: :green, noop: false)
|
418
|
+
open_tempfile_if_missing(path_a) do |file_a, temp_a|
|
419
|
+
message = check_binary_or_large(file_a, threshold)
|
420
|
+
return message if message
|
375
421
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
end
|
380
|
-
if binary?(file_a)
|
381
|
-
raise ArgumentError, "(#{file_a.path} is binary, diff output suppressed)"
|
382
|
-
end
|
383
|
-
open_tempfile_if_missing(path_b) do |file_b|
|
384
|
-
if binary?(file_b)
|
385
|
-
raise ArgumentError, "(#{file_a.path} is binary, diff output suppressed)"
|
386
|
-
end
|
387
|
-
if ::File.size(file_b) > threshold
|
388
|
-
return "(file size of #{file_b.path} exceeds #{threshold} bytes, diff output suppressed)"
|
389
|
-
end
|
422
|
+
open_tempfile_if_missing(path_b) do |file_b, temp_b|
|
423
|
+
message = check_binary_or_large(file_b, threshold)
|
424
|
+
return message if message
|
390
425
|
|
391
|
-
|
392
|
-
|
393
|
-
return output.join if options[:noop]
|
426
|
+
file_a_path, file_b_path = *diff_paths(file_a, file_b, temp_a, temp_b)
|
427
|
+
.map { |path| ::File.join(*path) }
|
394
428
|
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
429
|
+
log_status(:diff, "#{file_a_path} and #{file_b_path}",
|
430
|
+
verbose: verbose, color: color)
|
431
|
+
|
432
|
+
return "" if noop
|
433
|
+
|
434
|
+
diff_files = CompareFiles.new(format: format, context_lines: lines,
|
435
|
+
header: header, verbose: verbose,
|
436
|
+
color: color, noop: noop,
|
437
|
+
diff_colors: diff_colors)
|
438
|
+
|
439
|
+
return diff_files.call(file_a, file_b, file_a_path, file_b_path)
|
401
440
|
end
|
402
441
|
end
|
403
|
-
output.join
|
404
442
|
end
|
405
443
|
module_function :diff
|
406
444
|
|
407
445
|
alias diff_files diff
|
408
446
|
module_function :diff_files
|
409
447
|
|
448
|
+
# @api private
|
449
|
+
def diff_paths(file_a, file_b, temp_a, temp_b)
|
450
|
+
if temp_a && !temp_b
|
451
|
+
[["a", file_b.path], ["b", file_b.path]]
|
452
|
+
elsif !temp_a && temp_b
|
453
|
+
[["a", file_a.path], ["b", file_a.path]]
|
454
|
+
elsif temp_a && temp_b
|
455
|
+
[["a"], ["b"]]
|
456
|
+
else
|
457
|
+
[file_a.path, file_b.path]
|
458
|
+
end
|
459
|
+
end
|
460
|
+
private_module_function :diff_paths
|
461
|
+
|
462
|
+
def diff_colors
|
463
|
+
{
|
464
|
+
green: @pastel.green.detach,
|
465
|
+
red: @pastel.red.detach,
|
466
|
+
cyan: @pastel.cyan.detach
|
467
|
+
}
|
468
|
+
end
|
469
|
+
private_module_function :diff_colors
|
470
|
+
|
471
|
+
# Check if file is binary or exceeds threshold size
|
472
|
+
#
|
473
|
+
# @api private
|
474
|
+
def check_binary_or_large(file, threshold)
|
475
|
+
if binary?(file)
|
476
|
+
"#{file.path} is binary, diff output suppressed"
|
477
|
+
elsif ::File.size(file) > threshold
|
478
|
+
"file size of #{file.path} exceeds #{threshold} bytes, " \
|
479
|
+
" diff output suppressed"
|
480
|
+
end
|
481
|
+
end
|
482
|
+
private_module_function :check_binary_or_large
|
483
|
+
|
410
484
|
# Download the content from a given address and
|
411
485
|
# save at the given relative destination. If block
|
412
486
|
# is provided in place of destination, the content of
|
@@ -416,9 +490,8 @@ module TTY
|
|
416
490
|
# the URI address
|
417
491
|
# @param [String, Pathname] dest
|
418
492
|
# the relative path to save
|
419
|
-
# @param [
|
420
|
-
#
|
421
|
-
# the limit of redirects
|
493
|
+
# @param [Integer] limit
|
494
|
+
# the number of maximium redirects
|
422
495
|
#
|
423
496
|
# @example
|
424
497
|
# download_file("https://gist.github.com/4701967",
|
@@ -439,7 +512,7 @@ module TTY
|
|
439
512
|
return
|
440
513
|
end
|
441
514
|
|
442
|
-
content = DownloadFile.new(uri, dest_path, options).call
|
515
|
+
content = DownloadFile.new(uri, dest_path, limit: options[:limit]).call
|
443
516
|
|
444
517
|
if block_given?
|
445
518
|
content = (block.arity.nonzero? ? block[content] : block[])
|
@@ -467,11 +540,11 @@ module TTY
|
|
467
540
|
# end
|
468
541
|
#
|
469
542
|
# @api public
|
470
|
-
def prepend_to_file(relative_path, *args,
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
543
|
+
def prepend_to_file(relative_path, *args, verbose: true, color: :green,
|
544
|
+
force: true, noop: false, &block)
|
545
|
+
log_status(:prepend, relative_path, verbose: verbose, color: color)
|
546
|
+
inject_into_file(relative_path, *args, before: /\A/, verbose: false,
|
547
|
+
color: color, force: force, noop: noop, &block)
|
475
548
|
end
|
476
549
|
module_function :prepend_to_file
|
477
550
|
|
@@ -498,11 +571,11 @@ module TTY
|
|
498
571
|
# end
|
499
572
|
#
|
500
573
|
# @api public
|
501
|
-
def append_to_file(relative_path, *args,
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
574
|
+
def append_to_file(relative_path, *args, verbose: true, color: :green,
|
575
|
+
force: true, noop: false, &block)
|
576
|
+
log_status(:append, relative_path, verbose: verbose, color: color)
|
577
|
+
inject_into_file(relative_path, *args, after: /\z/, verbose: false,
|
578
|
+
force: force, noop: noop, color: color, &block)
|
506
579
|
end
|
507
580
|
module_function :append_to_file
|
508
581
|
|
@@ -520,16 +593,18 @@ module TTY
|
|
520
593
|
# Inject content into file at a given location
|
521
594
|
#
|
522
595
|
# @param [String, Pathname] relative_path
|
523
|
-
#
|
524
|
-
# @param [Hash] options
|
525
|
-
# @option options [Symbol] :before
|
596
|
+
# @param [String] before
|
526
597
|
# the matching line to insert content before
|
527
|
-
# @
|
598
|
+
# @param [String] after
|
528
599
|
# the matching line to insert content after
|
529
|
-
# @
|
600
|
+
# @param [Boolean] force
|
530
601
|
# insert content more than once
|
531
|
-
# @
|
532
|
-
# log status
|
602
|
+
# @param [Boolean] verbose
|
603
|
+
# when true log status
|
604
|
+
# @param [Symbol] color
|
605
|
+
# the color name used in displaying this action
|
606
|
+
# @param [Boolean] noop
|
607
|
+
# when true skip perfomring this action
|
533
608
|
#
|
534
609
|
# @example
|
535
610
|
# inject_into_file("Gemfile", "gem 'tty'", after: "gem 'rack'\n")
|
@@ -543,15 +618,12 @@ module TTY
|
|
543
618
|
# end
|
544
619
|
#
|
545
620
|
# @api public
|
546
|
-
def inject_into_file(relative_path, *args,
|
621
|
+
def inject_into_file(relative_path, *args, verbose: true, color: :green,
|
622
|
+
after: nil, before: nil, force: true, noop: false, &block)
|
547
623
|
check_path(relative_path)
|
548
624
|
replacement = block_given? ? block[] : args.join
|
549
625
|
|
550
|
-
flag, match =
|
551
|
-
[:after, options.delete(:after)]
|
552
|
-
else
|
553
|
-
[:before, options.delete(:before)]
|
554
|
-
end
|
626
|
+
flag, match = after ? [:after, after] : [:before, before]
|
555
627
|
|
556
628
|
match = match.is_a?(Regexp) ? match : Regexp.escape(match)
|
557
629
|
content = if flag == :after
|
@@ -560,10 +632,9 @@ module TTY
|
|
560
632
|
replacement + '\0'
|
561
633
|
end
|
562
634
|
|
563
|
-
log_status(:inject, relative_path,
|
564
|
-
|
565
|
-
|
566
|
-
**options.merge(verbose: false))
|
635
|
+
log_status(:inject, relative_path, verbose: verbose, color: color)
|
636
|
+
replace_in_file(relative_path, /#{match}/, content, verbose: false,
|
637
|
+
color: color, force: force, noop: noop)
|
567
638
|
end
|
568
639
|
module_function :inject_into_file
|
569
640
|
|
@@ -582,11 +653,14 @@ module TTY
|
|
582
653
|
# when no substitutions were performed, true otherwise.
|
583
654
|
#
|
584
655
|
# @param [String, Pathname] relative_path
|
585
|
-
# @
|
586
|
-
# @option options [Symbol] :force
|
656
|
+
# @param [Boolean] force
|
587
657
|
# replace content even if present
|
588
|
-
# @
|
589
|
-
# log status
|
658
|
+
# @param [Boolean] verbose
|
659
|
+
# when true log status to stdout
|
660
|
+
# @param [Boolean] noop
|
661
|
+
# when true skip executing this action
|
662
|
+
# @param [Symbol] color
|
663
|
+
# the name of the color used for displaying action
|
590
664
|
#
|
591
665
|
# @example
|
592
666
|
# replace_in_file("Gemfile", /gem 'rails'/, "gem 'hanami'")
|
@@ -600,18 +674,18 @@ module TTY
|
|
600
674
|
# true when replaced content, false otherwise
|
601
675
|
#
|
602
676
|
# @api public
|
603
|
-
def replace_in_file(relative_path, *args,
|
677
|
+
def replace_in_file(relative_path, *args, verbose: true, color: :green,
|
678
|
+
noop: false, force: true, &block)
|
604
679
|
check_path(relative_path)
|
605
680
|
contents = ::File.read(relative_path)
|
606
681
|
replacement = (block ? block[] : args[1..-1].join).gsub('\0', "")
|
607
682
|
match = Regexp.escape(replacement)
|
608
683
|
status = nil
|
609
684
|
|
610
|
-
log_status(:replace, relative_path,
|
611
|
-
|
612
|
-
return false if options[:noop]
|
685
|
+
log_status(:replace, relative_path, verbose: verbose, color: color)
|
686
|
+
return false if noop
|
613
687
|
|
614
|
-
if
|
688
|
+
if force || !(contents =~ /^#{match}(\r?\n)*/m)
|
615
689
|
status = contents.gsub!(*args, &block)
|
616
690
|
if !status.nil?
|
617
691
|
::File.open(relative_path, "w") do |file|
|
@@ -629,29 +703,27 @@ module TTY
|
|
629
703
|
# Remove a file or a directory at specified relative path.
|
630
704
|
#
|
631
705
|
# @param [String, Pathname] relative_path
|
632
|
-
# @param [
|
633
|
-
#
|
634
|
-
#
|
635
|
-
#
|
636
|
-
#
|
637
|
-
#
|
638
|
-
#
|
639
|
-
#
|
640
|
-
# for secure removing
|
706
|
+
# @param [Boolean] noop
|
707
|
+
# when true pretend to remove file
|
708
|
+
# @param [Boolean] force
|
709
|
+
# when true remove file ignoring errors
|
710
|
+
# @param [Boolean] verbose
|
711
|
+
# when true log status
|
712
|
+
# @param [Boolean] secure
|
713
|
+
# when true check for secure removing
|
641
714
|
#
|
642
715
|
# @example
|
643
716
|
# remove_file "doc/README.md"
|
644
717
|
#
|
645
718
|
# @api public
|
646
|
-
def remove_file(relative_path, *args,
|
719
|
+
def remove_file(relative_path, *args, verbose: true, color: :red, noop: false,
|
720
|
+
force: nil, secure: true)
|
647
721
|
relative_path = relative_path.to_s
|
648
|
-
log_status(:remove, relative_path,
|
649
|
-
options.fetch(:color, :red))
|
722
|
+
log_status(:remove, relative_path, verbose: verbose, color: color)
|
650
723
|
|
651
|
-
return if
|
724
|
+
return if noop || !::File.exist?(relative_path)
|
652
725
|
|
653
|
-
::FileUtils.rm_r(relative_path, force:
|
654
|
-
secure: options.fetch(:secure, true))
|
726
|
+
::FileUtils.rm_r(relative_path, force: force, secure: secure)
|
655
727
|
end
|
656
728
|
module_function :remove_file
|
657
729
|
|
@@ -659,44 +731,44 @@ module TTY
|
|
659
731
|
#
|
660
732
|
# @param [String, Pathname] relative_path
|
661
733
|
# the relative path to a file
|
662
|
-
#
|
663
|
-
# @param [Integer] num_lines
|
734
|
+
# @param [Integer] lines
|
664
735
|
# the number of lines to return from file
|
736
|
+
# @param [Integer] chunk_size
|
737
|
+
# the size of the chunk to read
|
665
738
|
#
|
666
739
|
# @example
|
667
740
|
# tail_file "filename"
|
668
741
|
# # => ["line 19", "line20", ... ]
|
669
742
|
#
|
670
743
|
# @example
|
671
|
-
# tail_file "filename", 15
|
744
|
+
# tail_file "filename", lines: 15
|
672
745
|
# # => ["line 19", "line20", ... ]
|
673
746
|
#
|
674
747
|
# @return [Array[String]]
|
675
748
|
#
|
676
749
|
# @api public
|
677
|
-
def tail_file(relative_path,
|
678
|
-
file
|
679
|
-
|
680
|
-
|
681
|
-
lines = []
|
750
|
+
def tail_file(relative_path, lines: 10, chunk_size: 512, &block)
|
751
|
+
file = ::File.open(relative_path)
|
752
|
+
line_sep = $/
|
753
|
+
output = []
|
682
754
|
newline_count = 0
|
683
755
|
|
684
756
|
ReadBackwardFile.new(file, chunk_size).each_chunk do |chunk|
|
685
757
|
# look for newline index counting from right of chunk
|
686
758
|
while (nl_index = chunk.rindex(line_sep, (nl_index || chunk.size) - 1))
|
687
759
|
newline_count += 1
|
688
|
-
break if newline_count >
|
760
|
+
break if newline_count > lines || nl_index.zero?
|
689
761
|
end
|
690
762
|
|
691
|
-
if newline_count >
|
692
|
-
|
763
|
+
if newline_count > lines
|
764
|
+
output.insert(0, chunk[(nl_index + 1)..-1])
|
693
765
|
break
|
694
766
|
else
|
695
|
-
|
767
|
+
output.insert(0, chunk)
|
696
768
|
end
|
697
769
|
end
|
698
770
|
|
699
|
-
|
771
|
+
output.join.split(line_sep).each(&block).to_a
|
700
772
|
end
|
701
773
|
module_function :tail_file
|
702
774
|
|
@@ -741,7 +813,7 @@ module TTY
|
|
741
813
|
# Log file operation
|
742
814
|
#
|
743
815
|
# @api private
|
744
|
-
def log_status(cmd, message, verbose, color
|
816
|
+
def log_status(cmd, message, verbose: true, color: false)
|
745
817
|
return unless verbose
|
746
818
|
|
747
819
|
cmd = cmd.to_s.rjust(12)
|
@@ -773,7 +845,7 @@ module TTY
|
|
773
845
|
tempfile << object
|
774
846
|
tempfile.rewind
|
775
847
|
|
776
|
-
block[tempfile]
|
848
|
+
block[tempfile, ::File.basename(tempfile)]
|
777
849
|
|
778
850
|
unless tempfile.nil?
|
779
851
|
tempfile.close
|
@@ -782,5 +854,15 @@ module TTY
|
|
782
854
|
end
|
783
855
|
end
|
784
856
|
private_module_function :open_tempfile_if_missing
|
857
|
+
|
858
|
+
# Check if IO is attached to a terminal
|
859
|
+
#
|
860
|
+
# return [Boolean]
|
861
|
+
#
|
862
|
+
# @api public
|
863
|
+
def tty?
|
864
|
+
@output.respond_to?(:tty?) && @output.tty?
|
865
|
+
end
|
866
|
+
private_module_function :tty?
|
785
867
|
end # File
|
786
868
|
end # TTY
|