Loading lib/jsduck/guide_toc.rb +45 −27 Original line number Diff line number Diff line require 'jsduck/util/html' require 'jsduck/guide_toc_entry' 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 @toc = GuideTocEntry.new @new_html = [] end # Inserts table of contents at the top of guide HTML by looking # for <h2> elements. def self.inject(html, guide_name) toc = [] new_html = [] html.each_line do |line| if line =~ /^\s*<(h[1-6])>(.*?)<\/h[1-6]>$/ tag = $1 # for headings at or below the specified maximum level. 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 = guide_name + "-section-" + title_to_id(text) if tag == "h2" toc << "<li><a href='#!/guide/#{id}'>#{text}</a></li>\n" id = title_to_id(text) if include_to_toc?(level) @toc.add(level, id, text) end new_html << "<#{tag} id='#{id}'>#{text}</#{tag}>\n" @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", "<ol>\n", toc, "</ol>\n", "</div>\n", ]) inject_toc! @new_html.flatten.join end new_html.flatten.join private def include_to_toc?(level) (@min_level..@max_level).include?(level) end def self.title_to_id(title) CGI::escape(title.downcase.gsub(/ /, "-")) 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.count < 2 @new_html.insert(1, [ "<div class='toc'>\n", "<p><strong>Contents</strong></p>\n", @toc.to_html, "</div>\n", ]) end end Loading lib/jsduck/guide_toc_entry.rb 0 → 100644 +54 −0 Original line number Diff line number Diff line require 'jsduck/util/html' module JsDuck # Manages the single TOC entry (with possible subentries). class GuideTocEntry attr_accessor :label, :items def initialize(parent=nil) @parent = parent @label = "" @items = [] @min_level = 2 end # Adds entry at the corresponding heading level. def add(level, id, text) if level == @min_level @items << GuideTocEntry.new(self) @items.last.label = "#{prefix} <a href='#!/guide/#{id}'>#{text}</a>\n" else if @items.empty? @items << GuideTocEntry.new(self) end @items.last.add(level-1, id, text) end end # Generates the heading counter, like "1.5.4." def prefix (@parent ? @parent.prefix : "") + "#{@items.length}." end # Total number of headings in TOC def count @items.map {|item| 1 + item.count}.reduce(0, :+) end # Converts to nested HTML list. def to_html return if @items.empty? return [ "<ul>", @items.map do |item| "<li>#{item.label} #{item.to_html}</li>" end, "</ul>", ].flatten.compact.join("\n") end end end lib/jsduck/guides.rb +1 −1 Original line number Diff line number Diff line Loading @@ -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']) 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) Loading lib/jsduck/options.rb +21 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ module JsDuck attr_accessor :message attr_accessor :welcome attr_accessor :guides attr_accessor :guides_toc_level attr_accessor :videos attr_accessor :examples attr_accessor :categories_path Loading Loading @@ -112,6 +113,7 @@ module JsDuck @message = "" @welcome = nil @guides = nil @guides_toc_level = 2 @videos = nil @examples = nil @categories_path = nil Loading Loading @@ -393,6 +395,25 @@ module JsDuck @guides = canonical(path) end opts.on('--guides-toc-level=LEVEL', "Max heading level for guides' tables of contents.", "", "Values between 1 and 6 define the maximum level of a heading", "to be included into guides' Table of Contents:", "", "1 - Hides the table of contents.", "2 - <H2> headings are included.", "3 - <H2>,<H3> headings are included.", "4 - <H2>,<H3>,<H4> headings are included.", "5 - <H2>,<H3>,<H4>,<H5> headings are included.", "6 - <H2>,<H3>,<H4>,<H5>,<H6> headings are included.") do |level| @guides_toc_level = level.to_i if !(1..6).include? @guides_toc_level Logger.fatal("Unsupported --guides-toc-level: '#{level}'") exit(1) end end opts.on('--videos=PATH', "JSON file describing the videos.", "", Loading spec/guide_toc_spec.rb +106 −56 Original line number Diff line number Diff line Loading @@ -4,9 +4,24 @@ require "jsduck/guide_toc" describe JsDuck::GuideToc do def inject(html) JsDuck::GuideToc.inject(html, "myguide") JsDuck::GuideToc.new(html, "myguide", max_level).inject! end describe "With max_level=1" do let(:max_level) { 2 } it "adds no toc section even when many H1 headings" do inject(<<-EOHTML).should_not =~ /<div class='toc'>/ <h1>Chapter A</h2> <h1>Chapter B</h2> <h1>Chapter C</h2> EOHTML end end describe "With max_level=2" do let(:max_level) { 2 } it "adds no toc section when no headings" do inject("blah").should_not =~ /<div class='toc'>/ end Loading Loading @@ -75,5 +90,40 @@ describe JsDuck::GuideToc do <h5>Another Chapter</h5> EOHTML end end describe "With max_level=3" do let(:max_level) { 3 } it "adds TOC when one H2 heading and one H3 heading" do inject(<<-EOHTML).should =~ /<div class='toc'>/ <h2>Chapter A</h2> <h3>Chapter A.A</h3> EOHTML end it "adds TOC when two H3 headings" do inject(<<-EOHTML).should =~ /<div class='toc'>/ <h3>Chapter A.A</h3> <h3>Chapter A.B</h3> EOHTML end it "links H3 heading from TOC" do inject(<<-EOHTML).should =~ /<a href='#!\/guide\/myguide-section-h3-chapter'>/ <h2>My Chapter</h2> <h3>H3 Chapter</h3> EOHTML end it "doesn't include any other headings besides H2 and H3 to TOC" do inject(<<-EOHTML).should_not =~ /<a href='#!\/guide\/myguide-section-my-chapter'>/ <h2>Foo</h2> <h3>Bar</h3> <h4>My Chapter</h4> <h5>Another Chapter</h5> EOHTML end end end Loading
lib/jsduck/guide_toc.rb +45 −27 Original line number Diff line number Diff line require 'jsduck/util/html' require 'jsduck/guide_toc_entry' 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 @toc = GuideTocEntry.new @new_html = [] end # Inserts table of contents at the top of guide HTML by looking # for <h2> elements. def self.inject(html, guide_name) toc = [] new_html = [] html.each_line do |line| if line =~ /^\s*<(h[1-6])>(.*?)<\/h[1-6]>$/ tag = $1 # for headings at or below the specified maximum level. 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 = guide_name + "-section-" + title_to_id(text) if tag == "h2" toc << "<li><a href='#!/guide/#{id}'>#{text}</a></li>\n" id = title_to_id(text) if include_to_toc?(level) @toc.add(level, id, text) end new_html << "<#{tag} id='#{id}'>#{text}</#{tag}>\n" @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", "<ol>\n", toc, "</ol>\n", "</div>\n", ]) inject_toc! @new_html.flatten.join end new_html.flatten.join private def include_to_toc?(level) (@min_level..@max_level).include?(level) end def self.title_to_id(title) CGI::escape(title.downcase.gsub(/ /, "-")) 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.count < 2 @new_html.insert(1, [ "<div class='toc'>\n", "<p><strong>Contents</strong></p>\n", @toc.to_html, "</div>\n", ]) end end Loading
lib/jsduck/guide_toc_entry.rb 0 → 100644 +54 −0 Original line number Diff line number Diff line require 'jsduck/util/html' module JsDuck # Manages the single TOC entry (with possible subentries). class GuideTocEntry attr_accessor :label, :items def initialize(parent=nil) @parent = parent @label = "" @items = [] @min_level = 2 end # Adds entry at the corresponding heading level. def add(level, id, text) if level == @min_level @items << GuideTocEntry.new(self) @items.last.label = "#{prefix} <a href='#!/guide/#{id}'>#{text}</a>\n" else if @items.empty? @items << GuideTocEntry.new(self) end @items.last.add(level-1, id, text) end end # Generates the heading counter, like "1.5.4." def prefix (@parent ? @parent.prefix : "") + "#{@items.length}." end # Total number of headings in TOC def count @items.map {|item| 1 + item.count}.reduce(0, :+) end # Converts to nested HTML list. def to_html return if @items.empty? return [ "<ul>", @items.map do |item| "<li>#{item.label} #{item.to_html}</li>" end, "</ul>", ].flatten.compact.join("\n") end end end
lib/jsduck/guides.rb +1 −1 Original line number Diff line number Diff line Loading @@ -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']) 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) Loading
lib/jsduck/options.rb +21 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ module JsDuck attr_accessor :message attr_accessor :welcome attr_accessor :guides attr_accessor :guides_toc_level attr_accessor :videos attr_accessor :examples attr_accessor :categories_path Loading Loading @@ -112,6 +113,7 @@ module JsDuck @message = "" @welcome = nil @guides = nil @guides_toc_level = 2 @videos = nil @examples = nil @categories_path = nil Loading Loading @@ -393,6 +395,25 @@ module JsDuck @guides = canonical(path) end opts.on('--guides-toc-level=LEVEL', "Max heading level for guides' tables of contents.", "", "Values between 1 and 6 define the maximum level of a heading", "to be included into guides' Table of Contents:", "", "1 - Hides the table of contents.", "2 - <H2> headings are included.", "3 - <H2>,<H3> headings are included.", "4 - <H2>,<H3>,<H4> headings are included.", "5 - <H2>,<H3>,<H4>,<H5> headings are included.", "6 - <H2>,<H3>,<H4>,<H5>,<H6> headings are included.") do |level| @guides_toc_level = level.to_i if !(1..6).include? @guides_toc_level Logger.fatal("Unsupported --guides-toc-level: '#{level}'") exit(1) end end opts.on('--videos=PATH', "JSON file describing the videos.", "", Loading
spec/guide_toc_spec.rb +106 −56 Original line number Diff line number Diff line Loading @@ -4,9 +4,24 @@ require "jsduck/guide_toc" describe JsDuck::GuideToc do def inject(html) JsDuck::GuideToc.inject(html, "myguide") JsDuck::GuideToc.new(html, "myguide", max_level).inject! end describe "With max_level=1" do let(:max_level) { 2 } it "adds no toc section even when many H1 headings" do inject(<<-EOHTML).should_not =~ /<div class='toc'>/ <h1>Chapter A</h2> <h1>Chapter B</h2> <h1>Chapter C</h2> EOHTML end end describe "With max_level=2" do let(:max_level) { 2 } it "adds no toc section when no headings" do inject("blah").should_not =~ /<div class='toc'>/ end Loading Loading @@ -75,5 +90,40 @@ describe JsDuck::GuideToc do <h5>Another Chapter</h5> EOHTML end end describe "With max_level=3" do let(:max_level) { 3 } it "adds TOC when one H2 heading and one H3 heading" do inject(<<-EOHTML).should =~ /<div class='toc'>/ <h2>Chapter A</h2> <h3>Chapter A.A</h3> EOHTML end it "adds TOC when two H3 headings" do inject(<<-EOHTML).should =~ /<div class='toc'>/ <h3>Chapter A.A</h3> <h3>Chapter A.B</h3> EOHTML end it "links H3 heading from TOC" do inject(<<-EOHTML).should =~ /<a href='#!\/guide\/myguide-section-h3-chapter'>/ <h2>My Chapter</h2> <h3>H3 Chapter</h3> EOHTML end it "doesn't include any other headings besides H2 and H3 to TOC" do inject(<<-EOHTML).should_not =~ /<a href='#!\/guide\/myguide-section-my-chapter'>/ <h2>Foo</h2> <h3>Bar</h3> <h4>My Chapter</h4> <h5>Another Chapter</h5> EOHTML end end end