| 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