geminabox-bootstrap 0.10.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/README.markdown +91 -0
  2. data/lib/geminabox.rb +271 -0
  3. data/lib/geminabox/disk_cache.rb +72 -0
  4. data/lib/geminabox/gem_version.rb +36 -0
  5. data/lib/geminabox/gem_version_collection.rb +63 -0
  6. data/lib/geminabox/incoming_gem.rb +65 -0
  7. data/lib/geminabox/indexer.rb +46 -0
  8. data/lib/geminabox/version.rb +1 -0
  9. data/lib/geminabox_client.rb +60 -0
  10. data/lib/hostess.rb +42 -0
  11. data/lib/rubygems/commands/inabox_command.rb +91 -0
  12. data/lib/rubygems_plugin.rb +2 -0
  13. data/public/favicon.ico +0 -0
  14. data/public/vendor/bootstrap/css/bootstrap-responsive.css +1279 -0
  15. data/public/vendor/bootstrap/css/bootstrap-responsive.min.css +18 -0
  16. data/public/vendor/bootstrap/css/bootstrap.css +7607 -0
  17. data/public/vendor/bootstrap/css/bootstrap.min.css +18 -0
  18. data/public/vendor/bootstrap/css/prettify.min.css +30 -0
  19. data/public/vendor/bootstrap/img/glyphicons-halflings-white.png +0 -0
  20. data/public/vendor/bootstrap/img/glyphicons-halflings.png +0 -0
  21. data/public/vendor/bootstrap/js/bootstrap.js +3060 -0
  22. data/public/vendor/bootstrap/js/bootstrap.min.js +7 -0
  23. data/public/vendor/bootstrap/js/prettify.min.js +28 -0
  24. data/public/vendor/fontawesome/css/font-awesome-ie7.min.css +22 -0
  25. data/public/vendor/fontawesome/css/font-awesome.css +540 -0
  26. data/public/vendor/fontawesome/css/font-awesome.min.css +33 -0
  27. data/public/vendor/fontawesome/font/FontAwesome.otf +0 -0
  28. data/public/vendor/fontawesome/font/fontawesome-webfont.eot +0 -0
  29. data/public/vendor/fontawesome/font/fontawesome-webfont.svg +284 -0
  30. data/public/vendor/fontawesome/font/fontawesome-webfont.ttf +0 -0
  31. data/public/vendor/fontawesome/font/fontawesome-webfont.woff +0 -0
  32. data/public/vendor/github-icon.png +0 -0
  33. data/public/vendor/jquery-1.9.1.min.js +5 -0
  34. data/views/adding_gems_partial.erb +0 -0
  35. data/views/atom.erb +28 -0
  36. data/views/example_gemfile.erb +5 -0
  37. data/views/gem.erb +66 -0
  38. data/views/index.erb +35 -0
  39. data/views/layout.erb +47 -0
  40. data/views/upload.erb +36 -0
  41. metadata +237 -0
