rubygems-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []