rubygems-server 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: edae593cc5b009ece9ad798a753242bf5fc501b78feedf34261e86f41605f8b7
4
+ data.tar.gz: c42e795f5252111d62d0a595c5e057ccb287953ca262a9ead9c4149b812ee4a7
5
+ SHA512:
6
+ metadata.gz: 72b1647b369e1d0a4cfaa22adf9b6c6b2d19c566944bf324e9d726ed234db81b8a7f2957580013fbc5fd2e46ad68e30778822bcd9d83deb1a53d3a2acffdefe7
7
+ data.tar.gz: b70f06a91f6263a15eb64bf3844850497fd6e4482733c44fcd6e820909686206285d8b863f16846fed0ec80f33bad0315fe31064e725b499b0eaecf2eafca02c
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ gem "rake"
8
+ gem "test-unit"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Hiroshi SHIBATA
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # Gem::Server
2
+
3
+ Gem::Server and allows users to serve gems for consumption by `gem --remote-install`.
4
+
5
+ gem_server starts an HTTP server on the given port and serves the following:
6
+
7
+ * "/" - Browsing of gem spec files for installed gems
8
+ * "/specs.#{Gem.marshal_version}.gz" - specs name/version/platform index
9
+ * "/latest_specs.#{Gem.marshal_version}.gz" - latest specs
10
+ name/version/platform index
11
+ * "/quick/" - Individual gemspecs
12
+ * "/gems" - Direct access to download the installable gems
13
+ * "/rdoc?q=" - Search for installed rdoc documentation
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'rubygems-server'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle install
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install rubygems-server
30
+
31
+ ## Usage
32
+
33
+ ```ruby
34
+ gem_server = Gem::Server.new Gem.dir, 8089, false
35
+ gem_server.run
36
+ ```
37
+
38
+ ## Development
39
+
40
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
41
+
42
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
43
+
44
+ ## Contributing
45
+
46
+ Bug reports and pull requests are welcome on GitHub at https://github.com/rubygems/rubygems-server.
47
+
48
+ ## License
49
+
50
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "rubygems/server"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+ require 'rubygems/command'
3
+ require_relative '../server'
4
+
5
+ class Gem::Commands::ServerCommand < Gem::Command
6
+
7
+ def initialize
8
+ super 'server', 'Documentation and gem repository HTTP server',
9
+ :port => 8808, :gemdir => [], :daemon => false
10
+
11
+ OptionParser.accept :Port do |port|
12
+ if port =~ /\A\d+\z/
13
+ port = Integer port
14
+ raise OptionParser::InvalidArgument, "#{port}: not a port number" if
15
+ port > 65535
16
+
17
+ port
18
+ else
19
+ begin
20
+ Socket.getservbyname port
21
+ rescue SocketError
22
+ raise OptionParser::InvalidArgument, "#{port}: no such named service"
23
+ end
24
+ end
25
+ end
26
+
27
+ add_option '-p', '--port=PORT', :Port,
28
+ 'port to listen on' do |port, options|
29
+ options[:port] = port
30
+ end
31
+
32
+ add_option '-d', '--dir=GEMDIR',
33
+ 'directories from which to serve gems',
34
+ 'multiple directories may be provided' do |gemdir, options|
35
+ options[:gemdir] << File.expand_path(gemdir)
36
+ end
37
+
38
+ add_option '--[no-]daemon', 'run as a daemon' do |daemon, options|
39
+ options[:daemon] = daemon
40
+ end
41
+
42
+ add_option '-b', '--bind=HOST,HOST',
43
+ 'addresses to bind', Array do |address, options|
44
+ options[:addresses] ||= []
45
+ options[:addresses].push(*address)
46
+ end
47
+
48
+ add_option '-l', '--launch[=COMMAND]',
49
+ 'launches a browser window',
50
+ "COMMAND defaults to 'start' on Windows",
51
+ "and 'open' on all other platforms" do |launch, options|
52
+ launch ||= Gem.win_platform? ? 'start' : 'open'
53
+ options[:launch] = launch
54
+ end
55
+ end
56
+
57
+ def defaults_str # :nodoc:
58
+ "--port 8808 --dir #{Gem.dir} --no-daemon"
59
+ end
60
+
61
+ def description # :nodoc:
62
+ <<-EOF
63
+ The server command starts up a web server that hosts the RDoc for your
64
+ installed gems and can operate as a server for installation of gems on other
65
+ machines.
66
+
67
+ The cache files for installed gems must exist to use the server as a source
68
+ for gem installation.
69
+
70
+ To install gems from a running server, use `gem install GEMNAME --source
71
+ http://gem_server_host:8808`
72
+
73
+ You can set up a shortcut to gem server documentation using the URL:
74
+
75
+ http://localhost:8808/rdoc?q=%s - Firefox
76
+ http://localhost:8808/rdoc?q=* - LaunchBar
77
+
78
+ EOF
79
+ end
80
+
81
+ def execute
82
+ options[:gemdir] = Gem.path if options[:gemdir].empty?
83
+ Gem::Server.run options
84
+ end
85
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubygems
4
+ module Server
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,882 @@
1
+ # frozen_string_literal: true
2
+ require 'zlib'
3
+ require 'erb'
4
+ require 'uri'
5
+
6
+ require 'rubygems'
7
+ require 'rubygems/rdoc'
8
+
9
+ ##
10
+ # Gem::Server and allows users to serve gems for consumption by
11
+ # `gem --remote-install`.
12
+ #
13
+ # gem_server starts an HTTP server on the given port and serves the following:
14
+ # * "/" - Browsing of gem spec files for installed gems
15
+ # * "/specs.#{Gem.marshal_version}.gz" - specs name/version/platform index
16
+ # * "/latest_specs.#{Gem.marshal_version}.gz" - latest specs
17
+ # name/version/platform index
18
+ # * "/quick/" - Individual gemspecs
19
+ # * "/gems" - Direct access to download the installable gems
20
+ # * "/rdoc?q=" - Search for installed rdoc documentation
21
+ #
22
+ # == Usage
23
+ #
24
+ # gem_server = Gem::Server.new Gem.dir, 8089, false
25
+ # gem_server.run
26
+ #
27
+ #--
28
+ # TODO Refactor into a real WEBrick servlet to remove code duplication.
29
+
30
+ class Gem::Server
31
+ attr_reader :spec_dirs
32
+
33
+ include ERB::Util
34
+ include Gem::UserInteraction
35
+
36
+ SEARCH = <<-ERB.freeze
37
+ <form class="headerSearch" name="headerSearchForm" method="get" action="/rdoc">
38
+ <div id="search" style="float:right">
39
+ <label for="q">Filter/Search</label>
40
+ <input id="q" type="text" style="width:10em" name="q">
41
+ <button type="submit" style="display:none"></button>
42
+ </div>
43
+ </form>
44
+ ERB
45
+
46
+ DOC_TEMPLATE = <<-'ERB'.freeze
47
+ <?xml version="1.0" encoding="iso-8859-1"?>
48
+ <!DOCTYPE html
49
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
50
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
51
+
52
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
53
+ <head>
54
+ <title>RubyGems Documentation Index</title>
55
+ <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
56
+ </head>
57
+ <body>
58
+ <div id="fileHeader">
59
+ <%= SEARCH %>
60
+ <h1>RubyGems Documentation Index</h1>
61
+ </div>
62
+ <!-- banner header -->
63
+
64
+ <div id="bodyContent">
65
+ <div id="contextContent">
66
+ <div id="description">
67
+ <h1>Summary</h1>
68
+ <p>There are <%=values["gem_count"]%> gems installed:</p>
69
+ <p>
70
+ <%= values["specs"].map { |v| "<a href=\"##{u v["name"]}\">#{h v["name"]}</a>" }.join ', ' %>.
71
+ <h1>Gems</h1>
72
+
73
+ <dl>
74
+ <% values["specs"].each do |spec| %>
75
+ <dt>
76
+ <% if spec["first_name_entry"] then %>
77
+ <a name="<%=h spec["name"]%>"></a>
78
+ <% end %>
79
+
80
+ <b><%=h spec["name"]%> <%=h spec["version"]%></b>
81
+
82
+ <% if spec["ri_installed"] || spec["rdoc_installed"] then %>
83
+ <a href="<%=spec["doc_path"]%>">[rdoc]</a>
84
+ <% else %>
85
+ <span title="rdoc not installed">[rdoc]</span>
86
+ <% end %>
87
+
88
+ <% if spec["homepage"] then %>
89
+ <a href="<%=uri_encode spec["homepage"]%>" title="<%=h spec["homepage"]%>">[www]</a>
90
+ <% else %>
91
+ <span title="no homepage available">[www]</span>
92
+ <% end %>
93
+
94
+ <% if spec["has_deps"] then %>
95
+ - depends on
96
+ <%= spec["dependencies"].map { |v| "<a href=\"##{u v["name"]}\">#{h v["name"]}</a>" }.join ', ' %>.
97
+ <% end %>
98
+ </dt>
99
+ <dd>
100
+ <%=spec["summary"]%>
101
+ <% if spec["executables"] then %>
102
+ <br/>
103
+
104
+ <% if spec["only_one_executable"] then %>
105
+ Executable is
106
+ <% else %>
107
+ Executables are
108
+ <%end%>
109
+
110
+ <%= spec["executables"].map { |v| "<span class=\"context-item-name\">#{h v["executable"]}</span>"}.join ', ' %>.
111
+
112
+ <%end%>
113
+ <br/>
114
+ <br/>
115
+ </dd>
116
+ <% end %>
117
+ </dl>
118
+
119
+ </div>
120
+ </div>
121
+ </div>
122
+ <div id="validator-badges">
123
+ <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
124
+ </div>
125
+ </body>
126
+ </html>
127
+ ERB
128
+
129
+ # CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108
130
+ RDOC_CSS = <<-CSS.freeze
131
+ body {
132
+ font-family: Verdana,Arial,Helvetica,sans-serif;
133
+ font-size: 90%;
134
+ margin: 0;
135
+ margin-left: 40px;
136
+ padding: 0;
137
+ background: white;
138
+ }
139
+
140
+ h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
141
+ h1 { font-size: 150%; }
142
+ h2,h3,h4 { margin-top: 1em; }
143
+
144
+ a { background: #eef; color: #039; text-decoration: none; }
145
+ a:hover { background: #039; color: #eef; }
146
+
147
+ /* Override the base stylesheets Anchor inside a table cell */
148
+ td > a {
149
+ background: transparent;
150
+ color: #039;
151
+ text-decoration: none;
152
+ }
153
+
154
+ /* and inside a section title */
155
+ .section-title > a {
156
+ background: transparent;
157
+ color: #eee;
158
+ text-decoration: none;
159
+ }
160
+
161
+ /* === Structural elements =================================== */
162
+
163
+ div#index {
164
+ margin: 0;
165
+ margin-left: -40px;
166
+ padding: 0;
167
+ font-size: 90%;
168
+ }
169
+
170
+
171
+ div#index a {
172
+ margin-left: 0.7em;
173
+ }
174
+
175
+ div#index .section-bar {
176
+ margin-left: 0px;
177
+ padding-left: 0.7em;
178
+ background: #ccc;
179
+ font-size: small;
180
+ }
181
+
182
+
183
+ div#classHeader, div#fileHeader {
184
+ width: auto;
185
+ color: white;
186
+ padding: 0.5em 1.5em 0.5em 1.5em;
187
+ margin: 0;
188
+ margin-left: -40px;
189
+ border-bottom: 3px solid #006;
190
+ }
191
+
192
+ div#classHeader a, div#fileHeader a {
193
+ background: inherit;
194
+ color: white;
195
+ }
196
+
197
+ div#classHeader td, div#fileHeader td {
198
+ background: inherit;
199
+ color: white;
200
+ }
201
+
202
+
203
+ div#fileHeader {
204
+ background: #057;
205
+ }
206
+
207
+ div#classHeader {
208
+ background: #048;
209
+ }
210
+
211
+
212
+ .class-name-in-header {
213
+ font-size: 180%;
214
+ font-weight: bold;
215
+ }
216
+
217
+
218
+ div#bodyContent {
219
+ padding: 0 1.5em 0 1.5em;
220
+ }
221
+
222
+ div#description {
223
+ padding: 0.5em 1.5em;
224
+ background: #efefef;
225
+ border: 1px dotted #999;
226
+ }
227
+
228
+ div#description h1,h2,h3,h4,h5,h6 {
229
+ color: #125;;
230
+ background: transparent;
231
+ }
232
+
233
+ div#validator-badges {
234
+ text-align: center;
235
+ }
236
+ div#validator-badges img { border: 0; }
237
+
238
+ div#copyright {
239
+ color: #333;
240
+ background: #efefef;
241
+ font: 0.75em sans-serif;
242
+ margin-top: 5em;
243
+ margin-bottom: 0;
244
+ padding: 0.5em 2em;
245
+ }
246
+
247
+
248
+ /* === Classes =================================== */
249
+
250
+ table.header-table {
251
+ color: white;
252
+ font-size: small;
253
+ }
254
+
255
+ .type-note {
256
+ font-size: small;
257
+ color: #DEDEDE;
258
+ }
259
+
260
+ .xxsection-bar {
261
+ background: #eee;
262
+ color: #333;
263
+ padding: 3px;
264
+ }
265
+
266
+ .section-bar {
267
+ color: #333;
268
+ border-bottom: 1px solid #999;
269
+ margin-left: -20px;
270
+ }
271
+
272
+
273
+ .section-title {
274
+ background: #79a;
275
+ color: #eee;
276
+ padding: 3px;
277
+ margin-top: 2em;
278
+ margin-left: -30px;
279
+ border: 1px solid #999;
280
+ }
281
+
282
+ .top-aligned-row { vertical-align: top }
283
+ .bottom-aligned-row { vertical-align: bottom }
284
+
285
+ /* --- Context section classes ----------------------- */
286
+
287
+ .context-row { }
288
+ .context-item-name { font-family: monospace; font-weight: bold; color: black; }
289
+ .context-item-value { font-size: small; color: #448; }
290
+ .context-item-desc { color: #333; padding-left: 2em; }
291
+
292
+ /* --- Method classes -------------------------- */
293
+ .method-detail {
294
+ background: #efefef;
295
+ padding: 0;
296
+ margin-top: 0.5em;
297
+ margin-bottom: 1em;
298
+ border: 1px dotted #ccc;
299
+ }
300
+ .method-heading {
301
+ color: black;
302
+ background: #ccc;
303
+ border-bottom: 1px solid #666;
304
+ padding: 0.2em 0.5em 0 0.5em;
305
+ }
306
+ .method-signature { color: black; background: inherit; }
307
+ .method-name { font-weight: bold; }
308
+ .method-args { font-style: italic; }
309
+ .method-description { padding: 0 0.5em 0 0.5em; }
310
+
311
+ /* --- Source code sections -------------------- */
312
+
313
+ a.source-toggle { font-size: 90%; }
314
+ div.method-source-code {
315
+ background: #262626;
316
+ color: #ffdead;
317
+ margin: 1em;
318
+ padding: 0.5em;
319
+ border: 1px dashed #999;
320
+ overflow: hidden;
321
+ }
322
+
323
+ div.method-source-code pre { color: #ffdead; overflow: hidden; }
324
+
325
+ /* --- Ruby keyword styles --------------------- */
326
+
327
+ .standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
328
+
329
+ .ruby-constant { color: #7fffd4; background: transparent; }
330
+ .ruby-keyword { color: #00ffff; background: transparent; }
331
+ .ruby-ivar { color: #eedd82; background: transparent; }
332
+ .ruby-operator { color: #00ffee; background: transparent; }
333
+ .ruby-identifier { color: #ffdead; background: transparent; }
334
+ .ruby-node { color: #ffa07a; background: transparent; }
335
+ .ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
336
+ .ruby-regexp { color: #ffa07a; background: transparent; }
337
+ .ruby-value { color: #7fffd4; background: transparent; }
338
+ CSS
339
+
340
+ RDOC_NO_DOCUMENTATION = <<-'ERB'.freeze
341
+ <?xml version="1.0" encoding="iso-8859-1"?>
342
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
343
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
344
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
345
+ <head>
346
+ <title>Found documentation</title>
347
+ <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
348
+ </head>
349
+ <body>
350
+ <div id="fileHeader">
351
+ <%= SEARCH %>
352
+ <h1>No documentation found</h1>
353
+ </div>
354
+
355
+ <div id="bodyContent">
356
+ <div id="contextContent">
357
+ <div id="description">
358
+ <p>No gems matched <%= h query.inspect %></p>
359
+
360
+ <p>
361
+ Back to <a href="/">complete gem index</a>
362
+ </p>
363
+
364
+ </div>
365
+ </div>
366
+ </div>
367
+ <div id="validator-badges">
368
+ <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
369
+ </div>
370
+ </body>
371
+ </html>
372
+ ERB
373
+
374
+ RDOC_SEARCH_TEMPLATE = <<-'ERB'.freeze
375
+ <?xml version="1.0" encoding="iso-8859-1"?>
376
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
377
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
378
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
379
+ <head>
380
+ <title>Found documentation</title>
381
+ <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
382
+ </head>
383
+ <body>
384
+ <div id="fileHeader">
385
+ <%= SEARCH %>
386
+ <h1>Found documentation</h1>
387
+ </div>
388
+ <!-- banner header -->
389
+
390
+ <div id="bodyContent">
391
+ <div id="contextContent">
392
+ <div id="description">
393
+ <h1>Summary</h1>
394
+ <p><%=doc_items.length%> documentation topics found.</p>
395
+ <h1>Topics</h1>
396
+
397
+ <dl>
398
+ <% doc_items.each do |doc_item| %>
399
+ <dt>
400
+ <b><%=doc_item[:name]%></b>
401
+ <a href="<%=u doc_item[:url]%>">[rdoc]</a>
402
+ </dt>
403
+ <dd>
404
+ <%=h doc_item[:summary]%>
405
+ <br/>
406
+ <br/>
407
+ </dd>
408
+ <% end %>
409
+ </dl>
410
+
411
+ <p>
412
+ Back to <a href="/">complete gem index</a>
413
+ </p>
414
+
415
+ </div>
416
+ </div>
417
+ </div>
418
+ <div id="validator-badges">
419
+ <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
420
+ </div>
421
+ </body>
422
+ </html>
423
+ ERB
424
+
425
+ def self.run(options)
426
+ new(options[:gemdir], options[:port], options[:daemon],
427
+ options[:launch], options[:addresses]).run
428
+ end
429
+
430
+ def initialize(gem_dirs, port, daemon, launch = nil, addresses = nil)
431
+ begin
432
+ require 'webrick'
433
+ rescue LoadError
434
+ abort "webrick is not found. You may need to `gem install webrick` to install webrick."
435
+ end
436
+
437
+ Gem::RDoc.load_rdoc
438
+ Socket.do_not_reverse_lookup = true
439
+
440
+ @gem_dirs = Array gem_dirs
441
+ @port = port
442
+ @daemon = daemon
443
+ @launch = launch
444
+ @addresses = addresses
445
+
446
+ logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL
447
+ @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger
448
+
449
+ @spec_dirs = @gem_dirs.map {|gem_dir| File.join gem_dir, 'specifications' }
450
+ @spec_dirs.reject! {|spec_dir| !File.directory? spec_dir }
451
+
452
+ reset_gems
453
+
454
+ @have_rdoc_4_plus = nil
455
+ end
456
+
457
+ def add_date(res)
458
+ res['date'] = @spec_dirs.map do |spec_dir|
459
+ File.stat(spec_dir).mtime
460
+ end.max
461
+ end
462
+
463
+ def uri_encode(str)
464
+ str.gsub(URI::UNSAFE) do |match|
465
+ match.each_byte.map {|c| sprintf('%%%02X', c.ord) }.join
466
+ end
467
+ end
468
+
469
+ def doc_root(gem_name)
470
+ if have_rdoc_4_plus?
471
+ "/doc_root/#{u gem_name}/"
472
+ else
473
+ "/doc_root/#{u gem_name}/rdoc/index.html"
474
+ end
475
+ end
476
+
477
+ def have_rdoc_4_plus?
478
+ @have_rdoc_4_plus ||=
479
+ Gem::Requirement.new('>= 4.0.0.preview2').satisfied_by? Gem::RDoc.rdoc_version
480
+ end
481
+
482
+ def latest_specs(req, res)
483
+ reset_gems
484
+
485
+ res['content-type'] = 'application/x-gzip'
486
+
487
+ add_date res
488
+
489
+ latest_specs = Gem::Specification.latest_specs
490
+
491
+ specs = latest_specs.sort.map do |spec|
492
+ platform = spec.original_platform || Gem::Platform::RUBY
493
+ [spec.name, spec.version, platform]
494
+ end
495
+
496
+ specs = Marshal.dump specs
497
+
498
+ if req.path =~ /\.gz$/
499
+ specs = Gem::Util.gzip specs
500
+ res['content-type'] = 'application/x-gzip'
501
+ else
502
+ res['content-type'] = 'application/octet-stream'
503
+ end
504
+
505
+ if req.request_method == 'HEAD'
506
+ res['content-length'] = specs.length
507
+ else
508
+ res.body << specs
509
+ end
510
+ end
511
+
512
+ ##
513
+ # Creates server sockets based on the addresses option. If no addresses
514
+ # were given a server socket for all interfaces is created.
515
+
516
+ def listen(addresses = @addresses)
517
+ addresses = [nil] unless addresses
518
+
519
+ listeners = 0
520
+
521
+ addresses.each do |address|
522
+ begin
523
+ @server.listen address, @port
524
+ @server.listeners[listeners..-1].each do |listener|
525
+ host, port = listener.addr.values_at 2, 1
526
+ host = "[#{host}]" if host =~ /:/ # we don't reverse lookup
527
+ say "Server started at http://#{host}:#{port}"
528
+ end
529
+
530
+ listeners = @server.listeners.length
531
+ rescue SystemCallError
532
+ next
533
+ end
534
+ end
535
+
536
+ if @server.listeners.empty?
537
+ say "Unable to start a server."
538
+ say "Check for running servers or your --bind and --port arguments"
539
+ terminate_interaction 1
540
+ end
541
+ end
542
+
543
+ def prerelease_specs(req, res)
544
+ reset_gems
545
+
546
+ res['content-type'] = 'application/x-gzip'
547
+
548
+ add_date res
549
+
550
+ specs = Gem::Specification.select do |spec|
551
+ spec.version.prerelease?
552
+ end.sort.map do |spec|
553
+ platform = spec.original_platform || Gem::Platform::RUBY
554
+ [spec.name, spec.version, platform]
555
+ end
556
+
557
+ specs = Marshal.dump specs
558
+
559
+ if req.path =~ /\.gz$/
560
+ specs = Gem::Util.gzip specs
561
+ res['content-type'] = 'application/x-gzip'
562
+ else
563
+ res['content-type'] = 'application/octet-stream'
564
+ end
565
+
566
+ if req.request_method == 'HEAD'
567
+ res['content-length'] = specs.length
568
+ else
569
+ res.body << specs
570
+ end
571
+ end
572
+
573
+ def quick(req, res)
574
+ reset_gems
575
+
576
+ res['content-type'] = 'text/plain'
577
+ add_date res
578
+
579
+ case req.request_uri.path
580
+ when %r{^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)\.gemspec\.rz$} then
581
+ marshal_format, full_name = $1, $2
582
+ specs = Gem::Specification.find_all_by_full_name(full_name)
583
+
584
+ selector = full_name.inspect
585
+
586
+ if specs.empty?
587
+ res.status = 404
588
+ res.body = "No gems found matching #{selector}"
589
+ elsif specs.length > 1
590
+ res.status = 500
591
+ res.body = "Multiple gems found matching #{selector}"
592
+ elsif marshal_format
593
+ res['content-type'] = 'application/x-deflate'
594
+ res.body << Gem.deflate(Marshal.dump(specs.first))
595
+ end
596
+ else
597
+ raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found."
598
+ end
599
+ end
600
+
601
+ def root(req, res)
602
+ reset_gems
603
+
604
+ add_date res
605
+
606
+ raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless
607
+ req.path == '/'
608
+
609
+ specs = []
610
+ total_file_count = 0
611
+
612
+ Gem::Specification.each do |spec|
613
+ total_file_count += spec.files.size
614
+ deps = spec.dependencies.map do |dep|
615
+ {
616
+ "name" => dep.name,
617
+ "type" => dep.type,
618
+ "version" => dep.requirement.to_s,
619
+ }
620
+ end
621
+
622
+ deps = deps.sort_by {|dep| [dep["name"].downcase, dep["version"]] }
623
+ deps.last["is_last"] = true unless deps.empty?
624
+
625
+ # executables
626
+ executables = spec.executables.sort.collect {|exec| {"executable" => exec} }
627
+ executables = nil if executables.empty?
628
+ executables.last["is_last"] = true if executables
629
+
630
+ # Pre-process spec homepage for safety reasons
631
+ begin
632
+ homepage_uri = URI.parse(spec.homepage)
633
+ if [URI::HTTP, URI::HTTPS].member? homepage_uri.class
634
+ homepage_uri = spec.homepage
635
+ else
636
+ homepage_uri = "."
637
+ end
638
+ rescue URI::InvalidURIError
639
+ homepage_uri = "."
640
+ end
641
+
642
+ specs << {
643
+ "authors" => spec.authors.sort.join(", "),
644
+ "date" => spec.date.to_s,
645
+ "dependencies" => deps,
646
+ "doc_path" => doc_root(spec.full_name),
647
+ "executables" => executables,
648
+ "only_one_executable" => (executables && executables.size == 1),
649
+ "full_name" => spec.full_name,
650
+ "has_deps" => !deps.empty?,
651
+ "homepage" => homepage_uri,
652
+ "name" => spec.name,
653
+ "rdoc_installed" => Gem::RDoc.new(spec).rdoc_installed?,
654
+ "ri_installed" => Gem::RDoc.new(spec).ri_installed?,
655
+ "summary" => spec.summary,
656
+ "version" => spec.version.to_s,
657
+ }
658
+ end
659
+
660
+ specs << {
661
+ "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others",
662
+ "dependencies" => [],
663
+ "doc_path" => doc_root("rubygems-#{Gem::VERSION}"),
664
+ "executables" => [{"executable" => 'gem', "is_last" => true}],
665
+ "only_one_executable" => true,
666
+ "full_name" => "rubygems-#{Gem::VERSION}",
667
+ "has_deps" => false,
668
+ "homepage" => "https://guides.rubygems.org/",
669
+ "name" => 'rubygems',
670
+ "ri_installed" => true,
671
+ "summary" => "RubyGems itself",
672
+ "version" => Gem::VERSION,
673
+ }
674
+
675
+ specs = specs.sort_by {|spec| [spec["name"].downcase, spec["version"]] }
676
+ specs.last["is_last"] = true
677
+
678
+ # tag all specs with first_name_entry
679
+ last_spec = nil
680
+ specs.each do |spec|
681
+ is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase)
682
+ spec["first_name_entry"] = is_first
683
+ last_spec = spec
684
+ end
685
+
686
+ # create page from template
687
+ template = ERB.new(DOC_TEMPLATE)
688
+ res['content-type'] = 'text/html'
689
+
690
+ values = { "gem_count" => specs.size.to_s, "specs" => specs,
691
+ "total_file_count" => total_file_count.to_s }
692
+
693
+ # suppress 1.9.3dev warning about unused variable
694
+ values = values
695
+
696
+ result = template.result binding
697
+ res.body = result
698
+ end
699
+
700
+ ##
701
+ # Can be used for quick navigation to the rdoc documentation. You can then
702
+ # define a search shortcut for your browser. E.g. in Firefox connect
703
+ # 'shortcut:rdoc' to http://localhost:8808/rdoc?q=%s template. Then you can
704
+ # directly open the ActionPack documentation by typing 'rdoc actionp'. If
705
+ # there are multiple hits for the search term, they are presented as a list
706
+ # with links.
707
+ #
708
+ # Search algorithm aims for an intuitive search:
709
+ # 1. first try to find the gems and documentation folders which name
710
+ # starts with the search term
711
+ # 2. search for entries, that *contain* the search term
712
+ # 3. show all the gems
713
+ #
714
+ # If there is only one search hit, user is immediately redirected to the
715
+ # documentation for the particular gem, otherwise a list with results is
716
+ # shown.
717
+ #
718
+ # === Additional trick - install documentation for Ruby core
719
+ #
720
+ # Note: please adjust paths accordingly use for example 'locate yaml.rb' and
721
+ # 'gem environment' to identify directories, that are specific for your
722
+ # local installation
723
+ #
724
+ # 1. install Ruby sources
725
+ # cd /usr/src
726
+ # sudo apt-get source ruby
727
+ #
728
+ # 2. generate documentation
729
+ # rdoc -o /usr/lib/ruby/gems/1.8/doc/core/rdoc \
730
+ # /usr/lib/ruby/1.8 ruby1.8-1.8.7.72
731
+ #
732
+ # By typing 'rdoc core' you can now access the core documentation
733
+
734
+ def rdoc(req, res)
735
+ query = req.query['q']
736
+ show_rdoc_for_pattern("#{query}*", res) && return
737
+ show_rdoc_for_pattern("*#{query}*", res) && return
738
+
739
+ template = ERB.new RDOC_NO_DOCUMENTATION
740
+
741
+ res['content-type'] = 'text/html'
742
+ res.body = template.result binding
743
+ end
744
+
745
+ ##
746
+ # Updates the server to use the latest installed gems.
747
+
748
+ def reset_gems # :nodoc:
749
+ Gem::Specification.dirs = @gem_dirs
750
+ end
751
+
752
+ ##
753
+ # Returns true and prepares http response, if rdoc for the requested gem
754
+ # name pattern was found.
755
+ #
756
+ # The search is based on the file system content, not on the gems metadata.
757
+ # This allows additional documentation folders like 'core' for the Ruby core
758
+ # documentation - just put it underneath the main doc folder.
759
+
760
+ def show_rdoc_for_pattern(pattern, res)
761
+ found_gems = Dir.glob("{#{@gem_dirs.join ','}}/doc/#{pattern}").select do |path|
762
+ File.exist? File.join(path, 'rdoc/index.html')
763
+ end
764
+ case found_gems.length
765
+ when 0
766
+ return false
767
+ when 1
768
+ new_path = File.basename(found_gems[0])
769
+ res.status = 302
770
+ res['Location'] = doc_root new_path
771
+ return true
772
+ else
773
+ doc_items = []
774
+ found_gems.each do |file_name|
775
+ base_name = File.basename(file_name)
776
+ doc_items << {
777
+ :name => base_name,
778
+ :url => doc_root(new_path),
779
+ :summary => '',
780
+ }
781
+ end
782
+
783
+ template = ERB.new(RDOC_SEARCH_TEMPLATE)
784
+ res['content-type'] = 'text/html'
785
+ result = template.result binding
786
+ res.body = result
787
+ return true
788
+ end
789
+ end
790
+
791
+ def run
792
+ listen
793
+
794
+ WEBrick::Daemon.start if @daemon
795
+
796
+ @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs)
797
+ @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs)
798
+
799
+ @server.mount_proc "/latest_specs.#{Gem.marshal_version}",
800
+ method(:latest_specs)
801
+ @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz",
802
+ method(:latest_specs)
803
+
804
+ @server.mount_proc "/prerelease_specs.#{Gem.marshal_version}",
805
+ method(:prerelease_specs)
806
+ @server.mount_proc "/prerelease_specs.#{Gem.marshal_version}.gz",
807
+ method(:prerelease_specs)
808
+
809
+ @server.mount_proc "/quick/", method(:quick)
810
+
811
+ @server.mount_proc("/gem-server-rdoc-style.css") do |req, res|
812
+ res['content-type'] = 'text/css'
813
+ add_date res
814
+ res.body << RDOC_CSS
815
+ end
816
+
817
+ @server.mount_proc "/", method(:root)
818
+
819
+ @server.mount_proc "/rdoc", method(:rdoc)
820
+
821
+ file_handlers = {
822
+ '/gems' => '/cache/',
823
+ }
824
+
825
+ if have_rdoc_4_plus?
826
+ @server.mount '/doc_root', RDoc::Servlet, '/doc_root'
827
+ else
828
+ file_handlers['/doc_root'] = '/doc/'
829
+ end
830
+
831
+ @gem_dirs.each do |gem_dir|
832
+ file_handlers.each do |mount_point, mount_dir|
833
+ @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler,
834
+ File.join(gem_dir, mount_dir), true)
835
+ end
836
+ end
837
+
838
+ trap("INT") { @server.shutdown; exit! }
839
+ trap("TERM") { @server.shutdown; exit! }
840
+
841
+ launch if @launch
842
+
843
+ @server.start
844
+ end
845
+
846
+ def specs(req, res)
847
+ reset_gems
848
+
849
+ add_date res
850
+
851
+ specs = Gem::Specification.sort_by(&:sort_obj).map do |spec|
852
+ platform = spec.original_platform || Gem::Platform::RUBY
853
+ [spec.name, spec.version, platform]
854
+ end
855
+
856
+ specs = Marshal.dump specs
857
+
858
+ if req.path =~ /\.gz$/
859
+ specs = Gem::Util.gzip specs
860
+ res['content-type'] = 'application/x-gzip'
861
+ else
862
+ res['content-type'] = 'application/octet-stream'
863
+ end
864
+
865
+ if req.request_method == 'HEAD'
866
+ res['content-length'] = specs.length
867
+ else
868
+ res.body << specs
869
+ end
870
+ end
871
+
872
+ def launch
873
+ listeners = @server.listeners.map{|l| l.addr[2] }
874
+
875
+ # TODO: 0.0.0.0 == any, not localhost.
876
+ host = listeners.any?{|l| l == '0.0.0.0' } ? 'localhost' : listeners.first
877
+
878
+ say "Launching browser to http://#{host}:#{@port}"
879
+
880
+ system("#{@launch} http://#{host}:#{@port}")
881
+ end
882
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems/command_manager'
2
+ require_relative 'rubygems/commands/server_command'
3
+
4
+ Gem::CommandManager.instance.register_command :server
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/rubygems/server/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "rubygems-server"
7
+ spec.version = Rubygems::Server::VERSION
8
+ spec.authors = ["Hiroshi SHIBATA"]
9
+ spec.email = ["hsbt@ruby-lang.org"]
10
+
11
+ spec.summary = "Gem::Server and allows users to serve gems for consumption by `gem --remote-install`."
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/rubygems/rubygems-server"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
25
+ end
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency "webrick"
32
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubygems-server
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Hiroshi SHIBATA
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-10-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: webrick
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Gem::Server and allows users to serve gems for consumption by `gem --remote-install`.
28
+ email:
29
+ - hsbt@ruby-lang.org
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - Gemfile
35
+ - LICENSE.txt
36
+ - README.md
37
+ - Rakefile
38
+ - bin/console
39
+ - bin/setup
40
+ - lib/rubygems/commands/server_command.rb
41
+ - lib/rubygems/server.rb
42
+ - lib/rubygems/server/version.rb
43
+ - lib/rubygems_plugin.rb
44
+ - rubygems-server.gemspec
45
+ homepage: https://github.com/rubygems/rubygems-server
46
+ licenses:
47
+ - MIT
48
+ metadata:
49
+ homepage_uri: https://github.com/rubygems/rubygems-server
50
+ source_code_uri: https://github.com/rubygems/rubygems-server
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 2.6.0
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubygems_version: 3.3.0.dev
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: Gem::Server and allows users to serve gems for consumption by `gem --remote-install`.
70
+ test_files: []