maid 0.10.0.pre.alpha.1 → 0.10.0.pre.alpha.2
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/.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
|