@@ -0,0 +1,91 @@
1
+ # Gem in a Box – Really simple rubygem hosting
2
+ [![Build Status](https://secure.travis-ci.org/cwninja/geminabox.png)](http://travis-ci.org/cwninja/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://i.imgur.com/hjm2PEc.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
+ geminabox -c
48
+
49
+ Change the host to upload to:
50
+
51
+ geminabox -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,271 @@
1
+ require 'rubygems'
2
+ require 'digest/md5'
3
+ require 'builder'
4
+ require 'sinatra/base'
5
+ require 'rubygems/indexer'
6
+ require 'rubygems/package'
7
+ require 'hostess'
8
+ require 'geminabox/version'
9
+ require 'rss/atom'
10
+ require 'tempfile'
11
+
12
+ class Geminabox < Sinatra::Base
13
+ enable :static, :methodoverride
14
+
15
+ set :public_folder, File.join(File.dirname(__FILE__), *%w[.. public])
16
+ set :data, File.join(File.dirname(__FILE__), *%w[.. data])
17
+ set :build_legacy, false
18
+ set :incremental_updates, true
19
+ set :views, File.join(File.dirname(__FILE__), *%w[.. views])
20
+ set :allow_replace, false
21
+ set :gem_permissions, 0644
22
+ use Hostess
23
+
24
+ class << self
25
+ def disallow_replace?
26
+ ! allow_replace
27
+ end
28
+
29
+ def fixup_bundler_rubygems!
30
+ return if @post_reset_hook_applied
31
+ Gem.post_reset{ Gem::Specification.all = nil } if defined? Bundler and Gem.respond_to? :post_reset
32
+ @post_reset_hook_applied = true
33
+ end
34
+ end
35
+
36
+ autoload :GemVersionCollection, "geminabox/gem_version_collection"
37
+ autoload :GemVersion, "geminabox/gem_version"
38
+ autoload :DiskCache, "geminabox/disk_cache"
39
+ autoload :IncomingGem, "geminabox/incoming_gem"
40
+
41
+ before do
42
+ headers 'X-Powered-By' => "geminabox #{GeminaboxVersion}"
43
+ end
44
+
45
+ get '/' do
46
+ @gems = load_gems
47
+ @index_gems = index_gems(@gems)
48
+ erb :index
49
+ end
50
+
51
+ get '/atom.xml' do
52
+ @gems = load_gems
53
+ erb :atom, :layout => false
54
+ end
55
+
56
+ get '/api/v1/dependencies' do
57
+ query_gems = params[:gems].to_s.split(',')
58
+ deps = query_gems.inject([]){|memo, query_gem| memo + gem_dependencies(query_gem) }
59
+ Marshal.dump(deps)
60
+ end
61
+
62
+ get '/upload' do
63
+ erb :upload
64
+ end
65
+
66
+ get '/example-gemfile' do
67
+ content_type 'text/plain'
68
+ erb :example_gemfile, :layout => false
69
+ end
70
+
71
+ get '/reindex' do
72
+ reindex(:force_rebuild)
73
+ redirect url("/")
74
+ end
75
+
76
+ get '/gems/:gemname' do
77
+ gems = Hash[load_gems.by_name]
78
+ @gem = gems[params[:gemname]]
79
+ halt 404 unless @gem
80
+ erb :gem
81
+ end
82
+
83
+ delete '/gems/*.gem' do
84
+ File.delete file_path if File.exists? file_path
85
+ reindex(:force_rebuild)
86
+ redirect url("/")
87
+ end
88
+
89
+ post '/upload' do
90
+ unless params[:file] && params[:file][:filename] && (tmpfile = params[:file][:tempfile])
91
+ @error = "No file selected"
92
+ halt [400, erb(:upload)]
93
+ end
94
+ handle_incoming_gem(IncomingGem.new(tmpfile))
95
+ end
96
+
97
+ post '/api/v1/gems' do
98
+ begin
99
+ handle_incoming_gem(IncomingGem.new(request.body))
100
+ rescue Object => o
101
+ File.open "/tmp/debug.txt", "a" do |io|
102
+ io.puts o, o.backtrace
103
+ end
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def handle_incoming_gem(gem)
110
+ prepare_data_folders
111
+ error_response(400, "Cannot process gem") unless gem.valid?
112
+ handle_replacement(gem) unless params[:overwrite] == "true"
113
+ write_and_index(gem)
114
+
115
+ if api_request?
116
+ "Gem #{gem.name} received and indexed. <a href=\"#{url("/")}\">Click here to return</a>"
117
+ else
118
+ redirect url("/")
119
+ end
120
+ end
121
+
122
+ def api_request?
123
+ request.accept.first != "text/html"
124
+ end
125
+
126
+ def error_response(code, message)
127
+ halt [code, message] if api_request?
128
+ html = <<HTML
129
+ <html>
130
+ <head><title>Error - #{code}</title></head>
131
+ <body>
132
+ <h1>Error - #{code}</h1>
133
+ <p>#{message}</p>
134
+ </body>
135
+ </html>
136
+ HTML
137
+ halt [code, html]
138
+ end
139
+
140
+ def prepare_data_folders
141
+ if File.exists? Geminabox.data
142
+ error_response( 500, "Please ensure #{File.expand_path(Geminabox.data)} is a directory." ) unless File.directory? Geminabox.data
143
+ error_response( 500, "Please ensure #{File.expand_path(Geminabox.data)} is writable by the geminabox web server." ) unless File.writable? Geminabox.data
144
+ else
145
+ begin
146
+ FileUtils.mkdir_p(settings.data)
147
+ rescue Errno::EACCES, Errno::ENOENT, RuntimeError => e
148
+ error_response( 500, "Could not create #{File.expand_path(Geminabox.data)}.\n#{e}\n#{e.message}" )
149
+ end
150
+ end
151
+
152
+ FileUtils.mkdir_p(File.join(settings.data, "gems"))
153
+ end
154
+
155
+ def handle_replacement(gem)
156
+ if Geminabox.disallow_replace? and File.exist?(gem.dest_filename)
157
+ existing_file_digest = Digest::SHA1.file(gem.dest_filename).hexdigest
158
+
159
+ if existing_file_digest != gem.hexdigest
160
+ error_response(409, "Updating an existing gem is not permitted.\nYou should either delete the existing version, or change your version number.")
161
+ else
162
+ error_response(200, "Ignoring upload, you uploaded the same thing previously.\nPlease use -o to ovewrite.")
163
+ end
164
+ end
165
+ end
166
+
167
+ def write_and_index(gem)
168
+ tmpfile = gem.gem_data
169
+ atomic_write(gem.dest_filename) do |f|
170
+ while blk = tmpfile.read(65536)
171
+ f << blk
172
+ end
173
+ end
174
+ reindex
175
+ end
176
+
177
+ def reindex(force_rebuild = false)
178
+ Geminabox.fixup_bundler_rubygems!
179
+ force_rebuild = true unless settings.incremental_updates
180
+ if force_rebuild
181
+ indexer.generate_index
182
+ dependency_cache.flush
183
+ else
184
+ begin
185
+ require 'geminabox/indexer'
186
+ updated_gemspecs = Geminabox::Indexer.updated_gemspecs(indexer)
187
+ Geminabox::Indexer.patch_rubygems_update_index_pre_1_8_25(indexer)
188
+ indexer.update_index
189
+ updated_gemspecs.each { |gem| dependency_cache.flush_key(gem.name) }
190
+ rescue => e
191
+ puts "#{e.class}:#{e.message}"
192
+ puts e.backtrace.join("\n")
193
+ reindex(:force_rebuild)
194
+ end
195
+ end
196
+ end
197
+
198
+ def indexer
199
+ Gem::Indexer.new(settings.data, :build_legacy => settings.build_legacy)
200
+ end
201
+
202
+ def file_path
203
+ File.expand_path(File.join(settings.data, *request.path_info))
204
+ end
205
+
206
+ def dependency_cache
207
+ @dependency_cache ||= Geminabox::DiskCache.new(File.join(settings.data, "_cache"))
208
+ end
209
+
210
+ def all_gems
211
+ %w(specs prerelease_specs).map{ |specs_file_type|
212
+ specs_file_path = File.join(settings.data, "#{specs_file_type}.#{Gem.marshal_version}.gz")
213
+ if File.exists?(specs_file_path)
214
+ Marshal.load(Gem.gunzip(Gem.read_binary(specs_file_path)))
215
+ else
216
+ []
217
+ end
218
+ }.inject(:|)
219
+ end
220
+
221
+ def load_gems
222
+ @loaded_gems ||= Geminabox::GemVersionCollection.new(all_gems)
223
+ end
224
+
225
+ def index_gems(gems)
226
+ Set.new(gems.map{|gem| gem.name[0..0].downcase})
227
+ end
228
+
229
+ # based on http://as.rubyonrails.org/classes/File.html
230
+ def atomic_write(file_name)
231
+ temp_dir = File.join(settings.data, "_temp")
232
+ FileUtils.mkdir_p(temp_dir)
233
+ temp_file = Tempfile.new("." + File.basename(file_name), temp_dir)
234
+ yield temp_file
235
+ temp_file.close
236
+ File.rename(temp_file.path, file_name)
237
+ File.chmod(settings.gem_permissions, file_name)
238
+ end
239
+
240
+ helpers do
241
+ def spec_for(gem_name, version)
242
+ spec_file = File.join(settings.data, "quick", "Marshal.#{Gem.marshal_version}", "#{gem_name}-#{version}.gemspec.rz")
243
+ Marshal.load(Gem.inflate(File.read(spec_file))) if File.exists? spec_file
244
+ end
245
+
246
+ # Return a list of versions of gem 'gem_name' with the dependencies of each version.
247
+ def gem_dependencies(gem_name)
248
+ dependency_cache.marshal_cache(gem_name) do
249
+ load_gems.
250
+ select { |gem| gem_name == gem.name }.
251
+ map { |gem| [gem, spec_for(gem.name, gem.number)] }.
252
+ reject { |(_, spec)| spec.nil? }.
253
+ map do |(gem, spec)|
254
+ {
255
+ :name => gem.name,
256
+ :number => gem.number.version,
257
+ :platform => gem.platform,
258
+ :dependencies => runtime_dependencies(spec)
259
+ }
260
+ end
261
+ end
262
+ end
263
+
264
+ def runtime_dependencies(spec)
265
+ spec.
266
+ dependencies.
267
+ select { |dep| dep.type == :runtime }.
268
+ map { |dep| [dep.name, dep.requirement.to_s] }
269
+ end
270
+ end
271
+ 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