lib/rdoc/servlet.rb in rdoc-4.0.0.preview2 vs lib/rdoc/servlet.rb in rdoc-4.0.0.preview2.1
- old
+ new
@@ -1,24 +1,63 @@
require 'rdoc'
require 'time'
require 'webrick'
+##
+# This is a WEBrick servlet that allows you to browse ri documentation.
+#
+# You can show documentation through either `ri --server` or, with RubyGems
+# 2.0 or newer, `gem server`. For ri, the server runs on port 8214 by
+# default. For RubyGems the server runs on port 8808 by default.
+#
+# You can use this servlet in your own project by mounting it on a WEBrick
+# server:
+#
+# require 'webrick'
+#
+# server = WEBrick::HTTPServer.new Port: 8000
+#
+# server.mount '/', RDoc::Servlet
+#
+# If you want to mount the servlet some other place than the root, provide the
+# base path when mounting:
+#
+# server.mount '/rdoc', RDoc::Servlet, '/rdoc'
+
class RDoc::Servlet < WEBrick::HTTPServlet::AbstractServlet
@server_stores = Hash.new { |hash, server| hash[server] = {} }
@cache = Hash.new { |hash, store| hash[store] = {} }
+ ##
+ # Maps an asset type to its path on the filesystem
+
attr_reader :asset_dirs
+ ##
+ # An RDoc::Options instance used for rendering options
+
attr_reader :options
- def self.get_instance server, *options
+ ##
+ # Creates an instance of this servlet that shares cached data between
+ # requests.
+
+ def self.get_instance server, *options # :nodoc:
stores = @server_stores[server]
new server, stores, @cache, *options
end
+ ##
+ # Creates a new WEBrick servlet.
+ #
+ # Use +mount_path+ when mounting the servlet somewhere other than /.
+ #
+ # +server+ is provided automatically by WEBrick when mounting. +stores+ and
+ # +cache+ are provided automatically by the servlet.
+
def initialize server, stores, cache, mount_path = nil
super server
@cache = cache
@mount_path = mount_path
@@ -42,10 +81,13 @@
:json_index =>
File.expand_path('../generator/template/json_index/', __FILE__),
}
end
+ ##
+ # Serves the asset at the path in +req+ for +generator_name+ via +res+.
+
def asset generator_name, req, res
asset_dir = @asset_dirs[generator_name]
asset_path = File.join asset_dir, req.path
@@ -58,10 +100,13 @@
when /js$/ then 'application/javascript'
else 'application/octet-stream'
end
end
+ ##
+ # GET request entry point. Fills in +res+ for the path, etc. in +req+.
+
def do_GET req, res
req.path.sub!(/^#{Regexp.escape @mount_path}/o, '') if @mount_path
case req.path
when '/' then
@@ -80,10 +125,17 @@
raise
rescue => e
error e, req, res
end
+ ##
+ # Fills in +res+ with the class, module or page for +req+ from +store+.
+ #
+ # +path+ is relative to the mount_path and is used to determine the class,
+ # module or page name (/RDoc/Servlet.html becomes RDoc::Servlet).
+ # +generator+ is used to create the page.
+
def documentation_page store, generator, path, req, res
name = path.sub(/.html$/, '').gsub '/', '::'
if klass = store.find_class_or_module(name) then
res.body = generator.generate_class klass
@@ -92,20 +144,28 @@
else
not_found generator, req, res
end
end
+ ##
+ # Creates the JSON search index on +res+ for the given +store+. +generator+
+ # must respond to \#json_index to build. +req+ is ignored.
+
def documentation_search store, generator, req, res
json_index = @cache[store].fetch :json_index do
@cache[store][:json_index] =
JSON.dump generator.json_index.build_index
end
res.content_type = 'application/javascript'
res.body = "var search_data = #{json_index}"
end
+ ##
+ # Returns the RDoc::Store and path relative to +mount_path+ for
+ # documentation at +path+.
+
def documentation_source path
_, source_name, path = path.split '/', 3
store = @stores[source_name]
return store, path if store
@@ -117,43 +177,56 @@
@stores[source_name] = store
return store, path
end
- def error e, req, res
- backtrace = e.backtrace.join "\n"
+ ##
+ # Generates an error page for the +exception+ while handling +req+ on +res+.
+ def error exception, req, res
+ backtrace = exception.backtrace.join "\n"
+
res.content_type = 'text/html'
res.status = 500
res.body = <<-BODY
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
-<title>Error - #{ERB::Util.html_escape e.class}</title>
+<title>Error - #{ERB::Util.html_escape exception.class}</title>
<link type="text/css" media="screen" href="#{@mount_path}/rdoc.css" rel="stylesheet">
</head>
<body>
<h1>Error</h1>
<p>While processing <code>#{ERB::Util.html_escape req.request_uri}</code> the
-RDoc server has encountered a <code>#{ERB::Util.html_escape e.class}</code>
+RDoc (#{ERB::Util.html_escape RDoc::VERSION}) server has encountered a
+<code>#{ERB::Util.html_escape exception.class}</code>
exception:
-<pre>#{ERB::Util.html_escape e.message}</pre>
+<pre>#{ERB::Util.html_escape exception.message}</pre>
+<p>Please report this to the
+<a href="https://github.com/rdoc/rdoc/issues">RDoc issues tracker</a>. Please
+include the RDoc version, the URI above and exception class, message and
+backtrace. If you're viewing a gem's documentation, include the gem name and
+version. If you're viewing Ruby's documentation, include the version of ruby.
+
<p>Backtrace:
<pre>#{ERB::Util.html_escape backtrace}</pre>
</body>
</html>
BODY
end
+ ##
+ # Instantiates a Darkfish generator for +store+
+
def generator_for store
generator = RDoc::Generator::Darkfish.new store, @options
generator.file_output = false
generator.asset_rel_path = '..'
@@ -166,10 +239,15 @@
@options.title = store.title
generator
end
+ ##
+ # Handles the If-Modified-Since HTTP header on +req+ for +path+. If the
+ # file has not been modified a Not Modified response is returned. If the
+ # file has been modified a Last-Modified header is added to +res+.
+
def if_modified_since req, res, path = nil
last_modified = File.stat(path).mtime if path
res['last-modified'] = last_modified.httpdate
@@ -181,10 +259,18 @@
res.body = ''
raise WEBrick::HTTPStatus::NotModified
end
end
+ ##
+ # Returns an Array of installed documentation.
+ #
+ # Each entry contains the documentation name (gem name, 'Ruby
+ # Documentation', etc.), the path relative to the mount point, whether the
+ # documentation exists, the type of documentation (See RDoc::RI::Paths#each)
+ # and the filesystem to the RDoc::Store for the documentation.
+
def installed_docs
ri_paths.map do |path, type|
store = RDoc::Store.new path, type
exists = File.exist? store.cache_path
@@ -200,27 +286,39 @@
['Home Documentation', 'home/', exists, type, path]
end
end
end
+ ##
+ # Returns a 404 page built by +generator+ for +req+ on +res+.
+
def not_found generator, req, res
res.body = generator.generate_servlet_not_found req.path
res.status = 404
end
+ ##
+ # Enumerates the ri paths. See RDoc::RI::Paths#each
+
def ri_paths &block
RDoc::RI::Paths.each true, true, true, :all, &block
end
+ ##
+ # Generates the root page on +res+. +req+ is ignored.
+
def root req, res
generator = RDoc::Generator::Darkfish.new nil, @options
res.body = generator.generate_servlet_root installed_docs
res.content_type = 'text/html'
end
+ ##
+ # Generates a search index for the root page on +res+. +req+ is ignored.
+
def root_search req, res
search_index = []
info = []
installed_docs.map do |name, href, exists, type, path|
@@ -257,10 +355,14 @@
res.body = "var search_data = #{JSON.dump index};"
res.content_type = 'application/javascript'
end
+ ##
+ # Displays documentation for +req+ on +res+, whether that be HTML or some
+ # asset.
+
def show_documentation req, res
store, path = documentation_source req.path
if_modified_since req, res, store.cache_path
@@ -277,9 +379,12 @@
documentation_page store, generator, path, req, res
end
ensure
res.content_type ||= 'text/html'
end
+
+ ##
+ # Returns an RDoc::Store for the given +source_name+ ('ruby' or a gem name).
def store_for source_name
case source_name
when 'ruby' then
RDoc::Store.new RDoc::RI::Paths.system_dir, :system