about summary refs log tree commit homepage
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/olddoc.rb21
-rw-r--r--lib/olddoc/changelog.rb28
-rw-r--r--lib/olddoc/gemspec.rb17
-rw-r--r--lib/olddoc/history.rb57
-rw-r--r--lib/olddoc/merge.rb26
-rw-r--r--lib/olddoc/news_atom.rb51
-rw-r--r--lib/olddoc/news_rdoc.rb36
-rw-r--r--lib/olddoc/prepare.rb27
-rw-r--r--lib/olddoc/readme.rb27
-rw-r--r--lib/oldweb.rb303
-rw-r--r--lib/oldweb/_head.rhtml7
-rw-r--r--lib/oldweb/_sidebar_classes.rhtml27
-rw-r--r--lib/oldweb/_sidebar_extends.rhtml13
-rw-r--r--lib/oldweb/_sidebar_includes.rhtml12
-rw-r--r--lib/oldweb/_sidebar_installed.rhtml10
-rw-r--r--lib/oldweb/_sidebar_methods.rhtml6
-rw-r--r--lib/oldweb/_sidebar_navigation.rhtml6
-rw-r--r--lib/oldweb/_sidebar_pages.rhtml17
-rw-r--r--lib/oldweb/_sidebar_parent.rhtml13
-rw-r--r--lib/oldweb/_sidebar_sections.rhtml8
-rw-r--r--lib/oldweb/_sidebar_table_of_contents.rhtml15
-rw-r--r--lib/oldweb/_tail.rhtml25
-rw-r--r--lib/oldweb/class.rhtml79
-rw-r--r--lib/oldweb/page.rhtml5
-rw-r--r--lib/oldweb/servlet_not_found.rhtml5
-rw-r--r--lib/oldweb/servlet_root.rhtml39
-rw-r--r--lib/oldweb/table_of_contents.rhtml52
-rw-r--r--lib/rdoc/discover.rb5
28 files changed, 937 insertions, 0 deletions
diff --git a/lib/olddoc.rb b/lib/olddoc.rb
new file mode 100644
index 0000000..f230cbf
--- /dev/null
+++ b/lib/olddoc.rb
@@ -0,0 +1,21 @@
+# Copyright (C) 2015, all contributors <olddoc-public@80x24.org>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+module Olddoc
+  VERSION = '1.0.0'
+
+  autoload :Changelog, 'olddoc/changelog'
+  autoload :Gemspec, 'olddoc/gemspec'
+  autoload :History, 'olddoc/history'
+  autoload :Merge, 'olddoc/merge'
+  autoload :NewsAtom, 'olddoc/news_atom'
+  autoload :NewsRdoc, 'olddoc/news_rdoc'
+  autoload :Prepare, 'olddoc/prepare'
+  autoload :Readme, 'olddoc/readme'
+
+  def self.config(path = ".olddoc.yml")
+    File.readable?(path) and return YAML.load(File.read(path))
+    warn "#{path} not found in current directory"
+    {}
+  end
+end
+require_relative 'oldweb'
diff --git a/lib/olddoc/changelog.rb b/lib/olddoc/changelog.rb
new file mode 100644
index 0000000..7a7fe96
--- /dev/null
+++ b/lib/olddoc/changelog.rb
@@ -0,0 +1,28 @@
+# helper method for generating the ChangeLog in RDoc format atomically
+require 'tempfile'
+
+module Olddoc::Changelog
+  include Olddoc::History
+
+  def changelog
+    fp = Tempfile.new('ChangeLog', '.')
+    fp.write "ChangeLog from #@cgit_uri"
+    cmd = %w(git log)
+    if @changelog_start && tags[0]
+      range = "#@changelog_start..#{tags[0][:tag]}"
+      fp.write(" (#{range})")
+      cmd << range
+    end
+    fp.write("\n\n")
+    prefix = "   "
+    IO.popen(cmd.join(' ')) do |io|
+      io.each { |line|
+        fp.write prefix
+        fp.write line
+      }
+    end
+    fp.chmod(0666 & ~File.umask)
+    File.rename(fp.path, 'ChangeLog')
+    fp.close!
+  end
+end
diff --git a/lib/olddoc/gemspec.rb b/lib/olddoc/gemspec.rb
new file mode 100644
index 0000000..73382ab
--- /dev/null
+++ b/lib/olddoc/gemspec.rb
@@ -0,0 +1,17 @@
+# helper methods for gemspecs
+module Olddoc::Gemspec
+  include Olddoc::Readme
+
+  def extra_rdoc_files(manifest)
+    File.readlines('.document').map! do |x|
+      x.chomp!
+      if File.directory?(x)
+        manifest.grep(%r{\A#{x}/})
+      elsif File.file?(x)
+        x
+      else
+        nil
+      end
+    end.flatten.compact
+  end
+end
diff --git a/lib/olddoc/history.rb b/lib/olddoc/history.rb
new file mode 100644
index 0000000..b817d2d
--- /dev/null
+++ b/lib/olddoc/history.rb
@@ -0,0 +1,57 @@
+# Copyright (C) 2015, all contributors <olddoc-public@80x24.org>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+require 'uri'
+
+module Olddoc::History
+  def initialize_history
+    @tags = @old_summaries = nil
+  end
+
+  # returns a cgit URI for a given +tag_name+
+  def tag_uri(tag_name)
+    uri = @cgit_uri.dup
+    uri.path += "/tag/"
+    uri.query = "id=#{tag_name}"
+    uri
+  end
+
+  def tags
+    timefmt = '%Y-%m-%dT%H:%M:%SZ'
+    @tags ||= `git tag -l`.split(/\n/).map do |tag|
+      next if tag == "v0.0.0"
+      if %r{\Av[\d\.]+} =~ tag
+        type = `git cat-file -t #{tag}`.chomp
+        user_type = { "tag" => "tagger", "commit" => "committer" }[type]
+        user_type or abort "unable to determine what to do with #{type}=#{tag}"
+        header, subject, body = `git cat-file #{type} #{tag}`.split(/\n\n/, 3)
+        body ||= "initial" unless old_summaries.include?(tag)
+        header = header.split(/\n/)
+
+        tagger = header.grep(/\A#{user_type} /).first
+        time = Time.at(tagger.split(/ /)[-2].to_i).utc
+        {
+          :time => time.strftime(timefmt),
+          :ruby_time => time,
+          :tagger_name => %r{^#{user_type} ([^<]+)}.match(tagger)[1].strip,
+          :tagger_email => %r{<([^>]+)>}.match(tagger)[1].strip,
+          :id => `git rev-parse refs/tags/#{tag}`.chomp!,
+          :tag => tag,
+          :subject => subject.strip,
+          :body => (old = old_summaries[tag]) ? "#{old}\n#{body}" : body,
+        }
+      end
+    end.compact.sort { |a,b| b[:time] <=> a[:time] }
+  end
+
+  def old_summaries
+    @old_summaries ||= if File.exist?(".CHANGELOG.old")
+      File.readlines(".CHANGELOG.old").inject({}) do |hash, line|
+        version, summary = line.split(/ - /, 2)
+        hash[version] = summary
+        hash
+      end
+    else
+      {}
+    end
+  end
+end
diff --git a/lib/olddoc/merge.rb b/lib/olddoc/merge.rb
new file mode 100644
index 0000000..da5bd07
--- /dev/null
+++ b/lib/olddoc/merge.rb
@@ -0,0 +1,26 @@
+# Copyright (C) 2015, all contributors <olddoc-public@80x24.org>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+
+class Olddoc::Merge
+  def initialize(opts)
+    @merge_html = opts["merge_html"] || {}
+  end
+
+  # FIXME: generate manpages directly from rdoc instead of relying on
+  # pandoc to do it via markdown.
+  def run
+    @merge_html.each do |file, source|
+      rdoc_html = "doc/#{file}.html"
+      fragment = File.read(source)
+      File.open(rdoc_html, "a+") { |fp|
+        html = fp.read
+        if html.sub!(%r{\s*<p>\s*olddoc_placeholder\s*</p>\s*}sm, fragment)
+          fp.truncate(0)
+          fp.write(html)
+        else
+          warn "olddoc_placeholder not found in #{rdoc_html}"
+        end
+      }
+    end
+  end
+end
diff --git a/lib/olddoc/news_atom.rb b/lib/olddoc/news_atom.rb
new file mode 100644
index 0000000..6a92b3e
--- /dev/null
+++ b/lib/olddoc/news_atom.rb
@@ -0,0 +1,51 @@
+# Copyright (C) 2015, all contributors <olddoc-public@80x24.org>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+require 'builder'
+
+module Olddoc::NewsAtom
+  include Olddoc::History
+  include Olddoc::Readme
+
+  # generates an Atom feed based on git tags in the document directory
+  def news_atom_xml
+    project_name, short_desc, _ = readme_metadata
+    new_tags = tags[0,10]
+    atom_uri = @rdoc_uri.dup
+    atom_uri.path += "NEWS.atom.xml"
+    news_uri = @rdoc_uri.dup
+    news_uri.path += "NEWS.html"
+    x = Builder::XmlMarkup.new
+    x.feed(xmlns: "http://www.w3.org/2005/Atom") do
+      x.id(atom_uri.to_s)
+      x.title("#{project_name} news")
+      x.subtitle(short_desc)
+      x.link(rel: 'alternate', type: 'text/html', href: news_uri.to_s)
+      x.updated(new_tags.empty? ? '1970-01-01:00:00:00Z' : new_tags[0][:time])
+      new_tags.each do |tag|
+        x.entry do
+          x.title(tag[:subject])
+          x.updated(tag[:time])
+          x.published(tag[:time])
+          x.author do
+            x.name(tag[:tagger_name])
+            x.email(tag[:tagger_email])
+          end
+          uri = tag_uri(tag[:tag]).to_s
+          x.link(rel: "alternate", type: 'text/html', href: uri)
+          x.id(uri)
+          x.content(type: :xhtml) { x.pre(tag[:body]) }
+        end # entry
+      end # new_tags
+    end # feed
+    [ x.target!, new_tags ]
+  end
+
+  def news_atom(dest = "NEWS.atom.xml")
+    xml, new_tags = news_atom_xml
+    File.open(dest, "w") { |fp| fp.write(xml) }
+    unless new_tags.empty?
+      time = new_tags[0][:ruby_time]
+      File.utime(time, time, dest)
+    end
+  end
+end
diff --git a/lib/olddoc/news_rdoc.rb b/lib/olddoc/news_rdoc.rb
new file mode 100644
index 0000000..cc51bec
--- /dev/null
+++ b/lib/olddoc/news_rdoc.rb
@@ -0,0 +1,36 @@
+# -*- encoding: utf-8 -*-
+# Copyright (C) 2015, all contributors <olddoc-public@80x24.org>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+
+require 'tempfile'
+
+module Olddoc::NewsRdoc
+  include Olddoc::History
+
+  def puts_tag(fp, tag)
+    time = tag[:time].tr('T', ' ').gsub!(/:\d\dZ/, ' UTC')
+    fp.puts "=== #{tag[:subject]} / #{time}"
+    fp.puts ""
+
+    body = tag[:body]
+    fp.puts tag[:body].gsub(/^/smu, "  ").gsub(/[ \t]+$/smu, "")
+    fp.puts ""
+  end
+
+  # generates a NEWS file in the top-level directory based on git tags
+  def news_rdoc
+    news = Tempfile.new('NEWS', '.')
+    tags.each { |tag| puts_tag(news, tag) }
+    File.open("LATEST", "wb") { |latest|
+      if tags.empty?
+        latest.puts "Currently unreleased"
+        news.puts "No news yet."
+      else
+        puts_tag(latest, tags[0])
+      end
+    }
+    news.chmod(0666 & ~File.umask)
+    File.rename(news.path, 'NEWS')
+    news.close!
+  end
+end
diff --git a/lib/olddoc/prepare.rb b/lib/olddoc/prepare.rb
new file mode 100644
index 0000000..f760bd3
--- /dev/null
+++ b/lib/olddoc/prepare.rb
@@ -0,0 +1,27 @@
+# Copyright (C) 2015, all contributors <olddoc-public@80x24.org>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+
+require 'uri'
+class Olddoc::Prepare
+  include Olddoc::NewsRdoc
+  include Olddoc::NewsAtom
+  include Olddoc::Changelog
+  include Olddoc::Readme
+
+  def initialize(opts)
+    rdoc_url = opts['rdoc_url']
+    cgit_url = opts['cgit_url']
+    rdoc_url && cgit_url or
+      abort "rdoc_url and cgit_url required in .olddoc.yml for `prepare'"
+    @rdoc_uri = URI.parse(rdoc_url)
+    @cgit_uri = URI.parse(cgit_url)
+    @changelog_start = opts['changelog_start']
+    @name, @short_desc = readme_metadata
+  end
+
+  def run
+    news_rdoc
+    changelog
+    news_atom
+  end
+end
diff --git a/lib/olddoc/readme.rb b/lib/olddoc/readme.rb
new file mode 100644
index 0000000..18f0205
--- /dev/null
+++ b/lib/olddoc/readme.rb
@@ -0,0 +1,27 @@
+# Copyright (C) 2015, all contributors <olddoc-public@80x24.org>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# helpers for parsing the top-level README file
+module Olddoc::Readme
+
+  def readme_path
+    'README'
+  end
+
+  # returns a one-paragraph summary from the README
+  def readme_description
+    File.read(readme_path).split(/\n\n/)[1]
+  end
+
+  # parses the README file in the top-level directory for project metadata
+  def readme_metadata
+    l = File.readlines(readme_path)[0].strip!
+    l.gsub!(/^=\s+/, '') or abort "#{l.inspect} doesn't start with '='"
+    title = l.dup
+    if l.gsub!(/^(\w+\!)\s+/, '') # Rainbows!
+      return $1, l, title
+    else
+      return (l.split(/\s*[:-]\s*/, 2)).push(title)
+    end
+  end
+end
diff --git a/lib/oldweb.rb b/lib/oldweb.rb
new file mode 100644
index 0000000..d045693
--- /dev/null
+++ b/lib/oldweb.rb
@@ -0,0 +1,303 @@
+# Copyright (C) 2015, all contributors <olddoc-public@80x24.org>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+# Loosely derived from Darkfish in the main rdoc distribution
+require 'rdoc'
+require 'erb'
+require 'pathname'
+require 'yaml'
+require 'cgi'
+require 'uri'
+
+class Oldweb
+  RDoc::RDoc.add_generator(self)
+  include ERB::Util
+  attr_reader :class_dir
+  attr_reader :file_dir
+
+  # description of the generator
+  DESCRIPTION = 'minimal HTML generator'
+
+  # version of this generator
+  VERSION = '1'
+
+  def initialize(store, options)
+    # just because we're capable of generating UTF-8 to get human names
+    # right does not mean we should overuse it for quotation marks and such,
+    # our clients may not have the necessary fonts.
+    RDoc::Text::TO_HTML_CHARACTERS[Encoding::UTF_8] =
+      RDoc::Text::TO_HTML_CHARACTERS[Encoding::ASCII]
+
+    @store = store
+    @options = options
+    @base_dir = Pathname.pwd.expand_path
+    @dry_run = options.dry_run
+    @file_output = true
+    @template_dir = Pathname.new(File.join(File.dirname(__FILE__), 'oldweb'))
+    @template_cache = {}
+    @classes = nil
+    @context = nil
+    @files = nil
+    @methods = nil
+    @modsort = nil
+    @class_dir = nil
+    @file_dir = nil
+    @outputdir = nil
+    @old_vcs_url = nil
+    @git_tag = nil
+
+    # olddoc-specific stuff
+    # infer title from README
+    if options.title == 'RDoc Documentation' && File.readable?('README')
+      line = File.open('README') { |fp| fp.gets.strip }
+      line.sub!(/\A=+\s*/, '')
+      options.title = line
+    end
+
+    # load olddoc config
+    cfg = '.olddoc.yml'
+    if File.readable?(cfg)
+      @old_cfg = YAML.load(File.read(cfg))
+    else
+      @old_cfg = {}
+      warn "#{cfg} not readable"
+    end
+    %w(toc_max).each { |k| v = @old_cfg[k] and @old_cfg[k] = v.to_i }
+    @old_cfg['toc_max'] ||= 6
+
+    ni = {}
+    noindex = @old_cfg['noindex'] and noindex.each { |k| ni[k] = true }
+    @old_cfg['noindex'] = ni
+
+    if cgit_url = @old_cfg['cgit_url']
+      cgit_url += '/tree/%s' # path name
+      tag = @git_tag and cgit_url << "id=#{URI.escape(tag)}"
+      cgit_url << '#n%d' # lineno
+      @old_vcs_url = cgit_url
+    end
+  end
+
+  def generate
+    setup
+    generate_class_files
+    generate_file_files
+    generate_table_of_contents
+    src = Dir["#@outputdir/README*.html"].first
+    begin
+      dst = "#@outputdir/index.html"
+      File.link(src, dst)
+    rescue SystemCallError
+      IO.copy_stream(src, dst)
+    end if src
+  end
+
+  def rel_path(out_file)
+    rel_prefix = @outputdir.relative_path_from(out_file.dirname)
+    rel_prefix == '.' ? '' : "#{rel_prefix}/"
+  end
+
+  # called standalone by servelet
+  def generate_class(klass, template_file = nil)
+    setup
+    current = klass
+    template_file ||= @template_dir + 'class.rhtml'
+    out_file = @outputdir + klass.path
+    rel_prefix = rel_path(out_file)
+    @title = "#{klass.type} #{klass.full_name}"
+    @suppress_warning = [ rel_prefix, current ]
+    render_template(template_file, out_file) { |io| binding }
+  end
+
+  # Generate a documentation file for each class and module
+  def generate_class_files
+    setup
+    template_file = @template_dir + 'class.rhtml'
+    current = nil
+
+    @classes.each do |klass|
+      current = klass
+      generate_class(klass, template_file)
+    end
+  rescue => e
+    e!(e, "error generating #{current.path}: #{e.message} (#{e.class})")
+  end
+
+  # Generate a documentation file for each file
+  def generate_file_files
+    setup
+    @files.each do |file|
+      generate_page(file) if file.text?
+    end
+  end
+
+  # Generate a page file for +file+
+  def generate_page(file, out_file = @outputdir + file.path)
+    setup
+    template_file = @template_dir + 'page.rhtml'
+    rel_prefix = rel_path(out_file)
+    current = file
+
+    @title = "#{file.page_name} - #{@options.title}"
+
+    # use the first header as title instead of page_name if there is one
+    File.open("#{@options.root}/#{file.absolute_name}") do |f|
+      line = f.gets.strip
+      line.sub!(/^=+\s*/, '') and @title = line
+    end
+
+    @suppress_warning = [ current, rel_prefix ]
+
+    render_template(template_file, out_file) { |io| binding }
+  rescue => e
+    e!(e, "error generating #{out_file}: #{e.message} (#{e.class})")
+  end
+
+  # Generates the 404 page for the RDoc servlet
+  def generate_servlet_not_found(message)
+    setup
+    template_file = @template_dir + 'servlet_not_found.rhtml'
+    rel_prefix = ''
+    @suppress_warning = rel_prefix
+    @title = 'Not Found'
+    render_template(template_file) { |io| binding }
+  rescue => e
+    e!(e, "error generating servlet_not_found: #{e.message} (#{e.class})")
+  end
+
+  # Generates the servlet root page for the RDoc servlet
+  def generate_servlet_root(installed)
+    setup
+
+    template_file = @template_dir + 'servlet_root.rhtml'
+
+    rel_prefix = ''
+    @suppress_warning = rel_prefix
+
+    @title = 'Local RDoc Documentation'
+    render_template(template_file) { |io| binding }
+  rescue => e
+    e!(e, "error generating servlet_root: #{e.message} (#{e.class})")
+  end
+
+  def generate_table_of_contents
+    setup
+    template_file = @template_dir + 'table_of_contents.rhtml'
+    out_file = @outputdir + 'table_of_contents.html'
+    rel_prefix = rel_path(out_file)
+    @suppress_warning = rel_prefix
+    @title = "Table of Contents - #{@options.title}"
+    render_template(template_file, out_file) { |io| binding }
+  rescue => e
+    e!(e, "error generating table_of_contents.html: #{e.message} (#{e.class})")
+  end
+
+  def setup
+    return if @outputdir
+    @outputdir = Pathname.new(@options.op_dir).expand_path(@base_dir)
+    return unless @store
+    @classes = @store.all_classes_and_modules.sort
+    @files = @store.all_files.sort
+    @methods = @classes.map(&:method_list).flatten.sort
+    @modsort = @classes.select(&:display?).sort
+  end
+
+  # Creates a template from its components and the +body_file+.
+  def assemble_template(body_file)
+    body = body_file.read
+    head = @template_dir + '_head.rhtml'
+    tail = @template_dir + '_tail.rhtml'
+    "<html><head>#{head.read.strip}</head><body\n" \
+      "id=\"top\">#{body.strip}#{tail.read.strip}</body></html>"
+  end
+
+  # Renders the ERb contained in +file_name+ relative to the template
+  # directory and returns the result based on the current context.
+  def render(file_name)
+    template_file = @template_dir + file_name
+    template = template_for(template_file, false, RDoc::ERBPartial)
+    template.filename = template_file.to_s
+    template.result(@context)
+  end
+
+  # Load and render the erb template in the given +template_file+ and write
+  # it out to +out_file+.
+  # Both +template_file+ and +out_file+ should be Pathname-like objects.
+  # An io will be yielded which must be captured by binding in the caller.
+  def render_template(template_file, out_file = nil) # :yield: io
+    io_output = out_file && !@dry_run && @file_output
+    erb_klass = io_output ? RDoc::ERBIO : ERB
+    template = template_for(template_file, true, erb_klass)
+
+    if io_output
+      out_file.dirname.mkpath
+      out_file.open('w', 0644) do |io|
+        io.set_encoding(@options.encoding)
+        @context = yield io
+        template_result(template, @context, template_file)
+      end
+    else
+      @context = yield nil
+      template_result(template, @context, template_file)
+    end
+  end
+
+  # Creates the result for +template+ with +context+.  If an error is raised a
+  # Pathname +template_file+ will indicate the file where the error occurred.
+  def template_result(template, context, template_file)
+    template.filename = template_file.to_s
+    template.result(context)
+  rescue NoMethodError => e
+    e!(e, "Error while evaluating #{template_file.expand_path}: #{e.message}")
+  end
+
+  def template_for(file, page = true, klass = ERB)
+    template = @template_cache[file]
+
+    return template if template
+
+    if page
+      template = assemble_template(file)
+      erbout = 'io'
+    else
+      template = file.read
+      template = template.encode(@options.encoding)
+      file_var = File.basename(file).sub(/\..*/, '')
+      erbout = "_erbout_#{file_var}"
+    end
+
+    template = klass.new(template, nil, '<>', erbout)
+    @template_cache[file] = template
+  end
+
+  def e!(e, msg)
+    raise RDoc::Error, msg, e.backtrace
+  end
+
+  def method_srclink(m)
+    url = @old_vcs_url or return ""
+    line = m.line or return ""
+    path = URI.escape(m.file_name)
+    %Q(<a href="#{url % [ path, line ]}">source</a>)
+  end
+
+  # reach into RDoc internals to generate less HTML
+  module LessHtml
+    def accept_verbatim(verbatim)
+      @res << "\n<pre>#{CGI.escapeHTML(verbatim.text.rstrip)}</pre>\n"
+    end
+
+    def accept_heading(heading)
+      level = [6, heading.level].min
+      label = heading.label(@code_object)
+      @res << "<h#{level}"
+      @res << (@options.output_decoration ? "\nid=\"#{label}\">" : ">")
+      @res << to_html(heading.text)
+      @res << "</h#{level}>"
+    end
+  end
+end
+
+class RDoc::Markup::ToHtml # :nodoc:
+  remove_method :accept_heading
+  remove_method :accept_verbatim
+  include Oldweb::LessHtml
+end
diff --git a/lib/oldweb/_head.rhtml b/lib/oldweb/_head.rhtml
new file mode 100644
index 0000000..9a9242c
--- /dev/null
+++ b/lib/oldweb/_head.rhtml
@@ -0,0 +1,7 @@
+<meta charset="<%= @options.charset %>"><title><%= h @title %></title><%
+if rdoc_url = @old_cfg['rdoc_url'] %><link
+rel="alternate"
+title="Atom feed"
+href="<%= rdoc_url %>NEWS.atom.xml"
+type="application/atom+xml" /><%
+end %>
diff --git a/lib/oldweb/_sidebar_classes.rhtml b/lib/oldweb/_sidebar_classes.rhtml
new file mode 100644
index 0000000..66f7fbf
--- /dev/null
+++ b/lib/oldweb/_sidebar_classes.rhtml
@@ -0,0 +1,27 @@
+<%
+@modsort.delete_if do |c|
+  c.full_name == "unknown" || @old_cfg["noindex"].include?(c.full_name)
+end
+unless @modsort.empty?
+  # To save space and reduce visual noise, we want to display:
+  #   Top Sub1 Sub2 Sub3
+  # Instead of
+  #   Top Top::Sub1 Top::Sub2 Top::Sub3
+  top_mod = nil
+  @modsort.each do |mod| %><%
+    parts = mod.full_name.split('::')
+    if parts[0] != top_mod && parts.size == 1
+      top_mod = parts[0]
+      %><%= '<br />' if mod != @modsort[0] %><%
+    elsif parts[0] != top_mod
+      top_mod = nil
+      %><%= '<br />' if mod != @modsort[0] %><%= parts[0] %> <%
+    else
+      parts.shift
+    end
+%><a
+href="<%= rel_prefix %><%= mod.path %>"><%= parts.join('::') %></a>
+<%
+  end
+end
+%><br />
diff --git a/lib/oldweb/_sidebar_extends.rhtml b/lib/oldweb/_sidebar_extends.rhtml
new file mode 100644
index 0000000..8dab782
--- /dev/null
+++ b/lib/oldweb/_sidebar_extends.rhtml
@@ -0,0 +1,13 @@
+<%
+unless klass.extends.empty?
+%><p><b>Extended with:</b> <%
+  klass.each_extend do |ext|
+    unless String === ext.module
+%><a
+href="<%= klass.aref_to ext.module.path %>"><%= ext.module.full_name %></a><%
+    else
+%><%= ext.name %><%
+    end
+  end
+end
+%>
diff --git a/lib/oldweb/_sidebar_includes.rhtml b/lib/oldweb/_sidebar_includes.rhtml
new file mode 100644
index 0000000..46a1d1d
--- /dev/null
+++ b/lib/oldweb/_sidebar_includes.rhtml
@@ -0,0 +1,12 @@
+<%
+unless klass.includes.empty?
+%><p><b>Included modules:</b> <%
+  klass.each_include do |inc|
+    unless String === inc.module %><a
+href="<%= klass.aref_to inc.module.path %>"><%= inc.module.full_name %></a><%
+    else %><%=
+      inc.name %><%
+    end
+  end
+end
+%>
diff --git a/lib/oldweb/_sidebar_installed.rhtml b/lib/oldweb/_sidebar_installed.rhtml
new file mode 100644
index 0000000..74d3bab
--- /dev/null
+++ b/lib/oldweb/_sidebar_installed.rhtml
@@ -0,0 +1,10 @@
+<h3>Documentation</h3><ul><%
+installed.each do |name, href, exists, type, _|
+  next if type == :extra %><li><%
+  if exists
+  %><a
+href="<%= href %>"><%= h name %></a><%
+  else
+  %><%= h name %><%
+  end
+end %></ul>
diff --git a/lib/oldweb/_sidebar_methods.rhtml b/lib/oldweb/_sidebar_methods.rhtml
new file mode 100644
index 0000000..f84fe9c
--- /dev/null
+++ b/lib/oldweb/_sidebar_methods.rhtml
@@ -0,0 +1,6 @@
+<% unless klass.method_list.empty? %>
+<h3 id="method-list-section">Methods</h3><%
+klass.each_method do |meth| %>
+<a href="#<%= meth.aref %>"><%=
+meth.singleton ? '::' : '#' %><%= h meth.name %></a><%
+end %><% end %>
diff --git a/lib/oldweb/_sidebar_navigation.rhtml b/lib/oldweb/_sidebar_navigation.rhtml
new file mode 100644
index 0000000..a7ea935
--- /dev/null
+++ b/lib/oldweb/_sidebar_navigation.rhtml
@@ -0,0 +1,6 @@
+<br /><a
+href="<%= rel_prefix %>table_of_contents.html#pages">Pages</a>
+<a
+href="<%= rel_prefix %>table_of_contents.html#classes">Classes</a>
+<a
+href="<%= rel_prefix %>table_of_contents.html#methods">Methods</a>
diff --git a/lib/oldweb/_sidebar_pages.rhtml b/lib/oldweb/_sidebar_pages.rhtml
new file mode 100644
index 0000000..cdc23f4
--- /dev/null
+++ b/lib/oldweb/_sidebar_pages.rhtml
@@ -0,0 +1,17 @@
+<%
+simple_files = @files.select(&:text?)
+unless simple_files.empty?
+  current_name = nil
+  if defined?(current) && current.respond_to?(:page_name)
+    current_name = current.page_name
+  end
+  simple_files.each do |f|
+    next if @old_cfg["noindex"].include?(f.page_name)
+    b = (current_name == f.page_name)
+%><a
+href="<%= rel_prefix %><%= f.path %>"><%=
+b ? '<b>' : '' %><%= h f.page_name %><%= b ? '</b>' : ''%></a>
+<%
+  end
+end
+%><br />
diff --git a/lib/oldweb/_sidebar_parent.rhtml b/lib/oldweb/_sidebar_parent.rhtml
new file mode 100644
index 0000000..9323101
--- /dev/null
+++ b/lib/oldweb/_sidebar_parent.rhtml
@@ -0,0 +1,13 @@
+<%
+# having Object as parent is boring
+if klass.type == 'class' && klass.superclass != 'Object'
+%><p><b>Parent:</b> <%
+  if klass.superclass and not String === klass.superclass
+%><a
+href="<%= klass.aref_to klass.superclass.path %>"><%=
+    klass.superclass.full_name %></a><%
+  else
+%><%= klass.superclass %><%
+  end
+end
+%>
diff --git a/lib/oldweb/_sidebar_sections.rhtml b/lib/oldweb/_sidebar_sections.rhtml
new file mode 100644
index 0000000..b6e9458
--- /dev/null
+++ b/lib/oldweb/_sidebar_sections.rhtml
@@ -0,0 +1,8 @@
+<%
+unless klass.sections.length == 1
+%><p><b>Sections:</b> <%
+  klass.sort_sections.each do |section| %><a
+href="#<%= section.aref %>"><%= h section.title %></a><%
+  end
+end
+%>
diff --git a/lib/oldweb/_sidebar_table_of_contents.rhtml b/lib/oldweb/_sidebar_table_of_contents.rhtml
new file mode 100644
index 0000000..890b0cc
--- /dev/null
+++ b/lib/oldweb/_sidebar_table_of_contents.rhtml
@@ -0,0 +1,15 @@
+<%
+comment = current.respond_to?(:comment_location) ? current.comment_location :
+          current.comment
+table = current.parse(comment).table_of_contents
+if table.length > 1
+%><%
+  table.each_with_index do |heading, i|
+    next if heading.level > @old_cfg['toc_max']
+%><a
+href="#<%= heading.label(current) %>"><%=
+    i == 0 ? "top" : heading.plain_html %></a>
+<%
+  end %><br /><%
+end
+%>
diff --git a/lib/oldweb/_tail.rhtml b/lib/oldweb/_tail.rhtml
new file mode 100644
index 0000000..d1fc7e5
--- /dev/null
+++ b/lib/oldweb/_tail.rhtml
@@ -0,0 +1,25 @@
+<%
+public_email = @old_cfg['public_email']
+private_email = @old_cfg['private_email']
+ml_url = @old_cfg['ml_url']
+git_doc = 'https://kernel.org/pub/software/scm/git/docs/'
+se_url = "#{git_doc}git-send-email.html"
+rp_url = "#{git_doc}git-request-pull.html"
+
+if public_email && private_email && ml_url %><hr /><p>
+We love to hear from you!<br />
+Email patches (using <a
+href="<%= se_url %>">git send-email</a>),
+pull requests (formatted using <a
+href="<%= rp_url %>">git request-pull</a>), questions, bug reports,
+suggestions, etc. to us publically at:<br /><a
+href="mailto:<%= public_email %>"><%= public_email %></a><br />
+Mail archives are available at: <a
+href="<%= ml_url %>"><%= ml_url %></a><br />
+Please send plain-text email only and do not waste bandwidth on HTML mail,
+HTML mail will not be read.<br />
+Quote as little as reasonable and do not <a
+href="http://catb.org/jargon/html/T/top-post.html">top post</a>.<br />
+For sensitive topics, email us privately at:
+<a
+href="mailto:<%= private_email %>"><%= private_email %></a><% end %>
diff --git a/lib/oldweb/class.rhtml b/lib/oldweb/class.rhtml
new file mode 100644
index 0000000..107ce41
--- /dev/null
+++ b/lib/oldweb/class.rhtml
@@ -0,0 +1,79 @@
+<%=
+render('_sidebar_pages.rhtml') <<
+render('_sidebar_classes.rhtml') <<
+render('_sidebar_methods.rhtml')
+%><h1
+id="<%= h klass.aref %>"><%= klass.type %> <%= klass.full_name %></h1><%=
+klass.description.strip
+%><%
+nd = '(Not documented)'
+klass.each_section do |section, constants, attributes|
+  constants = constants.select(&:display?)
+  attributes = attributes.select(&:display?)
+  if section.title
+%><h2
+id="<%= section.aref %>"><%= section.title %></h2><%
+  end
+  if section.comment %><p><%= section.description.strip %></p><%
+  end
+  unless constants.empty? %><h3>Constants</h3><%
+    constants.each { |const|
+      %><h4
+id="<%= const.name %>"><%= const.name %></h4><%=
+      const.comment ? const.description.strip : nd %><%
+    } %><%
+  end
+  unless attributes.empty? %><h3>Attributes</h3><%
+    attributes.each do |attrib|
+%><h4
+id="<%= attrib.aref %>"><%=
+      h(attrib.name) %> [<%= attrib.rw %>]</h4><%=
+      attrib.comment ? attrib.description.strip : nd %><%
+    end
+  end
+  klass.methods_by_type(section).each do |type, visibilities|
+    next if visibilities.empty?
+    visibilities.each do |visibility, methods|
+      next if methods.empty? %><h3
+id="<%= visibility %>-<%= type %>-<%= section.aref %>-method-details">
+<%= visibility.to_s.capitalize %> <%= type.capitalize %> Methods</h3><%
+      methods.each do |method|
+      %><pre id="<%= method.aref %>"><b><%
+        if method.call_seq %><%= h method.call_seq %><%
+        else
+          %><%= h method.name %> <%= h method.param_seq %><%
+        end %></b><%= method_srclink(method) %></pre><%=
+          method.comment ? method.description.strip : nd %><%
+        if method.calls_super %>Calls superclass method<%=
+           method.superclass_method ?
+           method.formatter.link(method.superclass_method.full_name,
+                                 method.superclass_method.full_name) : nil
+        %><%
+        end
+        unless method.aliases.empty?
+        %> Also aliased as: <%=
+          method.aliases.map do |aka|
+            if aka.parent # HACK lib/rexml/encodings
+              %{<a href="#{klass.aref_to(aka.path)}">#{h aka.name}</a>}
+            else
+              h aka.name
+            end
+          end.join ", " %><%
+        end
+        if method.is_alias_for
+       %><br />Alias for:
+<a
+href="<%= klass.aref_to method.is_alias_for.path %>"><%=
+          h method.is_alias_for.name %></a><%
+        end
+      end
+    end
+  end
+end
+%><%=
+render('_sidebar_sections.rhtml').strip <<
+render('_sidebar_parent.rhtml').strip <<
+render('_sidebar_includes.rhtml').strip <<
+render('_sidebar_extends.rhtml').strip <<
+render('_sidebar_navigation.rhtml').strip
+%>
diff --git a/lib/oldweb/page.rhtml b/lib/oldweb/page.rhtml
new file mode 100644
index 0000000..9049ee5
--- /dev/null
+++ b/lib/oldweb/page.rhtml
@@ -0,0 +1,5 @@
+<%=
+render('_sidebar_pages.rhtml') <<
+render('_sidebar_classes.rhtml') <<
+file.description.strip
+%>
diff --git a/lib/oldweb/servlet_not_found.rhtml b/lib/oldweb/servlet_not_found.rhtml
new file mode 100644
index 0000000..3264d9e
--- /dev/null
+++ b/lib/oldweb/servlet_not_found.rhtml
@@ -0,0 +1,5 @@
+<%=
+  render('_sidebar_navigation.rhtml') <<
+  render('_sidebar_pages.rhtml') <<
+  render('_sidebar_classes.rhtml')
+%><h1>Not Found</h1><%= message %>
diff --git a/lib/oldweb/servlet_root.rhtml b/lib/oldweb/servlet_root.rhtml
new file mode 100644
index 0000000..839198f
--- /dev/null
+++ b/lib/oldweb/servlet_root.rhtml
@@ -0,0 +1,39 @@
+<h2><a href="<%= rel_prefix %>">Home</a></h2><%=
+render '_sidebar_installed.rhtml'
+%><h1>Local RDoc Documentation</h1>
+<p>Here you can browse local documentation from the ruby standard library and
+  your installed gems.
+<% extra_dirs = installed.select { |_, _, _, type,| type == :extra } %>
+<% unless extra_dirs.empty? %>
+<h2>Extra Documentation Directories</h2>
+  <p>The following additional documentation directories are available:</p>
+  <ol>
+  <% extra_dirs.each do |name, href, exists, _, path| %>
+    <li>
+    <% if exists %>
+      <a href="<%= href %>"><%= h name %></a> (<%= h path %>)
+    <% else %>
+      <%= h name %> (<%= h path %>; not available)
+    <% end %>
+  <% end %>
+  </ol>
+<% end %>
+<%
+gems = installed.select { |_, _, _, type,| type == :gem }
+missing = gems.reject { |_, _, exists,| exists }
+unless missing.empty? %>
+<h2>Missing Gem Documentation</h2>
+<p>You are missing documentation for some of your installed gems.
+You can install missing documentation for gems by running
+<kbd>gem rdoc --all</kbd>.  After installing the missing documentation you
+only need to reload this page.  The newly created documentation will
+automatically appear.
+<p>You can also install documentation for a specific gem by running one of
+the following commands.
+<ul>
+<% names = missing.map { |name,| name.sub(/-([^-]*)$/, '') }.uniq %>
+<% names.each do |name| %>
+  <li><kbd>gem rdoc <%=h name %></kbd>
+<% end %>
+</ul>
+<% end %>
diff --git a/lib/oldweb/table_of_contents.rhtml b/lib/oldweb/table_of_contents.rhtml
new file mode 100644
index 0000000..ed455a8
--- /dev/null
+++ b/lib/oldweb/table_of_contents.rhtml
@@ -0,0 +1,52 @@
+<h1><%= h @title %></h1><%
+simple_files = @files.select(&:text?)
+unless simple_files.empty?
+%><h2 id="pages">Pages</h2>
+<ul><%
+  simple_files.sort.each do |file| %><li><a
+href="<%= file.path %>"><%= h file.page_name %></a>
+<%
+    # HACK table_of_contents should not exist on Document
+    table = file.parse(file.comment).table_of_contents
+    unless table.empty?
+%><ul><%
+      table.each do |heading|
+      %><li><a
+href="<%= file.path %>#<%= heading.aref %>"><%= heading.plain_html %></a>
+<%    end
+%></ul><%
+    end
+%><%
+  end
+%></ul><%
+end
+%><h2
+id="classes">Classes and Modules</h2><ul><%
+@modsort.each do |klass| %><li
+class="<%= klass.type %>"><a
+href="<%= klass.path %>"><%= klass.full_name %></a>
+<%
+  table = []
+  table.concat klass.parse(klass.comment_location).table_of_contents
+  table.concat klass.section_contents
+
+  unless table.empty?
+    %><ul><%
+    table.each do |item|
+    %><li><a
+href="<%= klass.path %>#<%= item.aref %>"><%= item.plain_html %></a>
+<%
+    end %></ul><%
+  end
+%><%
+end
+%></ul><h2
+id="methods">Methods</h2><ul><%
+  @store.all_classes_and_modules.map do |mod|
+    mod.method_list
+  end.flatten.sort.each do |method|
+%><li><a
+href="<%= method.path %>"><%= h method.pretty_name %></a>
+- <%= method.parent.full_name %><%
+end
+%></ul>
diff --git a/lib/rdoc/discover.rb b/lib/rdoc/discover.rb
new file mode 100644
index 0000000..e497514
--- /dev/null
+++ b/lib/rdoc/discover.rb
@@ -0,0 +1,5 @@
+begin
+  gem 'rdoc', '~> 4.1'
+  require_relative '../olddoc'
+rescue Gem::LoadError
+end unless defined?(Olddoc)