geminabox-inflection 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,91 @@
1
+ # Gem in a Box – Really simple rubygem hosting
2
+ [![Build Status](https://secure.travis-ci.org/geminabox/geminabox.png)](http://travis-ci.org/geminabox/geminabox)
3
+ [![Gem Version](https://badge.fury.io/rb/geminabox.png)](http://badge.fury.io/rb/geminabox)
4
+
5
+ Geminabox lets you host your own gems, and push new gems to it just like with rubygems.org.
6
+ The bundler dependencies API is supported out of the box.
7
+ Authentication is left up to either the web server, or the Rack stack.
8
+ For basic auth, try [Rack::Auth](http://rack.rubyforge.org/doc/Rack/Auth/Basic.html).
9
+
10
+ ![screen shot](http://pics.tomlea.co.uk/bbbba6/geminabox.png)
11
+
12
+ ## System Requirements
13
+
14
+ Tested on Mac OS X 10.8.2
15
+ Ruby 1.9.3-392
16
+
17
+ Tests fail on Ruby 2.0.0-p0
18
+
19
+ ## Server Setup
20
+
21
+ gem install geminabox
22
+
23
+ Create a config.ru as follows:
24
+
25
+ require "rubygems"
26
+ require "geminabox"
27
+
28
+ Geminabox.data = "/var/geminabox-data" # ... or wherever
29
+ run Geminabox
30
+
31
+ Start your gem server with 'rackup' to run WEBrick or hook up the config.ru as you normally would ([passenger][passenger], [thin][thin], [unicorn][unicorn], whatever floats your boat).
32
+
33
+ ## Legacy RubyGems index
34
+
35
+ RubyGems supports generating indexes for the so called legacy versions (< 1.2), and since it is very rare to use such versions nowadays, it can be disabled, thus improving indexing times for large repositories. If it's safe for your application, you can disable support for these legacy versions by adding the following configuration to your config.ru file:
36
+
37
+ Geminabox.build_legacy = false
38
+
39
+ ## Client Usage
40
+
41
+ gem install geminabox
42
+
43
+ gem inabox pkg/my-awesome-gem-1.0.gem
44
+
45
+ Configure Gem in a box (interactive prompt to specify where to upload to):
46
+
47
+ gem inabox -c
48
+
49
+ Change the host to upload to:
50
+
51
+ gem inabox -g HOST
52
+
53
+ Simples!
54
+
55
+ ## Command Line Help
56
+
57
+ Usage: gem inabox GEM [options]
58
+
59
+ Options:
60
+ -c, --configure Configure GemInABox
61
+ -g, --host HOST Host to upload to.
62
+ -o, --overwrite Overwrite Gem.
63
+
64
+
65
+ Common Options:
66
+ -h, --help Get help on this command
67
+ -V, --[no-]verbose Set the verbose level of output
68
+ -q, --quiet Silence commands
69
+ --config-file FILE Use this config file instead of default
70
+ --backtrace Show stack backtrace on errors
71
+ --debug Turn on Ruby debugging
72
+
73
+
74
+ Arguments:
75
+ GEM built gem to push up
76
+
77
+ Summary:
78
+ Push a gem up to your GemInABox
79
+
80
+ Description:
81
+ Push a gem up to your GemInABox
82
+
83
+ ## Licence
84
+
85
+ Fork it, mod it, choose it, use it, make it better. All under the [do what the fuck you want to + beer/pizza public license][WTFBPPL].
86
+
87
+ [WTFBPPL]: http://tomlea.co.uk/WTFBPPL.txt
88
+ [sinatra]: http://www.sinatrarb.com/
89
+ [passenger]: http://www.modrails.com/
90
+ [thin]: http://code.macournoyer.com/thin/
91
+ [unicorn]: http://unicorn.bogomips.org/
@@ -0,0 +1,275 @@
1
+ require 'rubygems'
2
+ require 'digest/md5'
3
+ require 'builder'
4
+ require 'sinatra/base'
5
+ require 'rubygems/user_interaction'
6
+ require 'rubygems/indexer'
7
+ require 'rubygems/package'
8
+ require 'hostess'
9
+ require 'geminabox/version'
10
+ require 'rss/atom'
11
+ require 'tempfile'
12
+
13
+ class Geminabox < Sinatra::Base
14
+ enable :static, :methodoverride
15
+
16
+ set :public_folder, File.join(File.dirname(__FILE__), *%w[.. public])
17
+ set :data, File.join(File.dirname(__FILE__), *%w[.. data])
18
+ set :build_legacy, false
19
+ set :incremental_updates, true
20
+ set :views, File.join(File.dirname(__FILE__), *%w[.. views])
21
+ set :allow_replace, false
22
+ set :gem_permissions, 0644
23
+ set :allow_delete, true
24
+ use Hostess
25
+
26
+ class << self
27
+ def disallow_replace?
28
+ ! allow_replace
29
+ end
30
+
31
+ def allow_delete?
32
+ allow_delete
33
+ end
34
+
35
+ def fixup_bundler_rubygems!
36
+ return if @post_reset_hook_applied
37
+ Gem.post_reset{ Gem::Specification.all = nil } if defined? Bundler and Gem.respond_to? :post_reset
38
+ @post_reset_hook_applied = true
39
+ end
40
+ end
41
+
42
+ autoload :GemVersionCollection, "geminabox/gem_version_collection"
43
+ autoload :GemVersion, "geminabox/gem_version"
44
+ autoload :DiskCache, "geminabox/disk_cache"
45
+ autoload :IncomingGem, "geminabox/incoming_gem"
46
+
47
+ before do
48
+ headers 'X-Powered-By' => "geminabox #{GeminaboxVersion}"
49
+ end
50
+
51
+ get '/' do
52
+ @gems = load_gems
53
+ @index_gems = index_gems(@gems)
54
+ erb :index
55
+ end
56
+
57
+ get '/atom.xml' do
58
+ @gems = load_gems
59
+ erb :atom, :layout => false
60
+ end
61
+
62
+ get '/api/v1/dependencies' do
63
+ query_gems = params[:gems].to_s.split(',')
64
+ deps = query_gems.inject([]){|memo, query_gem| memo + gem_dependencies(query_gem) }
65
+ Marshal.dump(deps)
66
+ end
67
+
68
+ get '/upload' do
69
+ erb :upload
70
+ end
71
+
72
+ get '/reindex' do
73
+ reindex(:force_rebuild)
74
+ redirect url("/")
75
+ end
76
+
77
+ get '/gems/:gemname' do
78
+ gems = Hash[load_gems.by_name]
79
+ @gem = gems[params[:gemname]]
80
+ halt 404 unless @gem
81
+ erb :gem
82
+ end
83
+
84
+ delete '/gems/*.gem' do
85
+ unless Geminabox.allow_delete?
86
+ error_response(403, 'Gem deletion is disabled - see https://github.com/cwninja/geminabox/issues/115')
87
+ end
88
+ File.delete file_path if File.exists? file_path
89
+ reindex(:force_rebuild)
90
+ redirect url("/")
91
+ end
92
+
93
+ post '/upload' do
94
+ unless params[:file] && params[:file][:filename] && (tmpfile = params[:file][:tempfile])
95
+ @error = "No file selected"
96
+ halt [400, erb(:upload)]
97
+ end
98
+ handle_incoming_gem(IncomingGem.new(tmpfile))
99
+ end
100
+
101
+ post '/api/v1/gems' do
102
+ begin
103
+ handle_incoming_gem(IncomingGem.new(request.body))
104
+ rescue Object => o
105
+ File.open "/tmp/debug.txt", "a" do |io|
106
+ io.puts o, o.backtrace
107
+ end
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ def handle_incoming_gem(gem)
114
+ prepare_data_folders
115
+ error_response(400, "Cannot process gem") unless gem.valid?
116
+ handle_replacement(gem) unless params[:overwrite] == "true"
117
+ write_and_index(gem)
118
+
119
+ if api_request?
120
+ "Gem #{gem.name} received and indexed."
121
+ else
122
+ redirect url("/")
123
+ end
124
+ end
125
+
126
+ def api_request?
127
+ request.accept.first != "text/html"
128
+ end
129
+
130
+ def error_response(code, message)
131
+ halt [code, message] if api_request?
132
+ html = <<HTML
133
+ <html>
134
+ <head><title>Error - #{code}</title></head>
135
+ <body>
136
+ <h1>Error - #{code}</h1>
137
+ <p>#{message}</p>
138
+ </body>
139
+ </html>
140
+ HTML
141
+ halt [code, html]
142
+ end
143
+
144
+ def prepare_data_folders
145
+ if File.exists? Geminabox.data
146
+ error_response( 500, "Please ensure #{File.expand_path(Geminabox.data)} is a directory." ) unless File.directory? Geminabox.data
147
+ error_response( 500, "Please ensure #{File.expand_path(Geminabox.data)} is writable by the geminabox web server." ) unless File.writable? Geminabox.data
148
+ else
149
+ begin
150
+ FileUtils.mkdir_p(settings.data)
151
+ rescue Errno::EACCES, Errno::ENOENT, RuntimeError => e
152
+ error_response( 500, "Could not create #{File.expand_path(Geminabox.data)}.\n#{e}\n#{e.message}" )
153
+ end
154
+ end
155
+
156
+ FileUtils.mkdir_p(File.join(settings.data, "gems"))
157
+ end
158
+
159
+ def handle_replacement(gem)
160
+ if Geminabox.disallow_replace? and File.exist?(gem.dest_filename)
161
+ existing_file_digest = Digest::SHA1.file(gem.dest_filename).hexdigest
162
+
163
+ if existing_file_digest != gem.hexdigest
164
+ error_response(409, "Updating an existing gem is not permitted.\nYou should either delete the existing version, or change your version number.")
165
+ else
166
+ error_response(200, "Ignoring upload, you uploaded the same thing previously.\nPlease use -o to ovewrite.")
167
+ end
168
+ end
169
+ end
170
+
171
+ def write_and_index(gem)
172
+ tmpfile = gem.gem_data
173
+ atomic_write(gem.dest_filename) do |f|
174
+ while blk = tmpfile.read(65536)
175
+ f << blk
176
+ end
177
+ end
178
+ reindex
179
+ end
180
+
181
+ def reindex(force_rebuild = false)
182
+ Geminabox.fixup_bundler_rubygems!
183
+ force_rebuild = true unless settings.incremental_updates
184
+ if force_rebuild
185
+ indexer.generate_index
186
+ dependency_cache.flush
187
+ else
188
+ begin
189
+ require 'geminabox/indexer'
190
+ updated_gemspecs = Geminabox::Indexer.updated_gemspecs(indexer)
191
+ Geminabox::Indexer.patch_rubygems_update_index_pre_1_8_25(indexer)
192
+ indexer.update_index
193
+ updated_gemspecs.each { |gem| dependency_cache.flush_key(gem.name) }
194
+ rescue => e
195
+ puts "#{e.class}:#{e.message}"
196
+ puts e.backtrace.join("\n")
197
+ reindex(:force_rebuild)
198
+ end
199
+ end
200
+ end
201
+
202
+ def indexer
203
+ Gem::Indexer.new(settings.data, :build_legacy => settings.build_legacy)
204
+ end
205
+
206
+ def file_path
207
+ File.expand_path(File.join(settings.data, *request.path_info))
208
+ end
209
+
210
+ def dependency_cache
211
+ @dependency_cache ||= Geminabox::DiskCache.new(File.join(settings.data, "_cache"))
212
+ end
213
+
214
+ def all_gems
215
+ %w(specs prerelease_specs).map{ |specs_file_type|
216
+ specs_file_path = File.join(settings.data, "#{specs_file_type}.#{Gem.marshal_version}.gz")
217
+ if File.exists?(specs_file_path)
218
+ Marshal.load(Gem.gunzip(Gem.read_binary(specs_file_path)))
219
+ else
220
+ []
221
+ end
222
+ }.inject(:|)
223
+ end
224
+
225
+ def load_gems
226
+ @loaded_gems ||= Geminabox::GemVersionCollection.new(all_gems)
227
+ end
228
+
229
+ def index_gems(gems)
230
+ Set.new(gems.map{|gem| gem.name[0..0].downcase})
231
+ end
232
+
233
+ # based on http://as.rubyonrails.org/classes/File.html
234
+ def atomic_write(file_name)
235
+ temp_dir = File.join(settings.data, "_temp")
236
+ FileUtils.mkdir_p(temp_dir)
237
+ temp_file = Tempfile.new("." + File.basename(file_name), temp_dir)
238
+ yield temp_file
239
+ temp_file.close
240
+ File.rename(temp_file.path, file_name)
241
+ File.chmod(settings.gem_permissions, file_name)
242
+ end
243
+
244
+ helpers do
245
+ def spec_for(gem_name, version)
246
+ spec_file = File.join(settings.data, "quick", "Marshal.#{Gem.marshal_version}", "#{gem_name}-#{version}.gemspec.rz")
247
+ Marshal.load(Gem.inflate(File.read(spec_file))) if File.exists? spec_file
248
+ end
249
+
250
+ # Return a list of versions of gem 'gem_name' with the dependencies of each version.
251
+ def gem_dependencies(gem_name)
252
+ dependency_cache.marshal_cache(gem_name) do
253
+ load_gems.
254
+ select { |gem| gem_name == gem.name }.
255
+ map { |gem| [gem, spec_for(gem.name, gem.number)] }.
256
+ reject { |(_, spec)| spec.nil? }.
257
+ map do |(gem, spec)|
258
+ {
259
+ :name => gem.name,
260
+ :number => gem.number.version,
261
+ :platform => gem.platform,
262
+ :dependencies => runtime_dependencies(spec)
263
+ }
264
+ end
265
+ end
266
+ end
267
+
268
+ def runtime_dependencies(spec)
269
+ spec.
270
+ dependencies.
271
+ select { |dep| dep.type == :runtime }.
272
+ map { |dep| [dep.name, dep.requirement.to_s] }
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,72 @@
1
+ require "fileutils"
2
+
3
+ class Geminabox::DiskCache
4
+ attr_reader :root_path
5
+
6
+ def initialize(root_path)
7
+ @root_path = root_path
8
+ ensure_dir_exists!
9
+ end
10
+
11
+ def flush_key(key)
12
+ path = path(key_hash(key))
13
+ FileUtils.rm_f(path)
14
+ end
15
+
16
+ def flush
17
+ FileUtils.rm_rf(root_path)
18
+ ensure_dir_exists!
19
+ end
20
+
21
+ def cache(key)
22
+ key_hash = key_hash(key)
23
+ read(key_hash) || write(key_hash, yield)
24
+ end
25
+
26
+ def marshal_cache(key)
27
+ key_hash = key_hash(key)
28
+ marshal_read(key_hash) || marshal_write(key_hash, yield)
29
+ end
30
+
31
+ protected
32
+
33
+ def ensure_dir_exists!
34
+ FileUtils.mkdir_p(root_path)
35
+ end
36
+
37
+ def key_hash(key)
38
+ Digest::MD5.hexdigest(key)
39
+ end
40
+
41
+ def path(key_hash)
42
+ File.join(root_path, key_hash)
43
+ end
44
+
45
+ def read(key_hash)
46
+ read_int(key_hash) { |path| File.read(path) }
47
+ end
48
+
49
+ def marshal_read(key_hash)
50
+ read_int(key_hash) { |path| Marshal.load(File.open(path)) }
51
+ end
52
+
53
+ def read_int(key_hash)
54
+ path = path(key_hash)
55
+ yield(path) if File.exists?(path)
56
+ end
57
+
58
+ def write(key_hash, value)
59
+ write_int(key_hash) { |f| f << value }
60
+ value
61
+ end
62
+
63
+ def marshal_write(key_hash, value)
64
+ write_int(key_hash) { |f| Marshal.dump(value, f) }
65
+ value
66
+ end
67
+
68
+ def write_int(key_hash)
69
+ File.open(path(key_hash), 'wb') { |f| yield(f) }
70
+ end
71
+
72
+ end
@@ -0,0 +1,36 @@
1
+ class Geminabox::GemVersion
2
+ attr_accessor :name, :number, :platform
3
+
4
+ def initialize(name, number, platform)
5
+ @name = name
6
+ @number = number
7
+ @platform = platform
8
+ end
9
+
10
+ def ruby?
11
+ !!(platform =~ /ruby/i)
12
+ end
13
+
14
+ def version
15
+ Gem::Version.create(number)
16
+ end
17
+
18
+ def <=>(other)
19
+ sort = other.name <=> name
20
+ sort = version <=> other.version if sort.zero?
21
+ sort = (other.ruby? && !ruby?) ? 1 : -1 if sort.zero? && ruby? != other.ruby?
22
+ sort = other.platform <=> platform if sort.zero?
23
+
24
+ sort
25
+ end
26
+
27
+ def ==(other)
28
+ return false unless other.class == self.class
29
+ [name, number, platform] == [other.name, other.number, other.platform]
30
+ end
31
+
32
+ def gemfile_name
33
+ included_platform = ruby? ? nil : platform
34
+ [name, number, included_platform].compact.join('-')
35
+ end
36
+ end