Class: Gauntlet

Inherits:
Object
  • Object
show all
Defined in:
lib/gauntlet.rb

Direct Known Subclasses

GrepGauntlet

Constant Summary collapse

VERSION =
'2.1.0'
GEMURL =
URI.parse 'http://gems.rubyforge.org'
GEMDIR =
File.expand_path "~/.gauntlet"
DATADIR =
File.expand_path "~/.gauntlet/data"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeGauntlet

Returns a new instance of Gauntlet.



20
21
22
23
24
25
# File 'lib/gauntlet.rb', line 20

def initialize
  name = self.class.name.downcase.sub(/gauntlet/, '')
  self.data_file = "#{DATADIR}/#{name}-data.yml"
  self.dirty = false
  @cache = nil
end

Instance Attribute Details

#dataObject

Returns the value of attribute data.



18
19
20
# File 'lib/gauntlet.rb', line 18

def data
  @data
end

#data_fileObject

Returns the value of attribute data_file.



18
19
20
# File 'lib/gauntlet.rb', line 18

def data_file
  @data_file
end

#dirtyObject

Returns the value of attribute dirty.



18
19
20
# File 'lib/gauntlet.rb', line 18

def dirty
  @dirty
end

Instance Method Details

#each_gem(filter = nil) ⇒ Object



183
184
185
186
187
188
189
190
191
192
# File 'lib/gauntlet.rb', line 183

def each_gem filter = nil
  filter ||= /^[\w-]+-\d+(\.\d+)*\.tgz$/
  in_gem_dir do
    Dir["*.tgz"].each do |tgz|
      next unless tgz =~ filter

      yield File.basename(tgz, ".tgz")
    end
  end
end

#in_gem_dirObject



223
224
225
226
227
# File 'lib/gauntlet.rb', line 223

def in_gem_dir
  Dir.chdir GEMDIR do
    yield
  end
end

#initialize_dirObject



27
28
29
30
31
32
33
# File 'lib/gauntlet.rb', line 27

def initialize_dir
  Dir.mkdir GEMDIR unless File.directory? GEMDIR
  Dir.mkdir DATADIR unless File.directory? DATADIR
  in_gem_dir do
    File.symlink ".", "cache" unless File.exist? "cache"
  end
end

#latest_gemsObject



54
55
56
57
58
59
60
61
62
# File 'lib/gauntlet.rb', line 54

def latest_gems
  list, _ = Gem::SpecFetcher.new.available_specs(:latest)
  list.map { |source, gems|
    gems.map { |tuple|
      gem_name = File.basename(tuple.spec_name, '.gemspec')
      [gem_name, source.uri.merge("/gems/#{gem_name}.gem")]
    }
  }.flatten(1)
end

#load_yaml(path, default = {}) ⇒ Object



211
212
213
# File 'lib/gauntlet.rb', line 211

def load_yaml path, default = {}
  YAML.load(File.read(path)) rescue default
end

#run(name) ⇒ Object

Override this to customize gauntlet. See run_the_gauntlet for other ways of adding behavior.



233
234
235
# File 'lib/gauntlet.rb', line 233

def run name
  raise "subclass responsibility"
end

#run_the_gauntlet(filter = nil) ⇒ Object

This is the main driver for gauntlet. filter allows you to pass in a regexp to only run against a subset of the gems available. You can pass a block to run_the_gauntlet or it will call run. Both are passed the name of the gem and are executed within the gem directory.



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/gauntlet.rb', line 251

def run_the_gauntlet filter = nil
  initialize_dir
  update_gem_tarballs if $u

  self.data ||= load_yaml data_file

  outdateds = self.data.keys - in_gem_dir do
    Dir["*.tgz"].map { |tgz| File.basename(tgz, ".tgz") }
  end

  outdateds.each do |outdated|
    self.data.delete outdated
  end

  %w[TERM KILL].each do |signal|
    trap signal do
      shutdown
      exit
    end
  end

  each_gem filter do |name|
    next if should_skip? name
    with_gem name do
      if block_given? then
        yield name
      else
        run name
      end
    end
  end
rescue Interrupt
  warn "user cancelled. quitting"
ensure
  shutdown
end

#save_yaml(path, data) ⇒ Object



215
216
217
218
219
220
221
# File 'lib/gauntlet.rb', line 215

def save_yaml path, data
  File.open("#{path}.new", 'w') do |f|
    warn "*** saving #{path}"
    YAML.dump data, f
  end
  File.rename "#{path}.new", path
end

#should_skip?(name) ⇒ Boolean

Override this to return true if the gem should be skipped.

Returns:

  • (Boolean)


240
241
242
# File 'lib/gauntlet.rb', line 240

