1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
|
#!/usr/bin/ruby
require 'fileutils'
require 'rubygems'
fu = FileUtils::Verbose
until ARGV.empty?
case ARGV.first
when '--'
ARGV.shift
break
when '-n', '--dry-run', '--dryrun'
## -n, --dry-run Don't remove
fu = FileUtils::DryRun
when /\A--make=/
# just to run when `make -n`
when /\A--mflags=(.*)/
fu = FileUtils::DryRun if /\A-\S*n/ =~ $1
when /\A--gem[-_]platform=(.*)/im
## --gem-platform=PLATFORM Platform in RubyGems style
gem_platform = $1
ruby_platform = nil
when /\A--ruby[-_]platform=(.*)/im
## --ruby-platform=PLATFORM Platform in Ruby style
ruby_platform = $1
gem_platform = nil
when /\A--ruby[-_]version=(.*)/im
## --ruby-version=VERSION Ruby version to keep
ruby_version = $1
when /\A--only=(?:(curdir|srcdir)|all)\z/im
## --only=(curdir|srcdir|all) Specify directory to remove gems from
only = $1&.downcase
when /\A--all\z/im
## --all Remove all gems not only bundled gems
all = true
when /\A--help\z/im
## --help Print this message
puts "Usage: #$0 [options] [srcdir]"
File.foreach(__FILE__) do |line|
line.sub!(/^ *## /, "") or next
break if line.chomp!.empty?
opt, desc = line.split(/ {2,}/, 2)
printf " %-28s %s\n", opt, desc
end
exit
when /\A-/
raise "#{$0}: unknown option: #{ARGV.first}"
else
break
end
##
ARGV.shift
end
gem_platform ||= Gem::Platform.new(ruby_platform).to_s if ruby_platform
class Removal
attr_reader :base
def initialize(base = nil)
@base = (File.join(base, "/") if base)
@remove = {}
end
def prefixed(name)
@base ? File.join(@base, name) : name
end
def stripped(name)
if @base && name.start_with?(@base)
name[@base.size..-1]
else
name
end
end
def slash(name)
name.sub(%r[[^/]\K\z], '/')
end
def exist?(name)
!@remove.fetch(name) {|k| @remove[k] = !File.exist?(prefixed(name))}
end
def directory?(name)
!@remove.fetch(slash(name)) {|k| @remove[k] = !File.directory?(prefixed(name))}
end
def unlink(name)
@remove[stripped(name)] = :rm_f
end
def rmdir(name)
@remove[slash(stripped(name))] = :rm_rf
end
def glob(pattern, *rest)
Dir.glob(prefixed(pattern), *rest) {|n|
yield stripped(n)
}
end
def sorted
@remove.sort_by {|k, | [-k.count("/"), k]}
end
def each_file
sorted.each {|k, v| yield prefixed(k) if v == :rm_f}
end
def each_directory
sorted.each {|k, v| yield prefixed(k) if v == :rm_rf}
end
end
srcdir = Removal.new(ARGV.shift)
curdir = !srcdir.base || File.identical?(srcdir.base, ".") ? srcdir : Removal.new
bundled = File.readlines("#{srcdir.base}gems/bundled_gems").
grep(/^(\w\S+)\s+\S+(?:\s+\S+\s+(\S+))?/) {$~.captures}.to_h rescue nil
srcdir.glob(".bundle/gems/*/") do |dir|
base = File.basename(dir)
next if !all && bundled && !bundled.key?(base[/\A.+(?=-)/])
unless srcdir.exist?("gems/#{base}.gem")
srcdir.rmdir(dir)
end
end
srcdir.glob(".bundle/.timestamp/*.revision") do |file|
unless bundled&.fetch(File.basename(file, ".revision"), nil)
srcdir.unlink(file)
end
end
srcdir.glob(".bundle/specifications/*.gemspec") do |spec|
unless srcdir.directory?(".bundle/gems/#{File.basename(spec, '.gemspec')}/")
srcdir.unlink(spec)
end
end
curdir.glob(".bundle/specifications/*.gemspec") do |spec|
unless srcdir.directory?(".bundle/gems/#{File.basename(spec, '.gemspec')}")
curdir.unlink(spec)
end
end
curdir.glob(".bundle/gems/*/") do |dir|
base = File.basename(dir)
unless curdir.exist?(".bundle/specifications/#{base}.gemspec") or
curdir.exist?("#{dir}/.bundled.#{base}.gemspec")
curdir.rmdir(dir)
end
end
curdir.glob(".bundle/{extensions,.timestamp}/*/") do |dir|
unless gem_platform and File.fnmatch?(gem_platform, File.basename(dir))
curdir.rmdir(dir)
end
end
if gem_platform
curdir.glob(".bundle/{extensions,.timestamp}/#{gem_platform}/*/") do |dir|
unless ruby_version and File.fnmatch?(ruby_version, File.basename(dir, '-static'))
curdir.rmdir(dir)
end
end
end
if ruby_version
curdir.glob(".bundle/extensions/#{gem_platform || '*'}/#{ruby_version}/*/") do |dir|
unless curdir.exist?(".bundle/specifications/#{File.basename(dir)}.gemspec")
curdir.rmdir(dir)
end
end
curdir.glob(".bundle/.timestamp/#{gem_platform || '*'}/#{ruby_version}/.*.time") do |stamp|
dir = stamp[%r[/\.([^/]+)\.time\z], 1].gsub('.-.', '/')[%r[\A[^/]+/[^/]+]]
unless curdir.directory?(File.join(".bundle", dir))
curdir.unlink(stamp)
end
end
end
unless only == "curdir"
srcdir.each_file {|f| fu.rm_f(f)}
srcdir.each_directory {|d| fu.rm_rf(d)}
end
unless only == "srcdir" or curdir.equal?(srcdir)
curdir.each_file {|f| fu.rm_f(f)}
curdir.each_directory {|d| fu.rm_rf(d)}
end
|