unmac 0.6
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.
- 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
|