geminabox 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of geminabox might be problematic. Click here for more details.
- data/README.markdown +12 -6
- data/lib/geminabox.rb +55 -21
- data/lib/geminabox_client.rb +58 -0
- data/lib/rubygems/commands/inabox_command.rb +21 -99
- data/lib/rubygems_plugin.rb +0 -6
- data/views/index.erb +1 -1
- data/views/layout.erb +4 -0
- metadata +71 -68
data/README.markdown
CHANGED
@@ -1,12 +1,18 @@
|
|
1
|
-
# Gem in a Box
|
1
|
+
# Gem in a Box – Really simple rubygem hosting
|
2
|
+
##
|
2
3
|
|
3
|
-
|
4
|
+
Geminabox let's you host your own gems, and push new gems to it just like with rubygems.org.
|
5
|
+
The bundler dependencies API is supported out of the box.
|
6
|
+
Authentication it left up to either the web server, or the Rack stack.
|
7
|
+
For basic auth, try [Rack::Auth](http://rack.rubyforge.org/doc/Rack/Auth/Basic.html).
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
![screen shot](http://pics.tomlea.co.uk/bbbba6/geminabox.png)
|
4
13
|
|
5
|
-
## Really simple rubygem hosting
|
6
14
|
|
7
|
-
Gem in a box is a simple [sinatra][sinatra] app to allow you to host your own in-house gems.
|
8
15
|
|
9
|
-
It has no security, or authentication so you should handle this yourself.
|
10
16
|
|
11
17
|
## Server Setup
|
12
18
|
|
@@ -17,7 +23,7 @@ Create a config.ru as follows:
|
|
17
23
|
require "rubygems"
|
18
24
|
require "geminabox"
|
19
25
|
|
20
|
-
Geminabox.data = "/var/geminabox-data" #
|
26
|
+
Geminabox.data = "/var/geminabox-data" # ... or wherever
|
21
27
|
run Geminabox
|
22
28
|
|
23
29
|
And finally, hook up the config.ru as you normally would ([passenger][passenger], [thin][thin], [unicorn][unicorn], whatever floats your boat).
|
data/lib/geminabox.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require
|
3
|
-
require
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'builder'
|
4
4
|
require 'sinatra/base'
|
5
5
|
require 'rubygems/builder'
|
6
|
-
require
|
7
|
-
|
6
|
+
require 'rubygems/indexer'
|
8
7
|
require 'hostess'
|
9
8
|
|
10
9
|
class Geminabox < Sinatra::Base
|
@@ -43,6 +42,20 @@ class Geminabox < Sinatra::Base
|
|
43
42
|
erb :atom, :layout => false
|
44
43
|
end
|
45
44
|
|
45
|
+
get '/api/v1/dependencies' do
|
46
|
+
query_gems = params[:gems].split(',')
|
47
|
+
deps = load_gems.gems.select {|gem| query_gems.include?(gem.name) }.map do |gem|
|
48
|
+
spec = spec_for(gem.name, gem.number)
|
49
|
+
{
|
50
|
+
:name => gem.name,
|
51
|
+
:number => gem.number.version,
|
52
|
+
:platform => gem.platform,
|
53
|
+
:dependencies => spec.dependencies.select {|dep| dep.type == :runtime}.map {|dep| [dep.name, dep.requirement.to_s] }
|
54
|
+
}
|
55
|
+
end
|
56
|
+
Marshal.dump(deps)
|
57
|
+
end
|
58
|
+
|
46
59
|
get '/upload' do
|
47
60
|
erb :upload
|
48
61
|
end
|
@@ -59,27 +72,37 @@ class Geminabox < Sinatra::Base
|
|
59
72
|
end
|
60
73
|
|
61
74
|
post '/upload' do
|
62
|
-
|
75
|
+
if File.exists? Geminabox.data
|
76
|
+
error_response( 500, "Please ensure #{File.expand_path(Geminabox.data)} is writable by the geminabox web server." ) unless File.writable? Geminabox.data
|
77
|
+
error_response( 500, "Please ensure #{File.expand_path(Geminabox.data)} is a directory." ) unless File.directory? Geminabox.data
|
78
|
+
else
|
79
|
+
begin
|
80
|
+
FileUtils.mkdir_p(settings.data)
|
81
|
+
rescue Errno::EACCES, Errno::ENOENT, RuntimeError => e
|
82
|
+
error_response( 500, "Could not create #{File.expand_path(Geminabox.data)}.\n#{e}\n#{e.message}" )
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
63
86
|
unless params[:file] && (tmpfile = params[:file][:tempfile]) && (name = params[:file][:filename])
|
64
87
|
@error = "No file selected"
|
65
|
-
|
88
|
+
halt [400, erb(:upload)]
|
66
89
|
end
|
67
90
|
|
68
|
-
|
91
|
+
FileUtils.mkdir_p(File.join(settings.data, "gems"))
|
69
92
|
|
70
|
-
|
71
|
-
|
72
|
-
dest_filename = File.join(settings.data, "gems", File.basename(name))
|
93
|
+
tmpfile.binmode
|
73
94
|
|
95
|
+
gem_name = File.basename(name)
|
96
|
+
dest_filename = File.join(settings.data, "gems", gem_name)
|
74
97
|
|
75
98
|
if Geminabox.disallow_replace? and File.exist?(dest_filename)
|
76
99
|
existing_file_digest = Digest::SHA1.file(dest_filename).hexdigest
|
77
100
|
tmpfile_digest = Digest::SHA1.file(tmpfile.path).hexdigest
|
78
101
|
|
79
102
|
if existing_file_digest != tmpfile_digest
|
80
|
-
|
103
|
+
error_response(409, "Updating an existing gem is not permitted.\nYou should either delete the existing version, or change your version number.")
|
81
104
|
else
|
82
|
-
|
105
|
+
error_response(200, "Ignoring upload, you uploaded the same thing previously.")
|
83
106
|
end
|
84
107
|
end
|
85
108
|
|
@@ -89,12 +112,22 @@ class Geminabox < Sinatra::Base
|
|
89
112
|
end
|
90
113
|
end
|
91
114
|
reindex
|
92
|
-
|
115
|
+
|
116
|
+
if api_request?
|
117
|
+
"Gem #{gem_name} received and indexed."
|
118
|
+
else
|
119
|
+
redirect url("/")
|
120
|
+
end
|
93
121
|
end
|
94
122
|
|
95
123
|
private
|
96
124
|
|
125
|
+
def api_request?
|
126
|
+
request.accept.first == "text/plain"
|
127
|
+
end
|
128
|
+
|
97
129
|
def error_response(code, message)
|
130
|
+
halt [code, message] if api_request?
|
98
131
|
html = <<HTML
|
99
132
|
<html>
|
100
133
|
<head><title>Error - #{code}</title></head>
|
@@ -104,7 +137,7 @@ private
|
|
104
137
|
</body>
|
105
138
|
</html>
|
106
139
|
HTML
|
107
|
-
[code, html]
|
140
|
+
halt [code, html]
|
108
141
|
end
|
109
142
|
|
110
143
|
def reindex(force_rebuild = false)
|
@@ -132,13 +165,14 @@ HTML
|
|
132
165
|
end
|
133
166
|
|
134
167
|
def load_gems
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
168
|
+
@loaded_gems ||=
|
169
|
+
%w(specs prerelease_specs).inject(GemVersionCollection.new){|gems, specs_file_type|
|
170
|
+
specs_file_path = File.join(settings.data, "#{specs_file_type}.#{Gem.marshal_version}.gz")
|
171
|
+
if File.exists?(specs_file_path)
|
172
|
+
gems |= Geminabox::GemVersionCollection.new(Marshal.load(Gem.gunzip(Gem.read_binary(specs_file_path))))
|
173
|
+
end
|
174
|
+
gems
|
175
|
+
}
|
142
176
|
end
|
143
177
|
|
144
178
|
def index_gems(gems)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'httpclient'
|
3
|
+
|
4
|
+
class GeminaboxClient
|
5
|
+
attr_reader :url, :http_client
|
6
|
+
|
7
|
+
def initialize(url)
|
8
|
+
extract_username_and_password_from_url!(url)
|
9
|
+
@http_client = HTTPClient.new
|
10
|
+
@http_client.set_auth(url_for(:upload), @username, @password) if @username or @password
|
11
|
+
@http_client.www_auth.basic_auth.challenge(url_for(:upload)) # Workaround: https://github.com/nahi/httpclient/issues/63
|
12
|
+
@http_client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
13
|
+
end
|
14
|
+
|
15
|
+
def extract_username_and_password_from_url!(url)
|
16
|
+
uri = URI.parse(url.to_s)
|
17
|
+
@username, @password = uri.user, uri.password
|
18
|
+
uri.user = uri.password = nil
|
19
|
+
uri.path = uri.path + "/" unless uri.path.end_with?("/")
|
20
|
+
@url = uri.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
def url_for(path)
|
24
|
+
url + path.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def push(gemfile)
|
28
|
+
response = http_client.post(url_for(:upload), {'file' => File.open(gemfile, "rb")}, {'Accept' => 'text/plain'})
|
29
|
+
|
30
|
+
if response.status < 400
|
31
|
+
response.body
|
32
|
+
else
|
33
|
+
raise GeminaboxClient::Error, "Error (#{response.code} received)\n\n#{response.body}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class GeminaboxClient::Error < RuntimeError
|
40
|
+
end
|
41
|
+
|
42
|
+
module GeminaboxClient::GemLocator
|
43
|
+
def find_gem(dir)
|
44
|
+
gemname = File.split(dir).last
|
45
|
+
glob_matcher = "{pkg/,}#{gemname}-*.gem"
|
46
|
+
latest_gem_for(gemname, Dir.glob(glob_matcher)) or raise Gem::CommandLineError, NO_GEM_PROVIDED_ERROR_MESSAGE
|
47
|
+
end
|
48
|
+
|
49
|
+
def latest_gem_for(gemname, files)
|
50
|
+
regexp_matcher = %r{(?:pkg/)#{gemname}-(#{Gem::Version::VERSION_PATTERN})\.gem}
|
51
|
+
sorter = lambda{|v| Gem::Version.new(regexp_matcher.match(v)[1]) }
|
52
|
+
files.grep(regexp_matcher).max_by(&sorter)
|
53
|
+
end
|
54
|
+
|
55
|
+
extend self
|
56
|
+
|
57
|
+
NO_GEM_PROVIDED_ERROR_MESSAGE = "Couldn't find a gem in pkg, please specify a gem name on the command line (e.g. gem inabox GEMNAME)"
|
58
|
+
end
|
@@ -1,5 +1,4 @@
|
|
1
|
-
require '
|
2
|
-
require 'yaml'
|
1
|
+
require 'rubygems/command'
|
3
2
|
|
4
3
|
class Gem::Commands::InaboxCommand < Gem::Command
|
5
4
|
def description
|
@@ -26,70 +25,40 @@ class Gem::Commands::InaboxCommand < Gem::Command
|
|
26
25
|
end
|
27
26
|
end
|
28
27
|
|
28
|
+
def last_minute_requires!
|
29
|
+
require 'yaml'
|
30
|
+
require File.expand_path("../../../geminabox_client.rb", __FILE__)
|
31
|
+
end
|
32
|
+
|
29
33
|
def execute
|
34
|
+
last_minute_requires!
|
30
35
|
return configure if options[:configure]
|
31
|
-
|
32
|
-
send_gem
|
33
|
-
end
|
36
|
+
configure unless geminabox_host
|
34
37
|
|
35
|
-
def setup
|
36
38
|
if options[:args].size == 0
|
37
|
-
|
39
|
+
say "You didn't specify a gem, looking for one in . and in ./pkg/..."
|
40
|
+
gemfiles = [GeminaboxClient::GemLocator.find_gem(Dir.pwd)]
|
38
41
|
else
|
39
|
-
|
42
|
+
gemfiles = get_all_gem_names
|
40
43
|
end
|
41
|
-
configure unless geminabox_host
|
42
|
-
end
|
43
44
|
|
44
|
-
|
45
|
-
say "You didn't specify a gem, looking for one in pkg..."
|
46
|
-
path, directory = File.split(Dir.pwd)
|
47
|
-
possible_gems = Dir.glob("pkg/#{directory}-*.gem")
|
48
|
-
raise Gem::CommandLineError, "Couldn't find a gem in pkg, please specify a gem name on the command line (e.g. gem inabox GEMNAME)" unless possible_gems.any?
|
49
|
-
name_regexp = Regexp.new("^pkg/#{directory}-")
|
50
|
-
possible_gems.sort_by{ |a| Gem::Version.new(a.sub(name_regexp,'')) }.last
|
45
|
+
send_gems(gemfiles)
|
51
46
|
end
|
52
47
|
|
53
|
-
def
|
54
|
-
|
55
|
-
url = URI.join(geminabox_host, "upload")
|
56
|
-
|
57
|
-
url_for_presentation = url.clone
|
58
|
-
url_for_presentation.password = '***' if url_for_presentation.password
|
59
|
-
|
48
|
+
def send_gems(gemfiles)
|
49
|
+
client = GeminaboxClient.new(geminabox_host)
|
60
50
|
|
61
|
-
|
62
|
-
say "Pushing #{File.basename(gemfile)} to #{
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
req = Net::HTTP::Post.new(url.path, request_headers)
|
69
|
-
req.basic_auth(url.user, url.password) if url.user
|
70
|
-
handle_response(con.request(req, request_body))
|
71
|
-
}
|
51
|
+
gemfiles.each do |gemfile|
|
52
|
+
say "Pushing #{File.basename(gemfile)} to #{client.url}..."
|
53
|
+
begin
|
54
|
+
say client.push(gemfile)
|
55
|
+
rescue GeminaboxClient::Error => e
|
56
|
+
alert_error e.message
|
57
|
+
terminate_interaction(1)
|
72
58
|
end
|
73
59
|
end
|
74
60
|
end
|
75
61
|
|
76
|
-
def proxy
|
77
|
-
if proxy_info = ENV['http_proxy'] || ENV['HTTP_PROXY'] and uri = URI.parse(proxy_info)
|
78
|
-
Net::HTTP::Proxy(uri.host, uri.port, uri.user, uri.password)
|
79
|
-
else
|
80
|
-
Net::HTTP
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def handle_response(response)
|
85
|
-
case response
|
86
|
-
when Net::HTTPSuccess, Net::HTTPRedirection
|
87
|
-
puts response.body
|
88
|
-
else
|
89
|
-
response.error!
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
62
|
def config_path
|
94
63
|
File.join(Gem.user_home, '.gem', 'geminabox')
|
95
64
|
end
|
@@ -115,51 +84,4 @@ class Gem::Commands::InaboxCommand < Gem::Command
|
|
115
84
|
end
|
116
85
|
end
|
117
86
|
|
118
|
-
module Multipart
|
119
|
-
require 'net/http'
|
120
|
-
require 'cgi'
|
121
|
-
|
122
|
-
class Param
|
123
|
-
attr_accessor :k, :v
|
124
|
-
def initialize( k, v )
|
125
|
-
@k = k
|
126
|
-
@v = v
|
127
|
-
end
|
128
|
-
|
129
|
-
def to_multipart
|
130
|
-
return "Content-Disposition: form-data; name=\"#{k}\"\r\n\r\n#{v}\r\n"
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
class FileParam
|
135
|
-
attr_accessor :k, :filename, :content
|
136
|
-
def initialize( k, filename, content )
|
137
|
-
@k = k
|
138
|
-
@filename = filename
|
139
|
-
@content = content
|
140
|
-
end
|
141
|
-
|
142
|
-
def to_multipart
|
143
|
-
return "Content-Disposition: form-data; name=\"#{k}\"; filename=\"#{filename}\"\r\n" + "Content-Transfer-Encoding: binary\r\n" + "Content-Type: application/octet-stream\r\n\r\n" + content + "\r\n"
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
class MultipartPost
|
148
|
-
BOUNDARY = 'tarsiers-rule0000'
|
149
|
-
HEADER = {"Content-type" => "multipart/form-data, boundary=" + BOUNDARY + " "}
|
150
|
-
|
151
|
-
def prepare_query(params)
|
152
|
-
fp = []
|
153
|
-
params.each {|k,v|
|
154
|
-
if v.respond_to?(:read)
|
155
|
-
fp.push(FileParam.new(k, v.path, v.read))
|
156
|
-
else
|
157
|
-
fp.push(Param.new(k,v))
|
158
|
-
end
|
159
|
-
}
|
160
|
-
query = fp.collect {|p| "--" + BOUNDARY + "\r\n" + p.to_multipart }.join("") + "--" + BOUNDARY + "--"
|
161
|
-
return query, HEADER
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
87
|
end
|
data/lib/rubygems_plugin.rb
CHANGED
data/views/index.erb
CHANGED
data/views/layout.erb
CHANGED
@@ -9,7 +9,11 @@
|
|
9
9
|
<div id="content">
|
10
10
|
<h1>Gem in a Box</h1>
|
11
11
|
<p>
|
12
|
+
<small>If you did not need any credentials to get to this page</small>
|
12
13
|
<pre><code>gem sources -a <%= url "/" %></code></pre>
|
14
|
+
<small>If you needed some credentials to get to this page</small>
|
15
|
+
<pre><code>gem sources -a <%= url.to_s.gsub('://','://username:password@') %></code></pre>
|
16
|
+
<small>and then ...</small>
|
13
17
|
<pre><code>gem install geminabox<br />gem inabox [gemfile]</code></pre>
|
14
18
|
</p>
|
15
19
|
<%= yield %>
|
metadata
CHANGED
@@ -1,77 +1,83 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: geminabox
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.6.0
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 5
|
9
|
-
- 2
|
10
|
-
version: 0.5.2
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Tom Lea
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
- !ruby/object:Gem::Dependency
|
21
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
22
|
-
none: false
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
hash: 3
|
27
|
-
segments:
|
28
|
-
- 0
|
29
|
-
version: "0"
|
30
|
-
version_requirements: *id001
|
12
|
+
date: 2012-03-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
31
15
|
name: sinatra
|
16
|
+
requirement: &70215540161800 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
32
22
|
type: :runtime
|
33
23
|
prerelease: false
|
34
|
-
|
35
|
-
|
36
|
-
none: false
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
hash: 3
|
41
|
-
segments:
|
42
|
-
- 0
|
43
|
-
version: "0"
|
44
|
-
version_requirements: *id002
|
24
|
+
version_requirements: *70215540161800
|
25
|
+
- !ruby/object:Gem::Dependency
|
45
26
|
name: builder
|
27
|
+
requirement: &70215540161340 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
46
33
|
type: :runtime
|
47
34
|
prerelease: false
|
48
|
-
|
49
|
-
|
35
|
+
version_requirements: *70215540161340
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: httpclient
|
38
|
+
requirement: &70215540160880 !ruby/object:Gem::Requirement
|
50
39
|
none: false
|
51
|
-
requirements:
|
52
|
-
- -
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70215540160880
|
47
|
+
- !ruby/object:Gem::Dependency
|
59
48
|
name: rake
|
49
|
+
requirement: &70215540160460 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70215540160460
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: minitest
|
60
|
+
requirement: &70215540160040 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
60
66
|
type: :development
|
61
67
|
prerelease: false
|
68
|
+
version_requirements: *70215540160040
|
62
69
|
description: A sinatra based gem hosting app, with client side gem push style functionality.
|
63
70
|
email: contrib@tomlea.co.uk
|
64
71
|
executables: []
|
65
|
-
|
66
72
|
extensions: []
|
67
|
-
|
68
|
-
extra_rdoc_files:
|
73
|
+
extra_rdoc_files:
|
69
74
|
- README.markdown
|
70
|
-
files:
|
75
|
+
files:
|
71
76
|
- README.markdown
|
72
77
|
- lib/geminabox/gem_version.rb
|
73
78
|
- lib/geminabox/gem_version_collection.rb
|
74
79
|
- lib/geminabox.rb
|
80
|
+
- lib/geminabox_client.rb
|
75
81
|
- lib/hostess.rb
|
76
82
|
- lib/rubygems/commands/inabox_command.rb
|
77
83
|
- lib/rubygems_plugin.rb
|
@@ -85,37 +91,34 @@ files:
|
|
85
91
|
- views/upload.erb
|
86
92
|
homepage: http://tomlea.co.uk/p/gem-in-a-box
|
87
93
|
licenses: []
|
88
|
-
|
89
94
|
post_install_message:
|
90
|
-
rdoc_options:
|
95
|
+
rdoc_options:
|
91
96
|
- --main
|
92
97
|
- README.markdown
|
93
|
-
require_paths:
|
98
|
+
require_paths:
|
94
99
|
- lib
|
95
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
101
|
none: false
|
97
|
-
requirements:
|
98
|
-
- -
|
99
|
-
- !ruby/object:Gem::Version
|
100
|
-
|
101
|
-
segments:
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
segments:
|
102
107
|
- 0
|
103
|
-
|
104
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
hash: -3189528604600378611
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
110
|
none: false
|
106
|
-
requirements:
|
107
|
-
- -
|
108
|
-
- !ruby/object:Gem::Version
|
109
|
-
|
110
|
-
segments:
|
111
|
+
requirements:
|
112
|
+
- - ! '>='
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
segments:
|
111
116
|
- 0
|
112
|
-
|
117
|
+
hash: -3189528604600378611
|
113
118
|
requirements: []
|
114
|
-
|
115
119
|
rubyforge_project:
|
116
|
-
rubygems_version: 1.8.
|
120
|
+
rubygems_version: 1.8.10
|
117
121
|
signing_key:
|
118
122
|
specification_version: 3
|
119
123
|
summary: Really simple rubygem hosting
|
120
124
|
test_files: []
|
121
|
-
|