unmac 0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +21 -0
- data/README +71 -0
- data/Rakefile +7 -0
- data/bin/unmac +107 -0
- data/lib/unmacer.rb +160 -0
- data/test/test_unmac.rb +241 -0
- data/test/test_unmacer.rb +234 -0
- data/unmac.gemspec +24 -0
- metadata +61 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2009 Xavier Noria
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
unmac -- Delete spurious Mac files under a directory or volume
|
2
|
+
|
3
|
+
Usage:
|
4
|
+
unmac [OPTIONS] directory ...
|
5
|
+
|
6
|
+
Options:
|
7
|
+
-h, --help Print this help
|
8
|
+
-v, --verbose Show processing
|
9
|
+
-p, --pretend Show what would be deleted, but don't do it
|
10
|
+
-s, --keep-spotlight Do not delete Spotlight data
|
11
|
+
-f, --keep-fsevents Do not delete Time Machine stuff
|
12
|
+
-t, --keep-trashes Do not delete volume trashes
|
13
|
+
-m, --keep-macosx Do not delete "__MACOSX" directories
|
14
|
+
-r, --keep-dsstore Do not delete ".DS_Store" files
|
15
|
+
-d, --keep-apple-double Do not delete "._*" ghost files
|
16
|
+
-o, --keep-apple-double-orphans Delete "._foo.txt" only if "foo.txt" exists
|
17
|
+
-i, --keep-custom-folder-icons Do not delete custom folder icons
|
18
|
+
|
19
|
+
Description:
|
20
|
+
When a Mac copies files to volumes that have a different file system it adds
|
21
|
+
some auxiliary files that represent different kinds of metadata. This typically
|
22
|
+
happens with memory sticks, network drives, SD cards, etc. Mac archivers like
|
23
|
+
zip(1) add special files as well, like "__MACOSX", you'll see them for example
|
24
|
+
if you extract a Zip file like that on Windows.
|
25
|
+
|
26
|
+
The purpose of these special files is to let another Mac rebuild the metadata
|
27
|
+
from them, Spotlight integration, etc. But if you are not interested in these
|
28
|
+
features your directory or volume just gets cluttered. This utility removes
|
29
|
+
all those extra files.
|
30
|
+
|
31
|
+
What's deleted:
|
32
|
+
This is just a summary, for more detailed explanations and pointers see the
|
33
|
+
comments in unmacer.rb:
|
34
|
+
|
35
|
+
* Spotlight leaves a folder called ".Spotlight-V100" in the root directory
|
36
|
+
of volumes.
|
37
|
+
|
38
|
+
* Time Machine relies on a folder called ".fseventsd" in the root directory
|
39
|
+
of volumes.
|
40
|
+
|
41
|
+
* A folder called ".Trashes" in the root directory of volumes stores their
|
42
|
+
trashes. Those ones are taken into account by the Trash in the Dock.
|
43
|
+
|
44
|
+
* Some archivers create auxiliary directories called "__MACOSX".
|
45
|
+
|
46
|
+
* Finder and Spotlight data related to each folder gets stored in an
|
47
|
+
extra file called ".DS_Store".
|
48
|
+
|
49
|
+
* For each file "foo.txt", its resource fork and some additional stuff
|
50
|
+
are stored in an accompaining ghost file called "._foo.txt". The pattern
|
51
|
+
is to prepend "._" to the original file name.
|
52
|
+
|
53
|
+
* If a folder has a custom icon you get a file "Icon^M" (that ^M is a hard CR).
|
54
|
+
|
55
|
+
Some stuff is only found in the root folder of volumes, unmac looks for them
|
56
|
+
in any directory you pass as argument, but not on their subdirectories.
|
57
|
+
|
58
|
+
Installation:
|
59
|
+
unmac is distributed as a Ruby gem. Once you have Ruby and RubyGems installed
|
60
|
+
execute
|
61
|
+
|
62
|
+
gem sources -a http://gems.github.com # you need this only once
|
63
|
+
gem install fxn-unmac
|
64
|
+
|
65
|
+
This program is portable.
|
66
|
+
|
67
|
+
License:
|
68
|
+
See the MIT-LICENSE file included in the distribution.
|
69
|
+
|
70
|
+
Copyright:
|
71
|
+
Copyright (C) 2009 Xavier Noria.
|
data/Rakefile
ADDED
data/bin/unmac
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'getoptlong'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'unmacer'
|
6
|
+
|
7
|
+
def usage
|
8
|
+
puts <<USAGE
|
9
|
+
unmac -- Delete spurious Mac files under a directory or volume
|
10
|
+
|
11
|
+
Usage:
|
12
|
+
unmac [OPTIONS] directory ...
|
13
|
+
|
14
|
+
Options:
|
15
|
+
-h, --help Print this help
|
16
|
+
-v, --verbose Show processing
|
17
|
+
-p, --pretend Show what would be deleted, but don't do it
|
18
|
+
-s, --keep-spotlight Do not delete Spotlight data
|
19
|
+
-f, --keep-fsevents Do not delete Time Machine stuff
|
20
|
+
-t, --keep-trashes Do not delete volume trashes
|
21
|
+
-m, --keep-macosx Do not delete "__MACOSX" directories
|
22
|
+
-r, --keep-dsstore Do not delete ".DS_Store" files
|
23
|
+
-d, --keep-apple-double Do not delete "._*" ghost files
|
24
|
+
-o, --keep-apple-double-orphans Delete "._foo.txt" only if "foo.txt" exists
|
25
|
+
-i, --keep-custom-folder-icons Do not delete custom folder icons
|
26
|
+
|
27
|
+
Description:
|
28
|
+
When a Mac copies files to volumes that have a different file system it adds
|
29
|
+
some auxiliary files that represent different kinds of metadata. This typically
|
30
|
+
happens with memory sticks, network drives, SD cards, etc. Mac archivers like
|
31
|
+
zip(1) add special files as well, like "#{Unmacer::MACOSX}", you'll see them
|
32
|
+
for example if you extract a Zip file like that on Windows.
|
33
|
+
|
34
|
+
The purpose of these special files is to let another Mac rebuild the metadata
|
35
|
+
from them, Spotlight integration, etc. But if you are not interested in these
|
36
|
+
features your directory or volume just gets cluttered. This utility removes
|
37
|
+
all those extra files.
|
38
|
+
|
39
|
+
What's deleted:
|
40
|
+
This is just a summary, for more detailed explanations and pointers see the
|
41
|
+
comments in unmacer.rb:
|
42
|
+
|
43
|
+
* Spotlight leaves a folder called "#{Unmacer::SPOTLIGHT}" in the root directory
|
44
|
+
of volumes.
|
45
|
+
|
46
|
+
* Time Machine relies on a folder called "#{Unmacer::FSEVENTS}" in the root directory
|
47
|
+
of volumes.
|
48
|
+
|
49
|
+
* A folder called "#{Unmacer::TRASHES}" in the root directory of volumes stores their
|
50
|
+
trashes. Those ones are taken into account by the Trash in the Dock.
|
51
|
+
|
52
|
+
* Some archivers create auxiliary directories called "#{Unmacer::MACOSX}".
|
53
|
+
|
54
|
+
* Finder and Spotlight data related to each folder gets stored in an
|
55
|
+
extra file called "#{Unmacer::DSSTORE}".
|
56
|
+
|
57
|
+
* For each file "foo.txt", its resource fork and some additional stuff
|
58
|
+
are stored in an accompaining ghost file called "._foo.txt". The pattern
|
59
|
+
is to prepend "._" to the original file name.
|
60
|
+
|
61
|
+
* If a folder has a custom icon you get a file "Icon^M" (that ^M is a hard CR).
|
62
|
+
|
63
|
+
Some stuff is only found in the root folder of volumes, unmac looks for them
|
64
|
+
in any directory you pass as argument, but not on their subdirectories.
|
65
|
+
USAGE
|
66
|
+
end
|
67
|
+
|
68
|
+
opts = GetoptLong.new(
|
69
|
+
['--help', '-h', GetoptLong::NO_ARGUMENT],
|
70
|
+
['--verbose', '-v', GetoptLong::NO_ARGUMENT],
|
71
|
+
['--pretend', '-p', GetoptLong::NO_ARGUMENT],
|
72
|
+
['--keep-spotlight', '-s', GetoptLong::NO_ARGUMENT],
|
73
|
+
['--keep-fsevents', '-f', GetoptLong::NO_ARGUMENT],
|
74
|
+
['--keep-trashes', '-t', GetoptLong::NO_ARGUMENT],
|
75
|
+
['--keep-macosx', '-m', GetoptLong::NO_ARGUMENT],
|
76
|
+
['--keep-dsstore', '-r', GetoptLong::NO_ARGUMENT],
|
77
|
+
['--keep-apple-double', '-d', GetoptLong::NO_ARGUMENT],
|
78
|
+
['--keep-apple-double-orphans', '-o', GetoptLong::NO_ARGUMENT],
|
79
|
+
['--keep-custom-folder-icons', '-i', GetoptLong::NO_ARGUMENT],
|
80
|
+
['--test', GetoptLong::NO_ARGUMENT]
|
81
|
+
)
|
82
|
+
|
83
|
+
test = false
|
84
|
+
begin
|
85
|
+
unmacer = Unmacer.new
|
86
|
+
opts.each do |opt, val|
|
87
|
+
case opt
|
88
|
+
when '--help'
|
89
|
+
usage and exit
|
90
|
+
when '--verbose'
|
91
|
+
unmacer.verbose = true
|
92
|
+
when '--pretend'
|
93
|
+
unmacer.pretend = true
|
94
|
+
when /\A--(keep-.*)/
|
95
|
+
flag = $1.gsub('-', '_')
|
96
|
+
unmacer.send("#{flag}=", true)
|
97
|
+
when '--test'
|
98
|
+
test = true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
unmacer.unmac!(ARGV) unless test
|
102
|
+
rescue GetoptLong::InvalidOption
|
103
|
+
usage and exit 1
|
104
|
+
end
|
105
|
+
|
106
|
+
# The test suite for this file expects the unmacer to be the last expression.
|
107
|
+
unmacer
|
data/lib/unmacer.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'find'
|
3
|
+
|
4
|
+
class Unmacer
|
5
|
+
attr_accessor :verbose,
|
6
|
+
:pretend,
|
7
|
+
:keep_spotlight,
|
8
|
+
:keep_fsevents,
|
9
|
+
:keep_trashes,
|
10
|
+
:keep_macosx,
|
11
|
+
:keep_dsstore,
|
12
|
+
:keep_apple_double,
|
13
|
+
:keep_apple_double_orphans,
|
14
|
+
:keep_custom_folder_icons
|
15
|
+
|
16
|
+
SPOTLIGHT = '.Spotlight-V100'
|
17
|
+
FSEVENTS = '.fseventsd'
|
18
|
+
TRASHES = '.Trashes'
|
19
|
+
MACOSX = '__MACOSX'
|
20
|
+
DSSTORE = '.DS_Store'
|
21
|
+
ICON = "Icon\cM"
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
self.verbose = false
|
25
|
+
self.pretend = false
|
26
|
+
self.keep_spotlight = false
|
27
|
+
self.keep_fsevents = false
|
28
|
+
self.keep_trashes = false
|
29
|
+
self.keep_macosx = false
|
30
|
+
self.keep_dsstore = false
|
31
|
+
self.keep_apple_double = false
|
32
|
+
self.keep_apple_double_orphans = false
|
33
|
+
self.keep_custom_folder_icons = false
|
34
|
+
end
|
35
|
+
|
36
|
+
def unmac!(dirnames)
|
37
|
+
dirnames.each do |dirname|
|
38
|
+
unmac_root(dirname)
|
39
|
+
find_skipping_root(dirname) do |f|
|
40
|
+
unmac_folder(f) if File.directory?(f)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_skipping_root(path)
|
46
|
+
root_seen = false
|
47
|
+
Find.find(path) do |f|
|
48
|
+
if !root_seen
|
49
|
+
root_seen = true
|
50
|
+
else
|
51
|
+
yield f
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def unmac_root(dirname)
|
59
|
+
# Order is important because ".Trashes" has "._.Trashes". Otherwise,
|
60
|
+
# "._.Trashes" could be left as an orphan.
|
61
|
+
unmac_folder(dirname)
|
62
|
+
delete_spotlight(dirname) unless keep_spotlight
|
63
|
+
delete_fsevents(dirname) unless keep_fsevents
|
64
|
+
delete_trashes(dirname) unless keep_trashes
|
65
|
+
end
|
66
|
+
|
67
|
+
def unmac_folder(dirname)
|
68
|
+
delete_macosx(dirname) unless keep_macosx
|
69
|
+
delete_dsstore(dirname) unless keep_dsstore
|
70
|
+
delete_apple_double(dirname) unless keep_apple_double
|
71
|
+
delete_custom_folder_icon(dirname) unless keep_custom_folder_icons
|
72
|
+
end
|
73
|
+
|
74
|
+
def delete(parent, file_or_directory)
|
75
|
+
name = File.join(parent, file_or_directory)
|
76
|
+
if File.exists?(name)
|
77
|
+
if pretend
|
78
|
+
puts "would delete #{name}"
|
79
|
+
else
|
80
|
+
# Vanilla rm_r cannot delete a directory that has AppleDoubles.
|
81
|
+
FileUtils.rmtree(name)
|
82
|
+
puts "deleted #{name}" if verbose
|
83
|
+
end
|
84
|
+
end
|
85
|
+
rescue Exception => e
|
86
|
+
$stderr.puts("could not delete #{name}: #{e.message}")
|
87
|
+
end
|
88
|
+
|
89
|
+
# Spotlight saves all its index-related files in the .Spotlight-V100 directory
|
90
|
+
# at the root level of a volume it has indexed.
|
91
|
+
#
|
92
|
+
# See http://www.thexlab.com/faqs/stopspotlightindex.html.
|
93
|
+
def delete_spotlight(dirname)
|
94
|
+
delete(dirname, SPOTLIGHT)
|
95
|
+
end
|
96
|
+
|
97
|
+
# The FSEvents framework has a daemon that dumps events from /dev/fsevents
|
98
|
+
# into log files stored in a .fseventsd directory at the root of the volume
|
99
|
+
# the events are for.
|
100
|
+
#
|
101
|
+
# See http://arstechnica.com/reviews/os/mac-os-x-10-5.ars/7.
|
102
|
+
def delete_fsevents(dirname)
|
103
|
+
delete(dirname, FSEVENTS)
|
104
|
+
end
|
105
|
+
|
106
|
+
# A volume may have a ".Trashes" folder in the root directory. The Trash in
|
107
|
+
# the Dock shows the contents of the ".Trash" folder in your home directory
|
108
|
+
# and the content of all the ".Trashes/uid" in the rest of volumes.
|
109
|
+
#
|
110
|
+
# See http://discussions.apple.com/thread.jspa?messageID=1145130.
|
111
|
+
def delete_trashes(dirname)
|
112
|
+
delete(dirname, TRASHES)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Apple's builtin Zip archiver stores some metadata in a directory called
|
116
|
+
# "__MACOSX".
|
117
|
+
#
|
118
|
+
# See http://floatingsun.net/2007/02/07/whats-with-__macosx-in-zip-files.
|
119
|
+
def delete_macosx(dirname)
|
120
|
+
delete(dirname, MACOSX)
|
121
|
+
end
|
122
|
+
|
123
|
+
# In each directory the ".DS_Store" file stores info about Finder window
|
124
|
+
# settings and Spotlight comments of its files.
|
125
|
+
#
|
126
|
+
# See http://en.wikipedia.org/wiki/.DS_Store.
|
127
|
+
def delete_dsstore(dirname)
|
128
|
+
delete(dirname, DSSTORE)
|
129
|
+
end
|
130
|
+
|
131
|
+
# When a file is copied to a volume that does not natively support HFS file
|
132
|
+
# characteristics its data fork is stored under the file's regular name, and
|
133
|
+
# the additional HFS information (resource fork, type & creator codes, etc.)
|
134
|
+
# is stored in a second file in AppleDouble format, with a name that starts
|
135
|
+
# with "._". So if the original file is called "foo.txt" you get a spurious
|
136
|
+
# ghost file called "._foo.txt".
|
137
|
+
#
|
138
|
+
# If you drag & drop a file onto a Windows partition, or you unpack a tarball
|
139
|
+
# on Linux that was built with Apple's tar(1) archiver you'll get that extra
|
140
|
+
# stuff. Reason is if the tarball is extracted into another Mac the metadata
|
141
|
+
# is preserved with this hack.
|
142
|
+
#
|
143
|
+
# See http://www.westwind.com/reference/OS-X/invisibles.html.
|
144
|
+
# See http://en.wikipedia.org/wiki/Resource_fork.
|
145
|
+
# See http://en.wikipedia.org/wiki/AppleSingle.
|
146
|
+
def delete_apple_double(dirname)
|
147
|
+
basenames = Dir.entries(dirname)
|
148
|
+
basenames.select do |basename|
|
149
|
+
basename =~ /\A\._(.*)/ &&
|
150
|
+
!keep_apple_double_orphans ||
|
151
|
+
basenames.include?($1)
|
152
|
+
end.each do |basename|
|
153
|
+
delete(dirname, basename)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def delete_custom_folder_icon(dirname)
|
158
|
+
delete(dirname, ICON)
|
159
|
+
end
|
160
|
+
end
|
data/test/test_unmac.rb
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
require 'unmacer'
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
UNMAC_RB = File.join(File.dirname(__FILE__), '..', 'bin', 'unmac')
|
8
|
+
UNMAC_SRC = File.open(UNMAC_RB) { |f| f.read }
|
9
|
+
|
10
|
+
class TestUnmac < Test::Unit::TestCase
|
11
|
+
def call_unmac(*args)
|
12
|
+
ARGV.clear
|
13
|
+
ARGV.concat(['--test', *args])
|
14
|
+
eval UNMAC_SRC
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_no_options
|
18
|
+
unmacer = call_unmac('dummy')
|
19
|
+
assert !unmacer.verbose
|
20
|
+
assert !unmacer.pretend
|
21
|
+
assert !unmacer.keep_spotlight
|
22
|
+
assert !unmacer.keep_fsevents
|
23
|
+
assert !unmacer.keep_trashes
|
24
|
+
assert !unmacer.keep_macosx
|
25
|
+
assert !unmacer.keep_dsstore
|
26
|
+
assert !unmacer.keep_apple_double
|
27
|
+
assert !unmacer.keep_apple_double_orphans
|
28
|
+
assert !unmacer.keep_custom_folder_icons
|
29
|
+
assert_equal %w(dummy), ARGV
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_verbose
|
33
|
+
for opt in ['--verbose', '-v']
|
34
|
+
unmacer = call_unmac(opt, 'dummy')
|
35
|
+
assert unmacer.verbose
|
36
|
+
assert !unmacer.pretend
|
37
|
+
assert !unmacer.keep_spotlight
|
38
|
+
assert !unmacer.keep_fsevents
|
39
|
+
assert !unmacer.keep_trashes
|
40
|
+
assert !unmacer.keep_macosx
|
41
|
+
assert !unmacer.keep_dsstore
|
42
|
+
assert !unmacer.keep_apple_double
|
43
|
+
assert !unmacer.keep_apple_double_orphans
|
44
|
+
assert !unmacer.keep_custom_folder_icons
|
45
|
+
assert_equal %w(dummy), ARGV
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_pretend
|
50
|
+
for opt in ['--pretend', '-p']
|
51
|
+
unmacer = call_unmac(opt, 'dummy')
|
52
|
+
assert !unmacer.verbose
|
53
|
+
assert unmacer.pretend
|
54
|
+
assert !unmacer.keep_spotlight
|
55
|
+
assert !unmacer.keep_fsevents
|
56
|
+
assert !unmacer.keep_trashes
|
57
|
+
assert !unmacer.keep_macosx
|
58
|
+
assert !unmacer.keep_dsstore
|
59
|
+
assert !unmacer.keep_apple_double
|
60
|
+
assert !unmacer.keep_apple_double_orphans
|
61
|
+
assert !unmacer.keep_custom_folder_icons
|
62
|
+
assert_equal %w(dummy), ARGV
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_spotlight
|
67
|
+
for opt in ['--keep-spotlight', '-s']
|
68
|
+
unmacer = call_unmac(opt, 'dummy')
|
69
|
+
assert !unmacer.verbose
|
70
|
+
assert !unmacer.pretend
|
71
|
+
assert unmacer.keep_spotlight
|
72
|
+
assert !unmacer.keep_fsevents
|
73
|
+
assert !unmacer.keep_trashes
|
74
|
+
assert !unmacer.keep_macosx
|
75
|
+
assert !unmacer.keep_dsstore
|
76
|
+
assert !unmacer.keep_apple_double
|
77
|
+
assert !unmacer.keep_apple_double_orphans
|
78
|
+
assert !unmacer.keep_custom_folder_icons
|
79
|
+
assert_equal %w(dummy), ARGV
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_fsevents
|
84
|
+
for opt in ['--keep-fsevents', '-f']
|
85
|
+
unmacer = call_unmac(opt, 'dummy')
|
86
|
+
assert !unmacer.verbose
|
87
|
+
assert !unmacer.pretend
|
88
|
+
assert !unmacer.keep_spotlight
|
89
|
+
assert unmacer.keep_fsevents
|
90
|
+
assert !unmacer.keep_trashes
|
91
|
+
assert !unmacer.keep_macosx
|
92
|
+
assert !unmacer.keep_dsstore
|
93
|
+
assert !unmacer.keep_apple_double
|
94
|
+
assert !unmacer.keep_apple_double_orphans
|
95
|
+
assert !unmacer.keep_custom_folder_icons
|
96
|
+
assert_equal %w(dummy), ARGV
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_trashes
|
101
|
+
for opt in ['--keep-trashes', '-t']
|
102
|
+
unmacer = call_unmac(opt, 'dummy')
|
103
|
+
assert !unmacer.verbose
|
104
|
+
assert !unmacer.pretend
|
105
|
+
assert !unmacer.keep_spotlight
|
106
|
+
assert !unmacer.keep_fsevents
|
107
|
+
assert unmacer.keep_trashes
|
108
|
+
assert !unmacer.keep_macosx
|
109
|
+
assert !unmacer.keep_dsstore
|
110
|
+
assert !unmacer.keep_apple_double
|
111
|
+
assert !unmacer.keep_apple_double_orphans
|
112
|
+
assert !unmacer.keep_custom_folder_icons
|
113
|
+
assert_equal %w(dummy), ARGV
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_macosx
|
118
|
+
for opt in ['--keep-macosx', '-m']
|
119
|
+
unmacer = call_unmac(opt, 'dummy')
|
120
|
+
assert !unmacer.verbose
|
121
|
+
assert !unmacer.pretend
|
122
|
+
assert !unmacer.keep_spotlight
|
123
|
+
assert !unmacer.keep_fsevents
|
124
|
+
assert !unmacer.keep_trashes
|
125
|
+
assert unmacer.keep_macosx
|
126
|
+
assert !unmacer.keep_dsstore
|
127
|
+
assert !unmacer.keep_apple_double
|
128
|
+
assert !unmacer.keep_apple_double_orphans
|
129
|
+
assert !unmacer.keep_custom_folder_icons
|
130
|
+
assert_equal %w(dummy), ARGV
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_dsstore
|
135
|
+
for opt in ['--keep-dsstore', '-r']
|
136
|
+
unmacer = call_unmac(opt, 'dummy')
|
137
|
+
assert !unmacer.verbose
|
138
|
+
assert !unmacer.pretend
|
139
|
+
assert !unmacer.keep_spotlight
|
140
|
+
assert !unmacer.keep_fsevents
|
141
|
+
assert !unmacer.keep_trashes
|
142
|
+
assert !unmacer.keep_macosx
|
143
|
+
assert unmacer.keep_dsstore
|
144
|
+
assert !unmacer.keep_apple_double
|
145
|
+
assert !unmacer.keep_apple_double_orphans
|
146
|
+
assert !unmacer.keep_custom_folder_icons
|
147
|
+
assert_equal %w(dummy), ARGV
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_apple_double
|
152
|
+
for opt in ['--keep-apple-double', '-d']
|
153
|
+
unmacer = call_unmac(opt, 'dummy')
|
154
|
+
assert !unmacer.verbose
|
155
|
+
assert !unmacer.pretend
|
156
|
+
assert !unmacer.keep_spotlight
|
157
|
+
assert !unmacer.keep_fsevents
|
158
|
+
assert !unmacer.keep_trashes
|
159
|
+
assert !unmacer.keep_macosx
|
160
|
+
assert !unmacer.keep_dsstore
|
161
|
+
assert unmacer.keep_apple_double
|
162
|
+
assert !unmacer.keep_apple_double_orphans
|
163
|
+
assert !unmacer.keep_custom_folder_icons
|
164
|
+
assert_equal %w(dummy), ARGV
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_apple_double_orphans
|
169
|
+
for opt in ['--keep-apple-double-orphans', '-o']
|
170
|
+
unmacer = call_unmac(opt, 'dummy')
|
171
|
+
assert !unmacer.verbose
|
172
|
+
assert !unmacer.pretend
|
173
|
+
assert !unmacer.keep_spotlight
|
174
|
+
assert !unmacer.keep_fsevents
|
175
|
+
assert !unmacer.keep_trashes
|
176
|
+
assert !unmacer.keep_macosx
|
177
|
+
assert !unmacer.keep_dsstore
|
178
|
+
assert !unmacer.keep_apple_double
|
179
|
+
assert unmacer.keep_apple_double_orphans
|
180
|
+
assert !unmacer.keep_custom_folder_icons
|
181
|
+
assert_equal %w(dummy), ARGV
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_custom_folder_icons
|
186
|
+
for opt in ['--keep-custom-folder-icons', '-i']
|
187
|
+
unmacer = call_unmac(opt, 'dummy')
|
188
|
+
assert !unmacer.verbose
|
189
|
+
assert !unmacer.pretend
|
190
|
+
assert !unmacer.keep_spotlight
|
191
|
+
assert !unmacer.keep_fsevents
|
192
|
+
assert !unmacer.keep_trashes
|
193
|
+
assert !unmacer.keep_macosx
|
194
|
+
assert !unmacer.keep_dsstore
|
195
|
+
assert !unmacer.keep_apple_double
|
196
|
+
assert !unmacer.keep_apple_double_orphans
|
197
|
+
assert unmacer.keep_custom_folder_icons
|
198
|
+
assert_equal %w(dummy), ARGV
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_mix_1
|
203
|
+
unmacer = call_unmac(*%w(-v -f -m -d -i dummy))
|
204
|
+
assert unmacer.verbose
|
205
|
+
assert !unmacer.pretend
|
206
|
+
assert !unmacer.keep_spotlight
|
207
|
+
assert unmacer.keep_fsevents
|
208
|
+
assert !unmacer.keep_trashes
|
209
|
+
assert unmacer.keep_macosx
|
210
|
+
assert !unmacer.keep_dsstore
|
211
|
+
assert unmacer.keep_apple_double
|
212
|
+
assert !unmacer.keep_apple_double_orphans
|
213
|
+
assert unmacer.keep_custom_folder_icons
|
214
|
+
assert_equal %w(dummy), ARGV
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_mix_2
|
218
|
+
unmacer = call_unmac(*%w(-s -t -r -o dummy))
|
219
|
+
assert !unmacer.verbose
|
220
|
+
assert !unmacer.pretend
|
221
|
+
assert unmacer.keep_spotlight
|
222
|
+
assert !unmacer.keep_fsevents
|
223
|
+
assert unmacer.keep_trashes
|
224
|
+
assert !unmacer.keep_macosx
|
225
|
+
assert unmacer.keep_dsstore
|
226
|
+
assert !unmacer.keep_apple_double
|
227
|
+
assert unmacer.keep_apple_double_orphans
|
228
|
+
assert !unmacer.keep_custom_folder_icons
|
229
|
+
assert_equal %w(dummy), ARGV
|
230
|
+
end
|
231
|
+
|
232
|
+
def test_help
|
233
|
+
buf = ''
|
234
|
+
$> = StringIO.open(buf, 'w')
|
235
|
+
call_unmac('-h')
|
236
|
+
$> = $stdout
|
237
|
+
assert_match /^unmac\b/, buf
|
238
|
+
assert_match /^Usage\b/, buf
|
239
|
+
assert ARGV.empty?
|
240
|
+
end
|
241
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'set'
|
6
|
+
require 'unmacer'
|
7
|
+
require 'test/unit'
|
8
|
+
|
9
|
+
TEST_DIR = File.expand_path(File.dirname(__FILE__))
|
10
|
+
TMP_DIR = File.join(TEST_DIR, 'tmp')
|
11
|
+
|
12
|
+
class TestUnmacer < Test::Unit::TestCase
|
13
|
+
def in_test_dir
|
14
|
+
Dir.chdir(TEST_DIR) { yield }
|
15
|
+
end
|
16
|
+
|
17
|
+
def in_tmp_dir
|
18
|
+
Dir.chdir(TMP_DIR) { yield }
|
19
|
+
end
|
20
|
+
|
21
|
+
def setup
|
22
|
+
in_test_dir { Dir.mkdir('tmp') }
|
23
|
+
@unmacer = Unmacer.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def teardown
|
27
|
+
in_test_dir { FileUtils.rm_r('tmp') }
|
28
|
+
end
|
29
|
+
|
30
|
+
def unmac!
|
31
|
+
@unmacer.unmac!(TMP_DIR)
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_struct(dirnames=[], fnames=[])
|
35
|
+
in_tmp_dir do
|
36
|
+
FileUtils.mkdir_p([*dirnames])
|
37
|
+
FileUtils.touch([*fnames])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def read_struct
|
42
|
+
entries = []
|
43
|
+
in_tmp_dir do
|
44
|
+
@unmacer.find_skipping_root('.') do |f|
|
45
|
+
# File.find returns "./foo.txt", remove the leading stuff.
|
46
|
+
entries << f.sub(%r{\A\.[/\\]}, '')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
entries
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_an_empty_directory_should_remain_empty
|
53
|
+
create_struct
|
54
|
+
unmac!
|
55
|
+
assert read_struct.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_pretend
|
59
|
+
dirs = [Unmacer::SPOTLIGHT, Unmacer::FSEVENTS, Unmacer::TRASHES, Unmacer::MACOSX]
|
60
|
+
create_struct(dirs, '._dummy')
|
61
|
+
buf = ''
|
62
|
+
$> = StringIO.new(buf, 'w')
|
63
|
+
@unmacer.pretend = true
|
64
|
+
unmac!
|
65
|
+
$> = $stdout
|
66
|
+
assert Set.new(dirs + ['._dummy']), Set.new(read_struct)
|
67
|
+
assert_match /^would delete.*\.Spotlight/, buf
|
68
|
+
assert_match /^would delete.*\.fsevents/, buf
|
69
|
+
assert_match /^would delete.*\.Trashes/, buf
|
70
|
+
assert_match /^would delete.*__MACOSX/, buf
|
71
|
+
assert_match /^would delete.*\._dummy/, buf
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_spotlight
|
75
|
+
create_struct(Unmacer::SPOTLIGHT)
|
76
|
+
unmac!
|
77
|
+
assert read_struct.empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_spotlight_ignored_if_not_in_root
|
81
|
+
fake = File.join('dummy', Unmacer::SPOTLIGHT)
|
82
|
+
create_struct(fake)
|
83
|
+
unmac!
|
84
|
+
assert read_struct.include?(fake)
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_keep_spotlight
|
88
|
+
create_struct(Unmacer::SPOTLIGHT)
|
89
|
+
@unmacer.keep_spotlight = true
|
90
|
+
unmac!
|
91
|
+
assert [Unmacer::SPOTLIGHT], read_struct
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_fsevents
|
95
|
+
create_struct(Unmacer::FSEVENTS)
|
96
|
+
unmac!
|
97
|
+
assert read_struct.empty?
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_fsevents_ignored_if_not_in_root
|
101
|
+
fake = File.join('dummy', Unmacer::FSEVENTS)
|
102
|
+
create_struct(fake)
|
103
|
+
unmac!
|
104
|
+
assert read_struct.include?(fake)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_keep_fsevents
|
108
|
+
create_struct(Unmacer::FSEVENTS)
|
109
|
+
@unmacer.keep_fsevents = true
|
110
|
+
unmac!
|
111
|
+
assert_equal [Unmacer::FSEVENTS], read_struct
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_trashes
|
115
|
+
create_struct(Unmacer::TRASHES)
|
116
|
+
unmac!
|
117
|
+
assert read_struct.empty?
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_trashes_ignored_if_not_in_root
|
121
|
+
fake = File.join('dummy', Unmacer::TRASHES)
|
122
|
+
create_struct(fake)
|
123
|
+
unmac!
|
124
|
+
assert read_struct.include?(fake)
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_keep_trashes
|
128
|
+
create_struct(Unmacer::TRASHES)
|
129
|
+
@unmacer.keep_trashes = true
|
130
|
+
unmac!
|
131
|
+
assert_equal [Unmacer::TRASHES], read_struct
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_macosx
|
135
|
+
create_struct(Unmacer::MACOSX)
|
136
|
+
unmac!
|
137
|
+
assert read_struct.empty?
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_keep_macosx
|
141
|
+
create_struct(Unmacer::MACOSX)
|
142
|
+
@unmacer.keep_macosx = true
|
143
|
+
unmac!
|
144
|
+
assert_equal [Unmacer::MACOSX], read_struct
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_dsstore
|
148
|
+
create_struct([], Unmacer::DSSTORE)
|
149
|
+
unmac!
|
150
|
+
assert read_struct.empty?
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_keep_dsstore
|
154
|
+
create_struct([], Unmacer::DSSTORE)
|
155
|
+
@unmacer.keep_dsstore = true
|
156
|
+
unmac!
|
157
|
+
assert_equal [Unmacer::DSSTORE], read_struct
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_apple_double_with_pair
|
161
|
+
create_struct([], %w(foo.txt ._foo.txt))
|
162
|
+
unmac!
|
163
|
+
assert_equal %w(foo.txt), read_struct
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_keep_apple_double_with_pair
|
167
|
+
create_struct([], %w(foo.txt ._foo.txt))
|
168
|
+
@unmacer.keep_apple_double = true
|
169
|
+
unmac!
|
170
|
+
assert_equal Set.new(%w(foo.txt ._foo.txt)), Set.new(read_struct)
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_apple_double_with_orphan
|
174
|
+
create_struct([], '._foo.txt')
|
175
|
+
unmac!
|
176
|
+
assert read_struct.empty?
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_keep_apple_double_with_orphan
|
180
|
+
create_struct([], '._foo.txt')
|
181
|
+
@unmacer.keep_apple_double = true
|
182
|
+
unmac!
|
183
|
+
assert ['._foo.txt'], read_struct
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_keep_apple_double_orphans
|
187
|
+
create_struct([], '._foo.txt')
|
188
|
+
@unmacer.keep_apple_double_orphans = true
|
189
|
+
unmac!
|
190
|
+
assert_equal %w(._foo.txt), read_struct
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_custom_folder_icons
|
194
|
+
create_struct([], Unmacer::ICON)
|
195
|
+
unmac!
|
196
|
+
assert read_struct.empty?
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_keep_custom_folder_icons
|
200
|
+
create_struct([], Unmacer::ICON)
|
201
|
+
@unmacer.keep_custom_folder_icons = true
|
202
|
+
unmac!
|
203
|
+
assert_equal [Unmacer::ICON], read_struct
|
204
|
+
end
|
205
|
+
|
206
|
+
def test_a_handful_of_files
|
207
|
+
dummy = 'dummy'
|
208
|
+
dummy2 = File.join(dummy, 'dummy2')
|
209
|
+
ghost = File.join(dummy, '._ghost')
|
210
|
+
remain = File.join(dummy2, 'should_remain')
|
211
|
+
create_struct([
|
212
|
+
Unmacer::SPOTLIGHT,
|
213
|
+
Unmacer::FSEVENTS,
|
214
|
+
Unmacer::TRASHES,
|
215
|
+
Unmacer::MACOSX,
|
216
|
+
dummy,
|
217
|
+
dummy2
|
218
|
+
], [
|
219
|
+
Unmacer::DSSTORE,
|
220
|
+
File.join(dummy, Unmacer::DSSTORE),
|
221
|
+
File.join(dummy2, Unmacer::DSSTORE),
|
222
|
+
File.join(dummy, Unmacer::MACOSX),
|
223
|
+
File.join(dummy2, Unmacer::MACOSX),
|
224
|
+
File.join(dummy, Unmacer::ICON),
|
225
|
+
File.join(dummy2, Unmacer::ICON),
|
226
|
+
'foo.txt',
|
227
|
+
'._foo.txt',
|
228
|
+
ghost,
|
229
|
+
remain
|
230
|
+
])
|
231
|
+
unmac!
|
232
|
+
assert_equal Set.new(['foo.txt', dummy, dummy2, remain]), Set.new(read_struct)
|
233
|
+
end
|
234
|
+
end
|
data/unmac.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = 'unmac'
|
3
|
+
spec.version = '0.6'
|
4
|
+
spec.summary = 'Delete spurious Mac files under a directory or volume'
|
5
|
+
spec.homepage = 'http://github.com/fxn/unmac/tree/master'
|
6
|
+
spec.executables = %w(unmac)
|
7
|
+
spec.author = 'Xavier Noria'
|
8
|
+
spec.email = 'fxn@hashref.com'
|
9
|
+
spec.rubyforge_project = 'unmac'
|
10
|
+
|
11
|
+
spec.test_files = %w(
|
12
|
+
test/test_unmacer.rb
|
13
|
+
test/test_unmac.rb
|
14
|
+
)
|
15
|
+
|
16
|
+
spec.files = %w(
|
17
|
+
unmac.gemspec
|
18
|
+
Rakefile
|
19
|
+
README
|
20
|
+
MIT-LICENSE
|
21
|
+
bin/unmac
|
22
|
+
lib/unmacer.rb
|
23
|
+
) + spec.test_files
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: unmac
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.6"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Xavier Noria
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-08 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: fxn@hashref.com
|
18
|
+
executables:
|
19
|
+
- unmac
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- unmac.gemspec
|
26
|
+
- Rakefile
|
27
|
+
- README
|
28
|
+
- MIT-LICENSE
|
29
|
+
- bin/unmac
|
30
|
+
- lib/unmacer.rb
|
31
|
+
- test/test_unmacer.rb
|
32
|
+
- test/test_unmac.rb
|
33
|
+
has_rdoc: false
|
34
|
+
homepage: http://github.com/fxn/unmac/tree/master
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "0"
|
45
|
+
version:
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
requirements: []
|
53
|
+
|
54
|
+
rubyforge_project: unmac
|
55
|
+
rubygems_version: 1.3.1
|
56
|
+
signing_key:
|
57
|
+
specification_version: 2
|
58
|
+
summary: Delete spurious Mac files under a directory or volume
|
59
|
+
test_files:
|
60
|
+
- test/test_unmacer.rb
|
61
|
+
- test/test_unmac.rb
|