lib/ronn/document.rb in ronn-0.5 vs lib/ronn/document.rb in ronn-0.6.0

- old
+ new

@@ -1,9 +1,11 @@ require 'set' +require 'cgi' require 'hpricot' require 'rdiscount' require 'ronn/roff' +require 'ronn/template' module Ronn # The Document class can be used to load and inspect a ronn document # and to convert a ronn document into other formats, like roff or # HTML. @@ -39,10 +41,13 @@ # The date the document was published; center displayed in # the document footer. attr_accessor :date + # Array of style modules to apply to the document. + attr_accessor :styles + # Create a Ronn::Document given a path or with the data returned by # calling the block. The document is loaded and preprocessed before # the intialize method returns. The attributes hash may contain values # for any writeable attributes defined on this class. def initialize(path=nil, attributes={}, &block) @@ -51,10 +56,12 @@ @reader = block || Proc.new { |f| File.read(f) } @data = @reader.call(path) @name, @section, @tagline = nil @manual, @organization, @date = nil @fragment = preprocess + @styles = %w[man] + attributes.each { |attr_name,value| send("#{attr_name}=", value) } end # Generate a file basename of the form "<name>.<section>.<type>" # for the given file extension. Uses the name and section from @@ -121,20 +128,35 @@ return @date if @date return File.mtime(path) if File.exist?(path) Time.now end + # Retrieve a list of top-level section headings in the document and return + # as an array of +[id, text]+ tuples, where +id+ is the element's generated + # id and +text+ is the inner text of the heading element. + def section_heads + parse_html(to_html_fragment).search('h2[@id]').map do |heading| + [heading.attributes['id'], heading.inner_text] + end + end + + # Styles to insert in the generated HTML output. This is a simple Array of + # string module names or file paths. + def styles=(styles) + @styles = (%w[man] + styles).uniq + end + # Convert the document to :roff, :html, or :html_fragment and # return the result as a string. def convert(format) send "to_#{format}" end # Convert the document to roff and return the result as a string. def to_roff RoffFilter.new( - to_html_fragment, + to_html_fragment(wrap_class=nil), name, section, tagline, manual, organization, @@ -142,48 +164,85 @@ ).to_s end # Convert the document to HTML and return the result as a string. def to_html - layout_filter(to_html_fragment) + if layout = ENV['RONN_LAYOUT'] + if !File.exist?(layout_path = File.expand_path(layout)) + warn "warn: can't find #{layout}, using default layout." + layout_path = nil + end + end + + template = Ronn::Template.new(self) + template.render(layout_path || 'default') end # Convert the document to HTML and return the result # as a string. The HTML does not include <html>, <head>, # or <style> tags. - def to_html_fragment + def to_html_fragment(wrap_class='mp') + wrap_class = nil if wrap_class.to_s.empty? buf = [] + buf << "<div class='#{wrap_class}'>" if wrap_class if name? && section? buf << "<h2 id='NAME'>NAME</h2>" - buf << "<p><code>#{name}</code> -- #{tagline}</p>" + buf << "<p><code>#{name}</code> - #{tagline}</p>" elsif tagline - buf << "<h1>#{[name, tagline].compact.join(' -- ')}</h1>" + buf << "<h1>#{[name, tagline].compact.join(' - ')}</h1>" end buf << @fragment.to_s + buf << "</div>" if wrap_class buf.join("\n") end protected + # The preprocessed markdown source text. + attr_reader :markdown + # Parse the document and extract the name, section, and tagline # from its contents. This is called while the object is being # initialized. def preprocess [ + :heading_anchor_pre_filter, :angle_quote_pre_filter, :markdown_filter, :angle_quote_post_filter, - :definition_list_filter + :definition_list_filter, + :heading_anchor_filter, + :annotate_bare_links_filter ].inject(data) { |res,filter| send(filter, res) } end - # Apply the standard HTML layout template. - def layout_filter(html) - template_file = File.dirname(__FILE__) + "/layout.html" - template = File.read(template_file) - eval("%Q{#{template}}", binding, template_file) + # Add a 'data-bare-link' attribute to hyperlinks + # whose text labels are the same as their href URLs. + def annotate_bare_links_filter(html) + doc = parse_html(html) + doc.search('a[@href]').each do |node| + href = node.attributes['href'] + text = node.inner_text + + if href == text || + href[0] == ?# || + CGI.unescapeHTML(href) == "mailto:#{CGI.unescapeHTML(text)}" + then + node.set_attribute('data-bare-link', 'true') + end + end + doc end + # Add URL anchors to all HTML heading elements. + def heading_anchor_filter(html) + doc = parse_html(html) + doc.search('h1|h2|h3|h4|h5|h6').not('[@id]').each do |heading| + heading.set_attribute('id', heading.inner_text.gsub(/\W+/, '-')) + end + doc + end + # Convert special format unordered lists to definition lists. def definition_list_filter(html) doc = parse_html(html) # process all unordered lists depth-first doc.search('ul').to_a.reverse.each do |ul| @@ -228,23 +287,24 @@ end # Run markdown on the data and extract name, section, and # tagline. def markdown_filter(data) + @markdown = data html = Markdown.new(data).to_html @tagline, html = html.split("</h1>\n", 2) if html.nil? html = @tagline @tagline = nil else # grab name and section from title @tagline.sub!('<h1>', '') - if @tagline =~ /([\w_.\[\]~+=@:-]+)\s*\((\d\w*)\)\s*--?\s*(.*)/ + if @tagline =~ /([\w_.\[\]~+=@:-]+)\s*\((\d\w*)\)\s*-+\s*(.*)/ @name = $1 @section = $2 @tagline = $3 - elsif @tagline =~ /([\w_.\[\]~+=@:-]+)\s+--\s+(.*)/ + elsif @tagline =~ /([\w_.\[\]~+=@:-]+)\s+-+\s+(.*)/ @name = $1 @tagline = $2 end end @@ -263,9 +323,23 @@ match.to_s else "<var>#{contents}</var>" end end + end + + # Add [id]: #ANCHOR elements to the markdown source text for all sections. + # This lets us use the [SECTION-REF][] syntax + def heading_anchor_pre_filter(data) + first = true + data.split("\n").grep(/^[#]{2,5} +[\w '-]+[# ]*$/).each do |line| + data << "\n\n" if first + first = false + title = line.gsub(/[^\w -]/, '').strip + anchor = title.gsub(/\W+/, '-').gsub(/(^-+|-+$)/, '') + data << "[#{title}]: ##{anchor} \"#{title}\"\n" + end + data end HTML = %w[ a abbr acronym b bdo big br cite code dfn em i img input kbd label q samp select