def should_skip? name
  self.data[name]
end

#shutdownObject



288
289
290
# File 'lib/gauntlet.rb', line 288

def shutdown
  save_yaml data_file, data if dirty
end

#source_indexObject



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/gauntlet.rb', line 35

def source_index
  @index ||= in_gem_dir do
    dump = if ($u and not $F) or not File.exist? '.source_index' then
             warn "fetching and caching gem index"
             url = GEMURL + "Marshal.#{Gem.marshal_version}.Z"
             dump = Gem::RemoteFetcher.fetcher.fetch_path url
             require 'zlib' # HACK for rubygems :(
             dump = Gem.inflate dump
             open '.source_index', 'wb' do |io| io.write dump end
             dump
           else
             warn "using cached gem index"
             open '.source_index', 'rb' do |io| io.read end
           end

    Marshal.load dump
  end
end

#update_gem_tarballsObject



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
# File 'lib/gauntlet.rb', line 64

def update_gem_tarballs
  initialize_dir

  latest = self.latest_gems

  warn "updating mirror"

  in_gem_dir do
    gems = Dir["*.gem"]
    tgzs = Dir["*.tgz"]

    old = tgzs - latest.map { |(full_name, _url)| "#{full_name}.tgz" }
    unless old.empty? then
      warn "deleting #{old.size} tgzs"
      old.each do |tgz|
        File.unlink tgz
      end
    end

    conversions = Queue.new
    gem_names = gems.map { |gem| File.basename gem, '.gem' }
    tgz_names = tgzs.map { |tgz| File.basename tgz, '.tgz' }
    to_convert = gem_names - tgz_names

    seen_tgzs = Hash[tgzs.map { |name| [name, true] }]

    warn "adding #{to_convert.size} unconverted gems" unless to_convert.empty?

    conversions.push to_convert.shift until to_convert.empty? # LAME

    downloads = Queue.new
    latest = latest.sort_by { |(full_name, _url)| full_name.downcase }
    latest.reject! { |(full_name, _url)| seen_tgzs["#{full_name}.tgz"] }

    downloads.push(latest.shift) until latest.empty? # LAME

    converter = Thread.start do
      while payload = conversions.shift do
        full_name, _ = payload
        tgz_name  = "#{full_name}.tgz"
        gem_name  = "#{full_name}.gem"

        warn " converting #{gem_name} to tarball"

        unless File.directory? full_name then
          system "gem unpack cache/#{gem_name} > /dev/null 2>&1"
          system "gem spec -l cache/#{gem_name} > #{full_name}/gemspec"
        end

        system ["chmod -R u+rwX \"#{full_name}\"",
                "tar zmcf #{tgz_name} -- #{full_name}",
                "rm -rf -- #{full_name} #{gem_name}"].join(" && ")
      end
    end

    warn "fetching #{downloads.size} gems"

    workers downloads do |http, (full_name, url)|
      gem_name  = "#{full_name}.gem"

      unless gems.include? gem_name then
        limit = 3
        begin
          warn "downloading #{full_name}"
          while limit > 0 do
            http.request url do |response|
              case response
              when Net::HTTPSuccess
                File.open gem_name, "wb" do |f|
                  response.read_body do |chunk|
                    f.write chunk
                  end
                end
                limit = 0 # kinda lame.
              when Net::HTTPRedirection
                url = URI.parse(response['location'])
                limit -= 1
              else
                warn "  #{full_name} got #{response.code}. skipping."
                limit = 0
              end
            end
          end
        rescue SocketError, OpenSSL::SSL::SSLError, Net::HTTP::Persistent::Error => e
          warn "  #{full_name} raised #{e.message}. skipping."
        end
      end

      conversions.push full_name
    end

    conversions.push nil

    converter.join
  end

rescue Interrupt
  warn "user cancelled... quitting"
  exit 1
end

#with_gem(name) ⇒ Object



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/gauntlet.rb', line 194

def with_gem name
  in_gem_dir do
    process_dir = "#{$$}"
    begin
      Dir.mkdir process_dir
      Dir.chdir process_dir do
        system "tar zxmf ../#{name}.tgz 2> /dev/null"
        Dir.chdir name do
          yield name
        end
      end
    ensure
      system "rm -rf -- #{process_dir}"
    end
  end
end

#workers(tasks, count = 10) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/gauntlet.rb', line 165

def workers tasks, count = 10
  threads = []
  count.times do
    threads << Thread.new do
      http = Net::HTTP::Persistent.new

      until tasks.empty? do
        task = tasks.shift
        yield http, task
      end
    end
  end

  threads.each do |thread|
    thread.join
  end
end