Commit b2d496ff authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Refactor GuideToc.

Turn GuideToc into instantiable class, so we can make use of
instance variables, refactor the logic into several helper methods and
avoid passing lots of arguments around.
parent 875b1fdc
Loading
Loading
Loading
Loading
+63 −33
Original line number Diff line number Diff line
@@ -4,50 +4,80 @@ module JsDuck

  # Adds Table of Contents section to guide HTML.
  class GuideToc
    def initialize(html, guide_name, max_level=2)
      @html = html
      @guide_name = guide_name

      @min_level = 2
      @max_level = max_level

      # Count the number of heading increments we've seen so far.
      @heading_counts = Array.new(@max_level+1) { 0 }

      @toc = []
      @new_html = []
    end

    # Inserts table of contents at the top of guide HTML by looking
    # for headings at or below the specified maximum level.
    def self.inject(html, guide_name, max_level)
      toc = []
      new_html = []

      # Count the number of heading increments we've seen so far; use one fewer
      # than max_level, since <h1> tags don't go in the TOC.
      heading_counts = Array.new(max_level - 1) { 0 }

      html.each_line do |line|
        if line =~ /^\s*<(h([1-6]))>(.*?)<\/h[1-6]>$/
          tag = $1
          level = $2.to_i - 1 # ignore <h1>
          text = Util::HTML.strip_tags($3)
          id = guide_name + "-section-" + title_to_id(text)
          if (1...max_level).include? level
            heading_counts[level - 1] += 1
            (level...heading_counts.length).each { |i| heading_counts[i] = 0 }
            prefix = heading_counts.slice(0...level).join('.')
            toc << "#{prefix}. <a href='#!/guide/#{id}'>#{text}</a><br/>\n"
          end
          new_html << "<#{tag} id='#{id}'>#{text}</#{tag}>\n"
    def inject!
      @html.each_line do |line|
        if line =~ /^\s*<h([1-6])>(.*?)<\/h[1-6]>$/
          level = $1.to_i
          text = Util::HTML.strip_tags($2)
          id = title_to_id(text)

          if include_to_toc?(level)
            increment_heading_count!(level)
            @toc << toc_entry(level, id, text)
          end

          @new_html << "<h#{level} id='#{id}'>#{text}</h#{level}>\n"
        else
          new_html << line
          @new_html << line
        end
      end

      # Inject TOC below first heading if at least 2 items in TOC
      if toc.length >= 2
        new_html.insert(1, [
            "<div class='toc'>\n",
            "<p><strong>Contents</strong></p>\n",
            toc,
            "</div>\n",
        ])
      inject_toc!

      @new_html.flatten.join
    end

    private

    def include_to_toc?(level)
      (@min_level..@max_level).include?(level)
    end

    # Increments count of current heading level.
    # Resets counts of all the subheadings.
    def increment_heading_count!(level)
      @heading_counts[level] += 1
      ((level+1)..@max_level).each { |i| @heading_counts[i] = 0 }
    end

      new_html.flatten.join
    def toc_entry(level, id, text)
      "#{toc_prefix(level)}. <a href='#!/guide/#{id}'>#{text}</a><br/>\n"
    end

    def self.title_to_id(title)
      CGI::escape(title.downcase.gsub(/ /, "-"))
    def toc_prefix(level)
      @heading_counts.slice(@min_level..level).join('.')
    end

    def title_to_id(title)
      @guide_name + "-section-" + CGI::escape(title.downcase.gsub(/ /, "-"))
    end

    # Injects TOC below first heading if at least 2 items in TOC
    def inject_toc!
      return if @toc.length < 2

      @new_html.insert(1, [
        "<div class='toc'>\n",
          "<p><strong>Contents</strong></p>\n",
          @toc,
        "</div>\n",
      ])
    end

  end
+1 −1
Original line number Diff line number Diff line
@@ -73,7 +73,7 @@ module JsDuck
      @formatter.doc_context = {:filename => guide[:filename], :linenr => 0}
      @formatter.images = Img::Dir.new(guide["url"], "guides/#{guide["name"]}")
      html = @formatter.format(Util::IO.read(guide[:filename]))
      html = GuideToc.inject(html, guide['name'], @opts.guides_toc_level)
      html = GuideToc.new(html, guide['name'], @opts.guides_toc_level).inject!
      html = GuideAnchors.transform(html, guide['name'])

      # Report unused images (but ignore the icon files)
+1 −1
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ require "jsduck/guide_toc"
describe JsDuck::GuideToc do

  def inject(html)
    JsDuck::GuideToc.inject(html, "myguide", max_level)
    JsDuck::GuideToc.new(html, "myguide", max_level).inject!
  end

  describe "With max_level=1" do