#!/usr/local/bin/ruby
#
# Gem server that uses Mongrel versus WebBrick
# Adapted the code to use Mongrel from the original gem_server
#
#
# author Dave Bryson
require 'rubygems'
Gem.manage_gems
require 'mongrel'
require 'yaml'
require 'optparse'
require 'rdoc/template'
# Handle request for the yaml file
class YamlHandler < Mongrel::HttpHandler
def initialize(spec_dir)
@spec_dir = spec_dir
end
def process(request, response)
response.start do |head,out|
head["Content-Type"] = "text/plain"
head["data"] = File.stat(spec_dir).mtime
out << Gem::SourceIndex.from_gems_in(@spec_dir).to_yaml
end
end
end
# Handle the embedded CSS
class CssHandler < Mongrel::HttpHandler
def initialize(spec_dir, css)
@spec_dir = spec_dir
@css = css
end
def process(request, response)
response.start do |head,out|
head["Content-Type"] = "text/css"
head["data"] = File.stat(@spec_dir).mtime
out << @css
end
end
end
# Handle the Gems
class GemHandler < Mongrel::HttpHandler
def initialize(spec_dir, template)
@spec_dir = spec_dir
@template = template
end
def process(request, response)
response.start do |head,out|
specs = []
total_file_count = 0
Gem::SourceIndex.from_gems_in(@spec_dir).each do |path, spec|
total_file_count += spec.files.size
deps = spec.dependencies.collect { |dep|
{ "name" => dep.name,
"version" => dep.version_requirements.to_s, }
}
deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] }
deps.last["is_last"] = true unless deps.empty?
# executables
executables = spec.executables.sort.collect { |exec| {"executable" => exec} }
executables = nil if executables.empty?
executables.last["is_last"] = true if executables
specs << {
"authors" => spec.authors.sort.join(", "),
"date" => spec.date.to_s,
"dependencies" => deps,
"doc_path" => ('/doc_root/' + spec.full_name + '/rdoc/index.html'),
"executables" => executables,
"only_one_executable" => (executables && executables.size==1),
"full_name" => spec.full_name,
"has_deps" => !deps.empty?,
"homepage" => spec.homepage,
"name" => spec.name,
"rdoc_installed" => Gem::DocManager.new(spec).rdoc_installed?,
"summary" => spec.summary,
"version" => spec.version.to_s,
}
end
specs << {
"authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others",
"dependencies" => [],
"doc_path" => "/doc_root/rubygems-#{Gem::RubyGemsVersion}/rdoc/index.html",
"executables" => [{"executable" => 'gem', "is_last" => true}],
"only_one_executable" => true,
"full_name" => "rubygems-#{Gem::RubyGemsVersion}",
"has_deps" => false,
"homepage" => "http://rubygems.org/",
"name" => 'rubygems',
"rdoc_installed" => true,
"summary" => "RubyGems itself",
"version" => Gem::RubyGemsVersion,
}
specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] }
specs.last["is_last"] = true
# tag all specs with first_name_entry
last_spec = nil
specs.each do |spec|
is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase)
spec["first_name_entry"] = is_first
last_spec = spec
end
# create page from template
template = TemplatePage.new(@template)
head['content-type'] = 'text/html'
template.write_html_on out,
"gem_count" => specs.size.to_s, "specs" => specs,
"total_file_count" => total_file_count.to_s
end
end
end
class MongrelGemServer
DOC_TEMPLATE = <<-WEBPAGE
RubyGems Documentation Index
Summary
There are %gem_count% gems installed:
START:specs
IFNOT:is_last
%name%,
ENDIF:is_last
IF:is_last
%name%.
ENDIF:is_last
END:specs
Gems
START:specs
-
IF:first_name_entry
ENDIF:first_name_entry
%name% %version%
IF:rdoc_installed
[rdoc]
ENDIF:rdoc_installed
IFNOT:rdoc_installed
[rdoc]
ENDIF:rdoc_installed
IF:homepage
[www]
ENDIF:homepage
IFNOT:homepage
[www]
ENDIF:homepage
IF:has_deps
- depends on
START:dependencies
IFNOT:is_last
%name%,
ENDIF:is_last
IF:is_last
%name%.
ENDIF:is_last
END:dependencies
ENDIF:has_deps
-
%summary%
IF:executables
IF:only_one_executable
Executable is
ENDIF:only_one_executable
IFNOT:only_one_executable
Executables are
ENDIF:only_one_executable
START:executables
IFNOT:is_last
%executable%,
ENDIF:is_last
IF:is_last
%executable%.
ENDIF:is_last
END:executables
ENDIF:executables
END:specs
WEBPAGE
# CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108
RDOC_CSS = <<-RDOCCSS
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; }
RDOCCSS
def self.handle_args(args)
options = {}
options[:port] = 8809
options[:gemdir] = Gem.dir
options[:daemon] = false
opts = OptionParser.new do |opts|
opts.on_tail("--help", "show this message") do
puts opts
exit
end
opts.on('-p', '--port=PORT', "Specify the port to listen on") do |port|
options[:port] = port
end
opts.on('-d', '--dir=GEMDIR',
"Specify the directory from which to serve Gems") do |gemdir|
options[:gemdir] = gemdir
end
opts.on('--daemon', "Run as a daemon") do |daemon|
options[:daemon] = true
end
end
opts.parse! args
options
end
def self.run(args = ARGV)
options = handle_args(args)
p options
new(options[:gemdir], options[:port], options[:daemon]).run
end
def initialize(gemdir, port, daemon)
@gemdir = gemdir
@port = port
@daemon = daemon
end
def run
spec_dir = File.join @gemdir, "specifications"
cache_dir = File.join @gemdir, "cache"
doc_dir = File.join @gemdir, "doc"
do_daemonize = @daemon
config = Mongrel::Configurator.new :host => "0.0.0.0", :port => @port do
daemonize :cwd => Dir.pwd, :log_file => "mongrel_gemserver.log", :pid_file => "mongrel_gemserver.pid" if do_daemonize
listener do
uri "/yaml", :handler => YamlHandler.new(spec_dir)
uri "/rdoc-style.css", :handler => CssHandler.new(spec_dir,RDOC_CSS)
uri "/", :handler => GemHandler.new(spec_dir,DOC_TEMPLATE)
uri "/gems", :handler => Mongrel::DirHandler.new(cache_dir)
uri "/doc_root", :handler => Mongrel::DirHandler.new(doc_dir)
end
trap("INT") { stop }
trap("TERM") { stop; exit! }
run
end
puts "Mongrel Gem Server running on port #{@port}"
config.join
end
end
MongrelGemServer.run