Class | Gem::Server |
In: |
lib/rubygems/server.rb
|
Parent: | Object |
Gem::Server and allows users to serve gems for consumption by `gem —remote-install`.
gem_server starts an HTTP server on the given port and serves the following:
gem_server = Gem::Server.new Gem.dir, 8089, false gem_server.run
SEARCH | = | <<-SEARCH <form class="headerSearch" name="headerSearchForm" method="get" action="/rdoc"> <div id="search" style="float:right"> <span>Filter/Search</span> <input id="q" type="text" style="width:10em" name="q"/> <button type="submit" style="display:none" /> </div> </form> SEARCH | ||
DOC_TEMPLATE | = | <<-'DOC_TEMPLATE' <?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>RubyGems Documentation Index</title> <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" /> </head> <body> <div id="fileHeader"> <%= SEARCH %> <h1>RubyGems Documentation Index</h1> </div> <!-- banner header --> <div id="bodyContent"> <div id="contextContent"> <div id="description"> <h1>Summary</h1> <p>There are <%=values["gem_count"]%> gems installed:</p> <p> <%= values["specs"].map { |v| "<a href=\"##{v["name"]}\">#{v["name"]}</a>" }.join ', ' %>. <h1>Gems</h1> <dl> <% values["specs"].each do |spec| %> <dt> <% if spec["first_name_entry"] then %> <a name="<%=spec["name"]%>"></a> <% end %> <b><%=spec["name"]%> <%=spec["version"]%></b> <% if spec["rdoc_installed"] then %> <a href="<%=spec["doc_path"]%>">[rdoc]</a> <% else %> <span title="rdoc not installed">[rdoc]</span> <% end %> <% if spec["homepage"] then %> <a href="<%=spec["homepage"]%>" title="<%=spec["homepage"]%>">[www]</a> <% else %> <span title="no homepage available">[www]</span> <% end %> <% if spec["has_deps"] then %> - depends on <%= spec["dependencies"].map { |v| "<a href=\"##{v["name"]}\">#{v["name"]}</a>" }.join ', ' %>. <% end %> </dt> <dd> <%=spec["summary"]%> <% if spec["executables"] then %> <br/> <% if spec["only_one_executable"] then %> Executable is <% else %> Executables are <%end%> <%= spec["executables"].map { |v| "<span class=\"context-item-name\">#{v["executable"]}</span>"}.join ', ' %>. <%end%> <br/> <br/> </dd> <% end %> </dl> </div> </div> </div> <div id="validator-badges"> <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p> </div> </body> </html> DOC_TEMPLATE | ||
RDOC_CSS | = | <<-RDOC_CSS body { font-family: Verdana,Arial,Helvetica,sans-serif; font-size: 90%; margin: 0; margin-left: 40px; padding: 0; background: white; } h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; } h1 { font-size: 150%; } h2,h3,h4 { margin-top: 1em; } a { background: #eef; color: #039; text-decoration: none; } a:hover { background: #039; color: #eef; } /* Override the base stylesheets Anchor inside a table cell */ td > a { background: transparent; color: #039; text-decoration: none; } /* and inside a section title */ .section-title > a { background: transparent; color: #eee; text-decoration: none; } /* === Structural elements =================================== */ div#index { margin: 0; margin-left: -40px; padding: 0; font-size: 90%; } div#index a { margin-left: 0.7em; } div#index .section-bar { margin-left: 0px; padding-left: 0.7em; background: #ccc; font-size: small; } div#classHeader, div#fileHeader { width: auto; color: white; padding: 0.5em 1.5em 0.5em 1.5em; margin: 0; margin-left: -40px; border-bottom: 3px solid #006; } div#classHeader a, div#fileHeader a { background: inherit; color: white; } div#classHeader td, div#fileHeader td { background: inherit; color: white; } div#fileHeader { background: #057; } div#classHeader { background: #048; } .class-name-in-header { font-size: 180%; font-weight: bold; } div#bodyContent { padding: 0 1.5em 0 1.5em; } div#description { padding: 0.5em 1.5em; background: #efefef; border: 1px dotted #999; } div#description h1,h2,h3,h4,h5,h6 { color: #125;; background: transparent; } div#validator-badges { text-align: center; } div#validator-badges img { border: 0; } div#copyright { color: #333; background: #efefef; font: 0.75em sans-serif; margin-top: 5em; margin-bottom: 0; padding: 0.5em 2em; } /* === Classes =================================== */ table.header-table { color: white; font-size: small; } .type-note { font-size: small; color: #DEDEDE; } .xxsection-bar { background: #eee; color: #333; padding: 3px; } .section-bar { color: #333; border-bottom: 1px solid #999; margin-left: -20px; } .section-title { background: #79a; color: #eee; padding: 3px; margin-top: 2em; margin-left: -30px; border: 1px solid #999; } .top-aligned-row { vertical-align: top } .bottom-aligned-row { vertical-align: bottom } /* --- Context section classes ----------------------- */ .context-row { } .context-item-name { font-family: monospace; font-weight: bold; color: black; } .context-item-value { font-size: small; color: #448; } .context-item-desc { color: #333; padding-left: 2em; } /* --- Method classes -------------------------- */ .method-detail { background: #efefef; padding: 0; margin-top: 0.5em; margin-bottom: 1em; border: 1px dotted #ccc; } .method-heading { color: black; background: #ccc; border-bottom: 1px solid #666; padding: 0.2em 0.5em 0 0.5em; } .method-signature { color: black; background: inherit; } .method-name { font-weight: bold; } .method-args { font-style: italic; } .method-description { padding: 0 0.5em 0 0.5em; } /* --- Source code sections -------------------- */ a.source-toggle { font-size: 90%; } div.method-source-code { background: #262626; color: #ffdead; margin: 1em; padding: 0.5em; border: 1px dashed #999; overflow: hidden; } div.method-source-code pre { color: #ffdead; overflow: hidden; } /* --- Ruby keyword styles --------------------- */ .standalone-code { background: #221111; color: #ffdead; overflow: hidden; } .ruby-constant { color: #7fffd4; background: transparent; } .ruby-keyword { color: #00ffff; background: transparent; } .ruby-ivar { color: #eedd82; background: transparent; } .ruby-operator { color: #00ffee; background: transparent; } .ruby-identifier { color: #ffdead; background: transparent; } .ruby-node { color: #ffa07a; background: transparent; } .ruby-comment { color: #b22222; font-weight: bold; background: transparent; } .ruby-regexp { color: #ffa07a; background: transparent; } .ruby-value { color: #7fffd4; background: transparent; } RDOC_CSS | CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108 | |
RDOC_NO_DOCUMENTATION | = | <<-'NO_DOC' <?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Found documentation</title> <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" /> </head> <body> <div id="fileHeader"> <%= SEARCH %> <h1>No documentation found</h1> </div> <div id="bodyContent"> <div id="contextContent"> <div id="description"> <p>No gems matched <%= h query.inspect %></p> <p> Back to <a href="/">complete gem index</a> </p> </div> </div> </div> <div id="validator-badges"> <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p> </div> </body> </html> NO_DOC | ||
RDOC_SEARCH_TEMPLATE | = | <<-'RDOC_SEARCH' <?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Found documentation</title> <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" /> </head> <body> <div id="fileHeader"> <%= SEARCH %> <h1>Found documentation</h1> </div> <!-- banner header --> <div id="bodyContent"> <div id="contextContent"> <div id="description"> <h1>Summary</h1> <p><%=doc_items.length%> documentation topics found.</p> <h1>Topics</h1> <dl> <% doc_items.each do |doc_item| %> <dt> <b><%=doc_item[:name]%></b> <a href="<%=doc_item[:url]%>">[rdoc]</a> </dt> <dd> <%=doc_item[:summary]%> <br/> <br/> </dd> <% end %> </dl> <p> Back to <a href="/">complete gem index</a> </p> </div> </div> </div> <div id="validator-badges"> <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p> </div> </body> </html> RDOC_SEARCH |
# File lib/rubygems/server.rb, line 437 437: def initialize(gem_dir, port, daemon) 438: Socket.do_not_reverse_lookup = true 439: 440: @gem_dir = gem_dir 441: @port = port 442: @daemon = daemon 443: logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL 444: @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger 445: 446: @spec_dir = File.join @gem_dir, 'specifications' 447: 448: unless File.directory? @spec_dir then 449: raise ArgumentError, "#{@gem_dir} does not appear to be a gem repository" 450: end 451: 452: @source_index = Gem::SourceIndex.from_gems_in @spec_dir 453: end
# File lib/rubygems/server.rb, line 433 433: def self.run(options) 434: new(options[:gemdir], options[:port], options[:daemon]).run 435: end
# File lib/rubygems/server.rb, line 455 455: def Marshal(req, res) 456: @source_index.refresh! 457: 458: res['date'] = File.stat(@spec_dir).mtime 459: 460: index = Marshal.dump @source_index 461: 462: if req.request_method == 'HEAD' then 463: res['content-length'] = index.length 464: return 465: end 466: 467: if req.path =~ /Z$/ then 468: res['content-type'] = 'application/x-deflate' 469: index = Gem.deflate index 470: else 471: res['content-type'] = 'application/octet-stream' 472: end 473: 474: res.body << index 475: end
# File lib/rubygems/server.rb, line 477 477: def latest_specs(req, res) 478: @source_index.refresh! 479: 480: res['content-type'] = 'application/x-gzip' 481: 482: res['date'] = File.stat(@spec_dir).mtime 483: 484: specs = @source_index.latest_specs.sort.map do |spec| 485: platform = spec.original_platform 486: platform = Gem::Platform::RUBY if platform.nil? 487: [spec.name, spec.version, platform] 488: end 489: 490: specs = Marshal.dump specs 491: 492: if req.path =~ /\.gz$/ then 493: specs = Gem.gzip specs 494: res['content-type'] = 'application/x-gzip' 495: else 496: res['content-type'] = 'application/octet-stream' 497: end 498: 499: if req.request_method == 'HEAD' then 500: res['content-length'] = specs.length 501: else 502: res.body << specs 503: end 504: end
# File lib/rubygems/server.rb, line 506 506: def quick(req, res) 507: @source_index.refresh! 508: 509: res['content-type'] = 'text/plain' 510: res['date'] = File.stat(@spec_dir).mtime 511: 512: case req.request_uri.path 513: when '/quick/index' then 514: res.body << @source_index.map { |name,| name }.sort.join("\n") 515: when '/quick/index.rz' then 516: index = @source_index.map { |name,| name }.sort.join("\n") 517: res['content-type'] = 'application/x-deflate' 518: res.body << Gem.deflate(index) 519: when '/quick/latest_index' then 520: index = @source_index.latest_specs.map { |spec| spec.full_name } 521: res.body << index.sort.join("\n") 522: when '/quick/latest_index.rz' then 523: index = @source_index.latest_specs.map { |spec| spec.full_name } 524: res['content-type'] = 'application/x-deflate' 525: res.body << Gem.deflate(index.sort.join("\n")) 526: when %r|^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz$| then 527: dep = Gem::Dependency.new $2, $3 528: specs = @source_index.search dep 529: marshal_format = $1 530: 531: selector = [$2, $3, $4].map { |s| s.inspect }.join ' ' 532: 533: platform = if $4 then 534: Gem::Platform.new $4.sub(/^-/, '') 535: else 536: Gem::Platform::RUBY 537: end 538: 539: specs = specs.select { |s| s.platform == platform } 540: 541: if specs.empty? then 542: res.status = 404 543: res.body = "No gems found matching #{selector}" 544: elsif specs.length > 1 then 545: res.status = 500 546: res.body = "Multiple gems found matching #{selector}" 547: elsif marshal_format then 548: res['content-type'] = 'application/x-deflate' 549: res.body << Gem.deflate(Marshal.dump(specs.first)) 550: else # deprecated YAML format 551: res['content-type'] = 'application/x-deflate' 552: res.body << Gem.deflate(specs.first.to_yaml) 553: end 554: else 555: raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." 556: end 557: end
Can be used for quick navigation to the rdoc documentation. You can then define a search shortcut for your browser. E.g. in Firefox connect ‘shortcut:rdoc’ to localhost:8808/rdoc?q=%s template. Then you can directly open the ActionPack documentation by typing ‘rdoc actionp’. If there are multiple hits for the search term, they are presented as a list with links.
Search algorithm aims for an intuitive search:
If there is only one search hit, user is immediately redirected to the documentation for the particular gem, otherwise a list with results is shown.
Note: please adjust paths accordingly use for example ‘locate yaml.rb’ and ‘gem environment’ to identify directories, that are specific for your local installation
cd /usr/src sudo apt-get source ruby
rdoc -o /usr/lib/ruby/gems/1.8/doc/core/rdoc # /usr/lib/ruby/1.8 ruby1.8-1.8.7.72
By typing ‘rdoc core’ you can now access the core documentation
# File lib/rubygems/server.rb, line 673 673: def rdoc(req, res) 674: query = req.query['q'] 675: show_rdoc_for_pattern("#{query}*", res) && return 676: show_rdoc_for_pattern("*#{query}*", res) && return 677: 678: template = ERB.new RDOC_NO_DOCUMENTATION 679: 680: res['content-type'] = 'text/html' 681: res.body = template.result binding 682: end
# File lib/rubygems/server.rb, line 559 559: def root(req, res) 560: @source_index.refresh! 561: res['date'] = File.stat(@spec_dir).mtime 562: 563: raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless 564: req.path == '/' 565: 566: specs = [] 567: total_file_count = 0 568: 569: @source_index.each do |path, spec| 570: total_file_count += spec.files.size 571: deps = spec.dependencies.map do |dep| 572: { "name" => dep.name, 573: "type" => dep.type, 574: "version" => dep.version_requirements.to_s, } 575: end 576: 577: deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] } 578: deps.last["is_last"] = true unless deps.empty? 579: 580: # executables 581: executables = spec.executables.sort.collect { |exec| {"executable" => exec} } 582: executables = nil if executables.empty? 583: executables.last["is_last"] = true if executables 584: 585: specs << { 586: "authors" => spec.authors.sort.join(", "), 587: "date" => spec.date.to_s, 588: "dependencies" => deps, 589: "doc_path" => "/doc_root/#{spec.full_name}/rdoc/index.html", 590: "executables" => executables, 591: "only_one_executable" => (executables && executables.size == 1), 592: "full_name" => spec.full_name, 593: "has_deps" => !deps.empty?, 594: "homepage" => spec.homepage, 595: "name" => spec.name, 596: "rdoc_installed" => Gem::DocManager.new(spec).rdoc_installed?, 597: "summary" => spec.summary, 598: "version" => spec.version.to_s, 599: } 600: end 601: 602: specs << { 603: "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others", 604: "dependencies" => [], 605: "doc_path" => "/doc_root/rubygems-#{Gem::RubyGemsVersion}/rdoc/index.html", 606: "executables" => [{"executable" => 'gem', "is_last" => true}], 607: "only_one_executable" => true, 608: "full_name" => "rubygems-#{Gem::RubyGemsVersion}", 609: "has_deps" => false, 610: "homepage" => "http://rubygems.org/", 611: "name" => 'rubygems', 612: "rdoc_installed" => true, 613: "summary" => "RubyGems itself", 614: "version" => Gem::RubyGemsVersion, 615: } 616: 617: specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] } 618: specs.last["is_last"] = true 619: 620: # tag all specs with first_name_entry 621: last_spec = nil 622: specs.each do |spec| 623: is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase) 624: spec["first_name_entry"] = is_first 625: last_spec = spec 626: end 627: 628: # create page from template 629: template = ERB.new(DOC_TEMPLATE) 630: res['content-type'] = 'text/html' 631: 632: values = { "gem_count" => specs.size.to_s, "specs" => specs, 633: "total_file_count" => total_file_count.to_s } 634: 635: result = template.result binding 636: res.body = result 637: end
# File lib/rubygems/server.rb, line 723 723: def run 724: @server.listen nil, @port 725: 726: say "Starting gem server on http://localhost:#{@port}/" 727: 728: WEBrick::Daemon.start if @daemon 729: 730: @server.mount_proc "/yaml", method(:yaml) 731: @server.mount_proc "/yaml.Z", method(:yaml) 732: 733: @server.mount_proc "/Marshal.#{Gem.marshal_version}", method(:Marshal) 734: @server.mount_proc "/Marshal.#{Gem.marshal_version}.Z", method(:Marshal) 735: 736: @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs) 737: @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs) 738: 739: @server.mount_proc "/latest_specs.#{Gem.marshal_version}", 740: method(:latest_specs) 741: @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz", 742: method(:latest_specs) 743: 744: @server.mount_proc "/quick/", method(:quick) 745: 746: @server.mount_proc("/gem-server-rdoc-style.css") do |req, res| 747: res['content-type'] = 'text/css' 748: res['date'] = File.stat(@spec_dir).mtime 749: res.body << RDOC_CSS 750: end 751: 752: @server.mount_proc "/", method(:root) 753: 754: @server.mount_proc "/rdoc", method(:rdoc) 755: 756: paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" } 757: paths.each do |mount_point, mount_dir| 758: @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler, 759: File.join(@gem_dir, mount_dir), true) 760: end 761: 762: trap("INT") { @server.shutdown; exit! } 763: trap("TERM") { @server.shutdown; exit! } 764: 765: @server.start 766: end
Returns true and prepares http response, if rdoc for the requested gem name pattern was found.
The search is based on the file system content, not on the gems metadata. This allows additional documentation folders like ‘core’ for the ruby core documentation - just put it underneath the main doc folder.
# File lib/rubygems/server.rb, line 692 692: def show_rdoc_for_pattern(pattern, res) 693: found_gems = Dir.glob("#{@gem_dir}/doc/#{pattern}").select {|path| 694: File.exist? File.join(path, 'rdoc/index.html') 695: } 696: case found_gems.length 697: when 0 698: return false 699: when 1 700: new_path = File.basename(found_gems[0]) 701: res.status = 302 702: res['Location'] = "/doc_root/#{new_path}/rdoc/index.html" 703: return true 704: else 705: doc_items = [] 706: found_gems.each do |file_name| 707: base_name = File.basename(file_name) 708: doc_items << { 709: :name => base_name, 710: :url => "/doc_root/#{base_name}/rdoc/index.html", 711: :summary => '' 712: } 713: end 714: 715: template = ERB.new(RDOC_SEARCH_TEMPLATE) 716: res['content-type'] = 'text/html' 717: result = template.result binding 718: res.body = result 719: return true 720: end 721: end
# File lib/rubygems/server.rb, line 768 768: def specs(req, res) 769: @source_index.refresh! 770: 771: res['date'] = File.stat(@spec_dir).mtime 772: 773: specs = @source_index.sort.map do |_, spec| 774: platform = spec.original_platform 775: platform = Gem::Platform::RUBY if platform.nil? 776: [spec.name, spec.version, platform] 777: end 778: 779: specs = Marshal.dump specs 780: 781: if req.path =~ /\.gz$/ then 782: specs = Gem.gzip specs 783: res['content-type'] = 'application/x-gzip' 784: else 785: res['content-type'] = 'application/octet-stream' 786: end 787: 788: if req.request_method == 'HEAD' then 789: res['content-length'] = specs.length 790: else 791: res.body << specs 792: end 793: end
# File lib/rubygems/server.rb, line 795 795: def yaml(req, res) 796: @source_index.refresh! 797: 798: res['date'] = File.stat(@spec_dir).mtime 799: 800: index = @source_index.to_yaml 801: 802: if req.path =~ /Z$/ then 803: res['content-type'] = 'application/x-deflate' 804: index = Gem.deflate index 805: else 806: res['content-type'] = 'text/plain' 807: end 808: 809: if req.request_method == 'HEAD' then 810: res['content-length'] = index.length 811: return 812: end 813: 814: res.body << index 815: end