maid 0.10.0.pre.alpha.1 → 0.10.0.pre.alpha.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/coverage.yml +29 -0
- data/.github/workflows/lint.yml +24 -0
- data/.github/workflows/release.yml +5 -2
- data/.gitignore +1 -0
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +35 -0
- data/.rubocop_todo.yml +372 -0
- data/CHANGELOG.md +8 -1
- data/Guardfile +31 -4
- data/README.md +3 -3
- data/Rakefile +1 -1
- data/Vagrantfile +2 -2
- data/lib/maid/app.rb +48 -51
- data/lib/maid/maid.rb +38 -38
- data/lib/maid/numeric_extensions.rb +26 -25
- data/lib/maid/platform.rb +1 -1
- data/lib/maid/rake/task.rb +1 -1
- data/lib/maid/repeat.rb +8 -8
- data/lib/maid/rule_container.rb +3 -3
- data/lib/maid/rules.sample.rb +17 -17
- data/lib/maid/tools.rb +142 -127
- data/lib/maid/trash_migration.rb +4 -4
- data/lib/maid/user_agent.rb +2 -2
- data/lib/maid/version.rb +5 -2
- data/lib/maid/watch.rb +10 -12
- data/maid.gemspec +29 -22
- data/spec/dependency_spec.rb +9 -8
- data/spec/lib/maid/app_spec.rb +15 -7
- data/spec/lib/maid/maid_spec.rb +63 -41
- data/spec/lib/maid/numeric_extensions_spec.rb +1 -1
- data/spec/lib/maid/rake/single_rule_spec.rb +4 -5
- data/spec/lib/maid/rake/task_spec.rb +3 -5
- data/spec/lib/maid/rule_spec.rb +1 -1
- data/spec/lib/maid/tools_spec.rb +87 -85
- data/spec/lib/maid/trash_migration_spec.rb +7 -6
- data/spec/lib/maid_spec.rb +1 -1
- data/spec/spec_helper.rb +18 -3
- metadata +161 -58
data/lib/maid/tools.rb
CHANGED
@@ -26,7 +26,8 @@ module Maid::Tools
|
|
26
26
|
|
27
27
|
# Move `sources` to a `destination` directory.
|
28
28
|
#
|
29
|
-
# Movement is only allowed to directories that already exist. If your
|
29
|
+
# Movement is only allowed to directories that already exist. If your
|
30
|
+
# intention is to rename, see the `rename` method.
|
30
31
|
#
|
31
32
|
# ## Examples
|
32
33
|
#
|
@@ -43,12 +44,13 @@ module Maid::Tools
|
|
43
44
|
|
44
45
|
if File.directory?(expanded_destination)
|
45
46
|
expand_all(sources).each do |source|
|
46
|
-
log("move #{
|
47
|
-
FileUtils.mv(source, expanded_destination,
|
47
|
+
log("move #{sh_escape(source)} #{sh_escape(expanded_destination)}")
|
48
|
+
FileUtils.mv(source, expanded_destination, **@file_options)
|
48
49
|
end
|
49
50
|
else
|
50
51
|
# Unix `mv` warns about the target not being a directory with multiple sources. Maid checks the same.
|
51
|
-
warn("skipping move because #{
|
52
|
+
warn("skipping move because #{sh_escape(expanded_destination)} is not a" \
|
53
|
+
"directory (use 'mkdir' to create first, or use 'rename')")
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
@@ -56,7 +58,8 @@ module Maid::Tools
|
|
56
58
|
#
|
57
59
|
# Any directories needed in order to complete the rename are made automatically.
|
58
60
|
#
|
59
|
-
# Overwriting is not allowed; it logs a warning. If overwriting is desired,
|
61
|
+
# Overwriting is not allowed; it logs a warning. If overwriting is desired,
|
62
|
+
# use `remove` to delete the file first, then use `rename`.
|
60
63
|
#
|
61
64
|
# ## Examples
|
62
65
|
#
|
@@ -78,10 +81,10 @@ module Maid::Tools
|
|
78
81
|
mkdir(File.dirname(destination))
|
79
82
|
|
80
83
|
if File.exist?(destination)
|
81
|
-
warn("skipping rename of #{
|
84
|
+
warn("skipping rename of #{sh_escape(source)} to #{sh_escape(destination)} because it would overwrite")
|
82
85
|
else
|
83
|
-
log("rename #{
|
84
|
-
FileUtils.mv(source, destination,
|
86
|
+
log("rename #{sh_escape(source)} #{sh_escape(destination)}")
|
87
|
+
FileUtils.mv(source, destination, **@file_options)
|
85
88
|
end
|
86
89
|
end
|
87
90
|
|
@@ -115,26 +118,27 @@ module Maid::Tools
|
|
115
118
|
def trash(paths, options = {})
|
116
119
|
# ## Implementation Notes
|
117
120
|
#
|
118
|
-
# Trashing files correctly is surprisingly hard. What Maid ends up doing
|
119
|
-
# solutions: moving the file.
|
121
|
+
# Trashing files correctly is surprisingly hard. What Maid ends up doing
|
122
|
+
# is one the easiest, most foolproof solutions: moving the file.
|
120
123
|
#
|
121
|
-
# Unfortunately, that means it's not possile to restore files automatically
|
122
|
-
# of the file is lost.
|
124
|
+
# Unfortunately, that means it's not possile to restore files automatically
|
125
|
+
# in OSX or Ubuntu. The previous location of the file is lost.
|
123
126
|
#
|
124
|
-
# OSX support depends on AppleScript or would require a not-yet-written C
|
125
|
-
#
|
126
|
-
#
|
127
|
+
# OSX support depends on AppleScript or would require a not-yet-written C
|
128
|
+
# extension to interface with the OS. The AppleScript solution is less
|
129
|
+
# than ideal: the user has to be logged in, Finder has to be running, and
|
130
|
+
# it makes the "trash can sound" every time a file is moved.
|
127
131
|
#
|
128
|
-
# Ubuntu makes it easy to implement, and there's a Python library for doing
|
129
|
-
# not a Ruby equivalent yet.
|
132
|
+
# Ubuntu makes it easy to implement, and there's a Python library for doing
|
133
|
+
# so (see `trash-cli`). However, there's not a Ruby equivalent yet.
|
130
134
|
|
131
135
|
expand_all(paths).each do |path|
|
132
136
|
target = File.join(@trash_path, File.basename(path))
|
133
|
-
safe_trash_path = File.join(@trash_path, "#{
|
137
|
+
safe_trash_path = File.join(@trash_path, "#{File.basename(path)} #{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}")
|
134
138
|
|
135
139
|
if options[:remove_over] &&
|
136
|
-
|
137
|
-
|
140
|
+
File.exist?(path) &&
|
141
|
+
disk_usage(path) > options[:remove_over]
|
138
142
|
remove(path)
|
139
143
|
end
|
140
144
|
|
@@ -150,8 +154,10 @@ module Maid::Tools
|
|
150
154
|
|
151
155
|
# Copy from `sources` to `destination`
|
152
156
|
#
|
153
|
-
# The path is not copied if a file already exists at the destination with the
|
154
|
-
#
|
157
|
+
# The path is not copied if a file already exists at the destination with the
|
158
|
+
# same name. A warning is logged instead. Note: Similar functionality is
|
159
|
+
# provided by the sync tool, but this requires installation of the `rsync`
|
160
|
+
# binary
|
155
161
|
# ## Examples
|
156
162
|
#
|
157
163
|
# Single path:
|
@@ -166,20 +172,23 @@ module Maid::Tools
|
|
166
172
|
destination = expand(destination)
|
167
173
|
|
168
174
|
expand_all(sources).each do |source|
|
169
|
-
|
175
|
+
target = File.join(destination, File.basename(source))
|
170
176
|
|
171
|
-
|
172
|
-
|
173
|
-
FileUtils.cp(source, destination, @file_options)
|
177
|
+
if File.exist?(target)
|
178
|
+
warn("skipping copy because #{sh_escape(source)} because #{sh_escape(target)} already exists")
|
174
179
|
else
|
175
|
-
|
180
|
+
log("cp #{sh_escape(source)} #{sh_escape(destination)}")
|
181
|
+
FileUtils.cp(source, destination, **@file_options)
|
176
182
|
end
|
177
183
|
end
|
178
184
|
end
|
179
185
|
|
180
186
|
# Delete the files at the given path recursively.
|
181
187
|
#
|
182
|
-
# **NOTE**: In most cases, `trash` is a safer choice, since the files will be
|
188
|
+
# **NOTE**: In most cases, `trash` is a safer choice, since the files will be
|
189
|
+
# recoverable by retreiving them from the trash. Once you delete a file
|
190
|
+
# using `remove`, it's gone! Please use `trash` whenever possible and only
|
191
|
+
# use `remove` when necessary.
|
183
192
|
#
|
184
193
|
# ## Options
|
185
194
|
#
|
@@ -207,8 +216,8 @@ module Maid::Tools
|
|
207
216
|
expand_all(paths).each do |path|
|
208
217
|
options = @file_options.merge(options)
|
209
218
|
|
210
|
-
log("Removing #{
|
211
|
-
FileUtils.rm_r(path, options)
|
219
|
+
log("Removing #{sh_escape(path)}")
|
220
|
+
FileUtils.rm_r(path, **options)
|
212
221
|
end
|
213
222
|
end
|
214
223
|
|
@@ -240,10 +249,10 @@ module Maid::Tools
|
|
240
249
|
# dir('~/Music/**/*.m4a')
|
241
250
|
#
|
242
251
|
def dir(globs)
|
243
|
-
expand_all(globs)
|
244
|
-
map { |glob| Dir.glob(glob) }
|
245
|
-
flatten
|
246
|
-
sort
|
252
|
+
expand_all(globs)
|
253
|
+
.map { |glob| Dir.glob(glob) }
|
254
|
+
.flatten
|
255
|
+
.sort
|
247
256
|
end
|
248
257
|
|
249
258
|
# Same as `dir`, but excludes files that are (possibly) being
|
@@ -256,8 +265,8 @@ module Maid::Tools
|
|
256
265
|
# move dir_safe('~/Downloads/*.deb'), '~/Archive/Software'
|
257
266
|
#
|
258
267
|
def dir_safe(globs)
|
259
|
-
dir(globs)
|
260
|
-
reject { |path| downloading?(path) }
|
268
|
+
dir(globs)
|
269
|
+
.reject { |path| downloading?(path) }
|
261
270
|
end
|
262
271
|
|
263
272
|
# Give only files matching the given glob.
|
@@ -265,8 +274,8 @@ module Maid::Tools
|
|
265
274
|
# This is the same as `dir` but only includes actual files (no directories or symlinks).
|
266
275
|
#
|
267
276
|
def files(globs)
|
268
|
-
dir(globs)
|
269
|
-
select { |f| File.file?(f) }
|
277
|
+
dir(globs)
|
278
|
+
.select { |f| File.file?(f) }
|
270
279
|
end
|
271
280
|
|
272
281
|
# Escape characters that have special meaning as a part of path global patterns.
|
@@ -277,7 +286,7 @@ module Maid::Tools
|
|
277
286
|
#
|
278
287
|
# escape_glob('test [tmp]') # => 'test \\[tmp\\]'
|
279
288
|
def escape_glob(glob)
|
280
|
-
glob.gsub(/[
|
289
|
+
glob.gsub(/[{}\[\]]/) { |s| '\\' + s }
|
281
290
|
end
|
282
291
|
|
283
292
|
# Create a directory and all of its parent directories.
|
@@ -301,8 +310,8 @@ module Maid::Tools
|
|
301
310
|
# move('~/Downloads/Pink Floyd*.mp3', mkdir('~/Music/Pink Floyd/'))
|
302
311
|
def mkdir(path, options = {})
|
303
312
|
path = expand(path)
|
304
|
-
log("mkdir -p #{
|
305
|
-
FileUtils.mkdir_p(path,
|
313
|
+
log("mkdir -p #{sh_escape(path)}")
|
314
|
+
FileUtils.mkdir_p(path, **@file_options.merge(options))
|
306
315
|
path
|
307
316
|
end
|
308
317
|
|
@@ -346,7 +355,7 @@ module Maid::Tools
|
|
346
355
|
#
|
347
356
|
# locate('foo.zip') # => ['/a/foo.zip', '/b/foo.zip']
|
348
357
|
def locate(name)
|
349
|
-
cmd("#{Maid::Platform::Commands.locate} #{
|
358
|
+
cmd("#{Maid::Platform::Commands.locate} #{sh_escape(name)}").split("\n")
|
350
359
|
end
|
351
360
|
|
352
361
|
# [Mac OS X] Use Spotlight metadata to determine the site from which a file was downloaded.
|
@@ -390,14 +399,14 @@ module Maid::Tools
|
|
390
399
|
#
|
391
400
|
def dupes_in(globs)
|
392
401
|
dupes = []
|
393
|
-
files(globs)
|
394
|
-
group_by { |f| size_of(f) }
|
395
|
-
reject { |
|
396
|
-
map do |
|
397
|
-
dupes += candidates
|
398
|
-
|
399
|
-
|
400
|
-
|
402
|
+
files(globs) # Start by filtering out non-files
|
403
|
+
.group_by { |f| size_of(f) } # ... then grouping by size, since that's fast
|
404
|
+
.reject { |_s, p| p.length < 2 } # ... and filter out any non-dupes
|
405
|
+
.map do |_size, candidates|
|
406
|
+
dupes += candidates
|
407
|
+
.group_by { |p| checksum_of(p) } # Now group our candidates by a slower checksum calculation
|
408
|
+
.reject { |_c, p| p.length < 2 } # ... and filter out any non-dupes
|
409
|
+
.values
|
401
410
|
end
|
402
411
|
dupes
|
403
412
|
end
|
@@ -411,9 +420,9 @@ module Maid::Tools
|
|
411
420
|
# trash newest_dupes_in('~/Downloads/*')
|
412
421
|
#
|
413
422
|
def newest_dupes_in(globs)
|
414
|
-
dupes_in(globs)
|
415
|
-
map { |dupes| dupes.sort_by { |p| File.mtime(p) }[1..-1] }
|
416
|
-
flatten
|
423
|
+
dupes_in(globs)
|
424
|
+
.map { |dupes| dupes.sort_by { |p| File.mtime(p) }[1..-1] }
|
425
|
+
.flatten
|
417
426
|
end
|
418
427
|
|
419
428
|
# Convenience method for `dupes_in` that excludes the dupe with the shortest name.
|
@@ -427,9 +436,9 @@ module Maid::Tools
|
|
427
436
|
# trash verbose_dupes_in('~/Downloads/*')
|
428
437
|
#
|
429
438
|
def verbose_dupes_in(globs)
|
430
|
-
dupes_in(globs)
|
431
|
-
map { |dupes| dupes.sort_by { |p| File.basename(p).length }[1..-1] }
|
432
|
-
flatten
|
439
|
+
dupes_in(globs)
|
440
|
+
.map { |dupes| dupes.sort_by { |p| File.basename(p).length }[1..-1] }
|
441
|
+
.flatten
|
433
442
|
end
|
434
443
|
|
435
444
|
# Determine the dimensions of GIF, PNG, JPEG, or TIFF images.
|
@@ -466,7 +475,7 @@ module Maid::Tools
|
|
466
475
|
#
|
467
476
|
# duration_s('foo.mp3') # => 235.705
|
468
477
|
def duration_s(path)
|
469
|
-
cmd("mdls -raw -name kMDItemDurationSeconds #{
|
478
|
+
cmd("mdls -raw -name kMDItemDurationSeconds #{sh_escape(path)}").to_f
|
470
479
|
end
|
471
480
|
|
472
481
|
# List the contents of a zip file.
|
@@ -489,15 +498,13 @@ module Maid::Tools
|
|
489
498
|
#
|
490
499
|
# disk_usage('foo.zip') # => 136
|
491
500
|
def disk_usage(path)
|
492
|
-
raw = cmd("du -s #{
|
501
|
+
raw = cmd("du -s #{sh_escape(path)}")
|
493
502
|
# FIXME: This reports in kilobytes, but should probably report in bytes.
|
494
503
|
usage_kb = raw.split(/\s+/).first.to_i
|
495
504
|
|
496
|
-
if usage_kb.zero?
|
497
|
-
|
498
|
-
|
499
|
-
usage_kb
|
500
|
-
end
|
505
|
+
raise "Stopping pessimistically because of unexpected value from du (#{raw.inspect})" if usage_kb.zero?
|
506
|
+
|
507
|
+
usage_kb
|
501
508
|
end
|
502
509
|
|
503
510
|
# Get the creation time of a file.
|
@@ -572,8 +579,8 @@ module Maid::Tools
|
|
572
579
|
# git_piston('~/code/projectname')
|
573
580
|
def git_piston(path)
|
574
581
|
full_path = expand(path)
|
575
|
-
stdout = cmd("cd #{
|
576
|
-
log("Fired git piston on #{
|
582
|
+
stdout = cmd("cd #{sh_escape(full_path)} && git pull && git push 2>&1")
|
583
|
+
log("Fired git piston on #{sh_escape(full_path)}. STDOUT:\n\n#{stdout}")
|
577
584
|
end
|
578
585
|
|
579
586
|
deprecated :git_piston, 'SparkleShare (http://sparkleshare.org/)'
|
@@ -612,7 +619,7 @@ module Maid::Tools
|
|
612
619
|
from = expand(from) + (from.end_with?('/') ? '/' : '')
|
613
620
|
to = expand(to) + (to.end_with?('/') ? '/' : '')
|
614
621
|
# default options
|
615
|
-
options = { :
|
622
|
+
options = { archive: true, update: true }.merge(options)
|
616
623
|
ops = []
|
617
624
|
ops << '-a' if options[:archive]
|
618
625
|
ops << '-v' if options[:verbose]
|
@@ -621,12 +628,12 @@ module Maid::Tools
|
|
621
628
|
ops << '-n' if @file_options[:noop]
|
622
629
|
|
623
630
|
Array(options[:exclude]).each do |path|
|
624
|
-
ops << "--exclude=#{
|
631
|
+
ops << "--exclude=#{sh_escape(path)}"
|
625
632
|
end
|
626
633
|
|
627
634
|
ops << '--delete' if options[:delete]
|
628
|
-
stdout = cmd("rsync #{
|
629
|
-
log("Fired sync from #{
|
635
|
+
stdout = cmd("rsync #{ops.join(' ')} #{sh_escape(from)} #{sh_escape(to)} 2>&1")
|
636
|
+
log("Fired sync from #{sh_escape(from)} to #{sh_escape(to)}. STDOUT:\n\n#{stdout}")
|
630
637
|
end
|
631
638
|
|
632
639
|
# [Mac OS X] Use Spotlight metadata to determine which content types a file has.
|
@@ -644,7 +651,8 @@ module Maid::Tools
|
|
644
651
|
#
|
645
652
|
# ## Examples
|
646
653
|
#
|
647
|
-
# content_types('foo.zip') # => ["public.zip-archive", "com.pkware.zip-archive",
|
654
|
+
# content_types('foo.zip') # => ["public.zip-archive", "com.pkware.zip-archive",
|
655
|
+
# "public.archive", "application/zip", "application"]
|
648
656
|
# content_types('bar.jpg') # => ["public.jpeg", "public.image", "image/jpeg", "image"]
|
649
657
|
def content_types(path)
|
650
658
|
[spotlight_content_types(path), mime_type(path), media_type(path)].flatten
|
@@ -658,9 +666,9 @@ module Maid::Tools
|
|
658
666
|
def mime_type(path)
|
659
667
|
type = MIME::Types.type_for(path)[0]
|
660
668
|
|
661
|
-
|
662
|
-
|
663
|
-
|
669
|
+
return unless type
|
670
|
+
|
671
|
+
[type.media_type, type.sub_type].join('/')
|
664
672
|
end
|
665
673
|
|
666
674
|
# Get the Internet media type of the file.
|
@@ -673,9 +681,9 @@ module Maid::Tools
|
|
673
681
|
def media_type(path)
|
674
682
|
type = MIME::Types.type_for(path)[0]
|
675
683
|
|
676
|
-
|
677
|
-
|
678
|
-
|
684
|
+
return unless type
|
685
|
+
|
686
|
+
type.media_type
|
679
687
|
end
|
680
688
|
|
681
689
|
# Filter an array by content types.
|
@@ -722,24 +730,24 @@ module Maid::Tools
|
|
722
730
|
# Look for files.
|
723
731
|
return false if Dir.glob(root + '/*').select { |f| File.file?(f) }.length > 0
|
724
732
|
|
725
|
-
empty_dirs = Dir.glob(root + '/**/*').select
|
733
|
+
empty_dirs = Dir.glob(root + '/**/*').select do |d|
|
726
734
|
File.directory?(d)
|
727
|
-
|
735
|
+
end.reverse.select do |d|
|
728
736
|
# `.reverse` sorts deeper directories first.
|
729
737
|
|
730
738
|
# If the directory is empty, its parent should ignore it.
|
731
|
-
should_ignore = Dir.glob(d + '/*').select
|
739
|
+
should_ignore = Dir.glob(d + '/*').select do |n|
|
732
740
|
!ignore.include?(n)
|
733
|
-
|
741
|
+
end.length == 0
|
734
742
|
|
735
743
|
ignore << d if should_ignore
|
736
744
|
|
737
745
|
should_ignore
|
738
|
-
|
746
|
+
end
|
739
747
|
|
740
|
-
Dir.glob(root + '/*').select
|
748
|
+
Dir.glob(root + '/*').select do |n|
|
741
749
|
!empty_dirs.include?(n)
|
742
|
-
|
750
|
+
end.length == 0
|
743
751
|
end
|
744
752
|
|
745
753
|
# Given an array of directories, return a new array without any child
|
@@ -749,14 +757,13 @@ module Maid::Tools
|
|
749
757
|
#
|
750
758
|
# ignore_child_dirs(["foo", "foo/a", "foo/b", "bar"]) # => ["foo", "bar"]
|
751
759
|
def ignore_child_dirs(arr)
|
752
|
-
arr.sort
|
760
|
+
arr.sort do |x, y|
|
753
761
|
y.count('/') - x.count('/')
|
754
|
-
|
762
|
+
end.select do |d|
|
755
763
|
!arr.include?(File.dirname(d))
|
756
|
-
|
764
|
+
end
|
757
765
|
end
|
758
766
|
|
759
|
-
|
760
767
|
# Get a list of Finder labels of a file or directory. Only available on OS X when you have tag installed.
|
761
768
|
#
|
762
769
|
# ## Example
|
@@ -772,7 +779,8 @@ module Maid::Tools
|
|
772
779
|
end
|
773
780
|
end
|
774
781
|
|
775
|
-
# Tell if a file or directory has any Finder labels. Only available on OS X
|
782
|
+
# Tell if a file or directory has any Finder labels. Only available on OS X
|
783
|
+
# when you have tag installed.
|
776
784
|
#
|
777
785
|
# ## Example
|
778
786
|
#
|
@@ -786,7 +794,8 @@ module Maid::Tools
|
|
786
794
|
end
|
787
795
|
end
|
788
796
|
|
789
|
-
# Tell if a file or directory has a certain Finder labels. Only available on
|
797
|
+
# Tell if a file or directory has a certain Finder labels. Only available on
|
798
|
+
# OS X when you have tag installed.
|
790
799
|
#
|
791
800
|
# ## Example
|
792
801
|
#
|
@@ -801,52 +810,55 @@ module Maid::Tools
|
|
801
810
|
end
|
802
811
|
end
|
803
812
|
|
804
|
-
# Add a Finder label or a list of labels to a file or directory. Only
|
813
|
+
# Add a Finder label or a list of labels to a file or directory. Only
|
814
|
+
# available on OS X when you have tag installed.
|
805
815
|
#
|
806
816
|
# ## Example
|
807
817
|
#
|
808
818
|
# add_tag("~/Downloads/a.dmg.download", "Unfinished")
|
809
819
|
def add_tag(path, tag)
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
820
|
+
return unless has_tag_available_and_warn?
|
821
|
+
|
822
|
+
path = expand(path)
|
823
|
+
ts = Array(tag).join(',')
|
824
|
+
log "add tags #{ts} to #{path}"
|
825
|
+
return if @file_options[:noop]
|
826
|
+
|
827
|
+
cmd("tag -a #{sh_escape(ts)} #{sh_escape(path)}")
|
818
828
|
end
|
819
829
|
|
820
|
-
# Remove a Finder label or a list of labels from a file or directory. Only
|
830
|
+
# Remove a Finder label or a list of labels from a file or directory. Only
|
831
|
+
# available on OS X when you have tag installed.
|
821
832
|
#
|
822
833
|
# ## Example
|
823
834
|
#
|
824
835
|
# remove_tag("~/Downloads/a.dmg", "Unfinished")
|
825
836
|
def remove_tag(path, tag)
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
837
|
+
return unless has_tag_available_and_warn?
|
838
|
+
|
839
|
+
path = expand(path)
|
840
|
+
ts = Array(tag).join(',')
|
841
|
+
log "remove tags #{ts} from #{path}"
|
842
|
+
return if @file_options[:noop]
|
843
|
+
|
844
|
+
cmd("tag -r #{sh_escape(ts)} #{sh_escape(path)}")
|
834
845
|
end
|
835
846
|
|
836
|
-
# Set Finder label of a file or directory to a label or a list of labels.
|
847
|
+
# Set Finder label of a file or directory to a label or a list of labels.
|
848
|
+
# Only available on OS X when you have tag installed.
|
837
849
|
#
|
838
850
|
# ## Example
|
839
851
|
#
|
840
852
|
# set_tag("~/Downloads/a.dmg.download", "Unfinished")
|
841
853
|
def set_tag(path, tag)
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
854
|
+
return unless has_tag_available_and_warn?
|
855
|
+
|
856
|
+
path = expand(path)
|
857
|
+
ts = Array(tag).join(',')
|
858
|
+
log "set tags #{ts} to #{path}"
|
859
|
+
return if @file_options[:noop]
|
860
|
+
|
861
|
+
cmd("tag -s #{sh_escape(ts)} #{sh_escape(path)}")
|
850
862
|
end
|
851
863
|
|
852
864
|
# Tell if a file is hidden
|
@@ -856,7 +868,7 @@ module Maid::Tools
|
|
856
868
|
# hidden?("~/.maid") # => true
|
857
869
|
def hidden?(path)
|
858
870
|
if Maid::Platform.osx?
|
859
|
-
raw = cmd("mdls -raw -name kMDItemFSInvisible #{
|
871
|
+
raw = cmd("mdls -raw -name kMDItemFSInvisible #{sh_escape(path)}")
|
860
872
|
raw == '1'
|
861
873
|
else
|
862
874
|
p = Pathname.new(expand(path))
|
@@ -872,9 +884,9 @@ module Maid::Tools
|
|
872
884
|
def has_been_used?(path)
|
873
885
|
if Maid::Platform.osx?
|
874
886
|
path = expand(path)
|
875
|
-
raw = cmd("mdls -raw -name kMDItemLastUsedDate #{
|
887
|
+
raw = cmd("mdls -raw -name kMDItemLastUsedDate #{sh_escape(path)}")
|
876
888
|
|
877
|
-
if raw ==
|
889
|
+
if raw == '(null)'
|
878
890
|
false
|
879
891
|
else
|
880
892
|
begin
|
@@ -897,9 +909,9 @@ module Maid::Tools
|
|
897
909
|
def used_at(path)
|
898
910
|
if Maid::Platform.osx?
|
899
911
|
path = expand(path)
|
900
|
-
raw = cmd("mdls -raw -name kMDItemLastUsedDate #{
|
912
|
+
raw = cmd("mdls -raw -name kMDItemLastUsedDate #{sh_escape(path)}")
|
901
913
|
|
902
|
-
if raw ==
|
914
|
+
if raw == '(null)'
|
903
915
|
nil
|
904
916
|
else
|
905
917
|
begin
|
@@ -921,9 +933,9 @@ module Maid::Tools
|
|
921
933
|
def added_at(path)
|
922
934
|
if Maid::Platform.osx?
|
923
935
|
path = expand(path)
|
924
|
-
raw = cmd("mdls -raw -name kMDItemDateAdded #{
|
936
|
+
raw = cmd("mdls -raw -name kMDItemDateAdded #{sh_escape(path)}")
|
925
937
|
|
926
|
-
if raw ==
|
938
|
+
if raw == '(null)'
|
927
939
|
1.second.ago
|
928
940
|
else
|
929
941
|
begin
|
@@ -948,9 +960,9 @@ module Maid::Tools
|
|
948
960
|
true
|
949
961
|
else
|
950
962
|
if Maid::Platform.osx?
|
951
|
-
warn(
|
963
|
+
warn('To use this feature, you need `tag` installed. Run `brew install tag`')
|
952
964
|
else
|
953
|
-
warn(
|
965
|
+
warn('sorry, tagging is unavailable on your platform')
|
954
966
|
end
|
955
967
|
|
956
968
|
false
|
@@ -979,13 +991,16 @@ module Maid::Tools
|
|
979
991
|
|
980
992
|
def mdls_to_array(path, attribute)
|
981
993
|
if Maid::Platform.osx?
|
982
|
-
raw = cmd("mdls -raw -name #{sh_escape(attribute)} #{
|
994
|
+
raw = cmd("mdls -raw -name #{sh_escape(attribute)} #{sh_escape(path)}")
|
983
995
|
|
984
996
|
if raw.empty?
|
985
997
|
[]
|
986
998
|
else
|
987
999
|
clean = raw[1, raw.length - 2]
|
988
|
-
clean.split(/,\s+/).map
|
1000
|
+
clean.split(/,\s+/).map do |s|
|
1001
|
+
t = s.strip
|
1002
|
+
t[1, t.length - 2]
|
1003
|
+
end
|
989
1004
|
end
|
990
1005
|
else
|
991
1006
|
[]
|
data/lib/maid/trash_migration.rb
CHANGED
@@ -8,7 +8,7 @@ module Maid
|
|
8
8
|
def incorrect_trash
|
9
9
|
File.expand_path('~/.Trash') + '/'
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def correct_trash
|
13
13
|
Maid.new.trash_path
|
14
14
|
end
|
@@ -18,16 +18,16 @@ module Maid
|
|
18
18
|
File.directory?(incorrect_trash) &&
|
19
19
|
!ENV['MAID_NO_MIGRATE_TRASH']
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
def perform
|
23
|
-
maid = ::Maid::Maid.new(:
|
23
|
+
maid = ::Maid::Maid.new(trash_path: correct_trash)
|
24
24
|
# Use local variable so it's available in the closure used by `instance_eval`
|
25
25
|
path = incorrect_trash
|
26
26
|
|
27
27
|
# Might as well use Maid itself for this :)
|
28
28
|
maid.instance_eval do
|
29
29
|
rule 'Migrate Linux trash to correct path' do
|
30
|
-
trash(dir("#{
|
30
|
+
trash(dir("#{path}/*"))
|
31
31
|
trash(path)
|
32
32
|
end
|
33
33
|
end
|
data/lib/maid/user_agent.rb
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
module Maid::UserAgent
|
6
6
|
class << self
|
7
7
|
def short
|
8
|
-
"Maid/#{
|
8
|
+
"Maid/#{::Maid.const_get(:VERSION)}"
|
9
9
|
end
|
10
10
|
|
11
11
|
# This used to be called `#to_s`, but that made things difficult when testing.
|
12
12
|
def value
|
13
|
-
"#{
|
13
|
+
"#{short} (#{RUBY_DESCRIPTION})"
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
data/lib/maid/version.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Maid
|
2
|
-
VERSION = '0.10.0-alpha.
|
3
|
-
SUMMARY = 'Be lazy. Let Maid clean up after you, based on rules you define.
|
4
|
+
VERSION = '0.10.0-alpha.2'
|
5
|
+
SUMMARY = 'Be lazy. Let Maid clean up after you, based on rules you define. ' \
|
6
|
+
'Think of it as "Hazel for hackers".'
|
4
7
|
end
|