Class | Ronn::Document |
In: |
lib/ronn/document.rb
|
Parent: | Object |
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.
Ronn files may optionally follow the naming convention: "<name>.<section>.ronn". The <name> and <section> are used in generated documentation unless overridden by the information extracted from the document‘s name section.
data | [R] | The raw input data, read from path or stream and unmodified. |
date | [RW] | The date the document was published; center displayed in the document footer. |
index | [RW] | The index used to resolve man and file references. |
manual | [RW] | The manual this document belongs to; center displayed in the header. |
name | [RW] | The man pages name: usually a single word name of a program or filename; displayed along with the section in the left and right portions of the header as well as the bottom right section of the footer. |
organization | [RW] | The name of the group, organization, or individual responsible for this document; displayed in the left portion of the footer. |
path | [R] | Path to the Ronn document. This may be ’-’ or nil when the Ronn::Document object is created with a stream. |
section | [RW] | The man page‘s section: a string whose first character is numeric; displayed in parenthesis along with the name. |
styles | [RW] | Array of style modules to apply to the document. |
tagline | [RW] | Single sentence description of the thing being described by this man page; displayed in the NAME section. |
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.
# File lib/ronn/document.rb, line 64 64: def initialize(path=nil, attributes={}, &block) 65: @path = path 66: @basename = path.to_s =~ /^-?$/ ? nil : File.basename(path) 67: @reader = block || 68: lambda do |f| 69: if ['-', nil].include?(f) 70: STDIN.read 71: else 72: File.read(f) 73: end 74: end 75: @data = @reader.call(path) 76: @name, @section, @tagline = sniff 77: 78: @styles = %w[man] 79: @manual, @organization, @date = nil 80: @markdown, @input_html, @html = nil 81: @index = Ronn::Index[path || '.'] 82: @index.add_manual(self) if path && name 83: 84: attributes.each { |attr_name,value| send("#{attr_name}=", value) } 85: end
Generate a file basename of the form "<name>.<section>.<type>" for the given file extension. Uses the name and section from the source file path but falls back on the name and section defined in the document.
# File lib/ronn/document.rb, line 91 91: def basename(type=nil) 92: type = nil if ['', 'roff'].include?(type.to_s) 93: [path_name || @name, path_section || @section, type]. 94: compact.join('.') 95: end
A Hpricot::Document for the manual content fragment.
# File lib/ronn/document.rb, line 214 214: def html 215: @html ||= process_html! 216: end
Construct a path for a file near the source file. Uses the Document#basename method to generate the basename part and appends it to the dirname of the source document.
# File lib/ronn/document.rb, line 100 100: def path_for(type=nil) 101: if @basename 102: File.join(File.dirname(path), basename(type)) 103: else 104: basename(type) 105: end 106: end
Sniff the document header and extract basic document metadata. Return a tuple of the form: [name, section, description], where missing information is represented by nil and any element may be missing.
# File lib/ronn/document.rb, line 190 190: def sniff 191: html = Markdown.new(data[0, 512]).to_html 192: heading, html = html.split("</h1>\n", 2) 193: return [nil, nil, nil] if html.nil? 194: 195: case heading 196: when /([\w_.\[\]~+=@:-]+)\s*\((\d\w*)\)\s*-+\s*(.*)/ 197: # name(section) -- description 198: [$1, $2, $3] 199: when /([\w_.\[\]~+=@:-]+)\s+-+\s+(.*)/ 200: # name -- description 201: [$1, nil, $2] 202: else 203: # description 204: [nil, nil, heading.sub('<h1>', '')] 205: end 206: end
Styles to insert in the generated HTML output. This is a simple Array of string module names or file paths.
# File lib/ronn/document.rb, line 183 183: def styles=(styles) 184: @styles = (%w[man] + styles).uniq 185: end
# File lib/ronn/document.rb, line 263 263: def to_h 264: %w[name section tagline manual organization date styles toc]. 265: inject({}) { |hash, name| hash[name] = send(name); hash } 266: end
Convert the document to HTML and return the result as a string.
# File lib/ronn/document.rb, line 234 234: def to_html 235: if layout = ENV['RONN_LAYOUT'] 236: if !File.exist?(layout_path = File.expand_path(layout)) 237: warn "warn: can't find #{layout}, using default layout." 238: layout_path = nil 239: end 240: end 241: 242: template = Ronn::Template.new(self) 243: template.context.push :html => to_html_fragment(wrap_class=nil) 244: template.render(layout_path || 'default') 245: end
Convert the document to HTML and return the result as a string. The HTML does not include <html>, <head>, or <style> tags.
# File lib/ronn/document.rb, line 250 250: def to_html_fragment(wrap_class='mp') 251: return html.to_s if wrap_class.nil? 252: [ 253: "<div class='#{wrap_class}'>", 254: html.to_s, 255: "</div>" 256: ].join("\n") 257: end
# File lib/ronn/document.rb, line 273 273: def to_json 274: require 'json' 275: to_h.merge('date' => date.iso8601).to_json 276: end
Convert the document to roff and return the result as a string.
# File lib/ronn/document.rb, line 225 225: def to_roff 226: RoffFilter.new( 227: to_html_fragment(wrap_class=nil), 228: name, section, tagline, 229: manual, organization, date 230: ).to_s 231: end
# File lib/ronn/document.rb, line 268 268: def to_yaml 269: require 'yaml' 270: to_h.to_yaml 271: 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.
# File lib/ronn/document.rb, line 175 175: def toc 176: @toc ||= 177: html.search('h2[@id]').map { |h2| [h2.attributes['id'], h2.inner_text] } 178: end
Perform angle quote (<THESE>) post filtering.
# File lib/ronn/document.rb, line 355 355: def html_filter_angle_quotes 356: # convert all angle quote vars nested in code blocks 357: # back to the original text 358: @html.search('code').search('text()').each do |node| 359: next unless node.to_html.include?('var>') 360: new = 361: node.to_html. 362: gsub('<var>', '<'). 363: gsub("</var>", '>') 364: node.swap(new) 365: end 366: end
Add a ‘data-bare-link’ attribute to hyperlinks whose text labels are the same as their href URLs.
# File lib/ronn/document.rb, line 423 423: def html_filter_annotate_bare_links 424: @html.search('a[@href]').each do |node| 425: href = node.attributes['href'] 426: text = node.inner_text 427: 428: if href == text || 429: href[0] == ?# || 430: CGI.unescapeHTML(href) == "mailto:#{CGI.unescapeHTML(text)}" 431: then 432: node.set_attribute('data-bare-link', 'true') 433: end 434: end 435: end
Convert special format unordered lists to definition lists.
# File lib/ronn/document.rb, line 369 369: def html_filter_definition_lists 370: # process all unordered lists depth-first 371: @html.search('ul').to_a.reverse.each do |ul| 372: items = ul.search('li') 373: next if items.any? { |item| item.inner_text.split("\n", 2).first !~ /:$/ } 374: 375: ul.name = 'dl' 376: items.each do |item| 377: if child = item.at('p') 378: wrap = '<p></p>' 379: container = child 380: else 381: wrap = '<dd></dd>' 382: container = item 383: end 384: term, definition = container.inner_html.split(":\n", 2) 385: 386: dt = item.before("<dt>#{term}</dt>").first 387: dt.attributes['class'] = 'flush' if dt.inner_text.length <= 7 388: 389: item.name = 'dd' 390: container.swap(wrap.sub(/></, ">#{definition}<")) 391: end 392: end 393: end
Add URL anchors to all HTML heading elements.
# File lib/ronn/document.rb, line 415 415: def html_filter_heading_anchors 416: @html.search('h2|h3|h4|h5|h6').not('[@id]').each do |heading| 417: heading.set_attribute('id', heading.inner_text.gsub(/\W+/, '-')) 418: end 419: end
# File lib/ronn/document.rb, line 395 395: def html_filter_inject_name_section 396: markup = 397: if title? 398: "<h1>#{title}</h1>" 399: elsif name 400: "<h2>NAME</h2>\n" + 401: "<p class='man-name'>\n <code>#{name}</code>" + 402: (tagline ? " - <span class='man-whatis'>#{tagline}</span>\n" : "\n") + 403: "</p>\n" 404: end 405: if markup 406: if @html.children 407: @html.at("*").before(markup) 408: else 409: @html = Hpricot(markup) 410: end 411: end 412: end
Convert text of the form "name(section)" to a hyperlink. The URL is obtaiend from the index.
# File lib/ronn/document.rb, line 439 439: def html_filter_manual_reference_links 440: return if index.nil? 441: @html.search('text()').each do |node| 442: next if !node.content.include?(')') 443: next if %w[pre code h1 h2 h3].include?(node.parent.name) 444: next if child_of?(node, 'a') 445: node.swap( 446: node.content.gsub(/([0-9A-Za-z_:.+=@~-]+)(\(\d+\w*\))/) { 447: name, sect = $1, $2 448: if ref = index["#{name}#{sect}"] 449: "<a class='man-ref' href='#{ref.url}'>#{name}<span class='s'>#{sect}</span></a>" 450: else 451: # warn "warn: manual reference not defined: '#{name}#{sect}'" 452: "<span class='man-ref'>#{name}<span class='s'>#{sect}</span></span>" 453: end 454: } 455: ) 456: end 457: end
# File lib/ronn/document.rb, line 289 289: def input_html 290: @input_html ||= strip_heading(Markdown.new(markdown).to_html) 291: end
Convert <WORD> to <var>WORD</var> but only if WORD isn‘t an HTML tag.
# File lib/ronn/document.rb, line 341 341: def markdown_filter_angle_quotes(markdown) 342: markdown.gsub(/\<([^:.\/]+?)\>/) do |match| 343: contents = $1 344: tag, attrs = contents.split(' ', 2) 345: if attrs =~ /\/=/ || html_element?(tag.sub(/^\//, '')) || 346: data.include?("</#{tag}>") 347: match.to_s 348: else 349: "<var>#{contents}</var>" 350: end 351: end 352: end
Add [id]: ANCHOR elements to the markdown source text for all sections. This lets us use the [SECTION-REF][] syntax
# File lib/ronn/document.rb, line 328 328: def markdown_filter_heading_anchors(markdown) 329: first = true 330: markdown.split("\n").grep(/^[#]{2,5} +[\w '-]+[# ]*$/).each do |line| 331: markdown << "\n\n" if first 332: first = false 333: title = line.gsub(/[^\w -]/, '').strip 334: anchor = title.gsub(/\W+/, '-').gsub(/(^-+|-+$)/, '') 335: markdown << "[#{title}]: ##{anchor} \"#{title}\"\n" 336: end 337: markdown 338: end
Appends all index links to the end of the document as Markdown reference links. This lets us use [foo(3)][] syntax to link to index entries.
# File lib/ronn/document.rb, line 320 320: def markdown_filter_link_index(markdown) 321: return markdown if index.nil? || index.empty? 322: markdown << "\n\n" 323: index.each { |ref| markdown << "[#{ref.name}]: #{ref.url}\n" } 324: end
# File lib/ronn/document.rb, line 304 304: def process_html! 305: @html = Hpricot(input_html) 306: html_filter_angle_quotes 307: html_filter_definition_lists 308: html_filter_inject_name_section 309: html_filter_heading_anchors 310: html_filter_annotate_bare_links 311: html_filter_manual_reference_links 312: @html 313: end
# File lib/ronn/document.rb, line 298 298: def process_markdown! 299: markdown = markdown_filter_heading_anchors(self.data) 300: markdown_filter_link_index(markdown) 301: markdown_filter_angle_quotes(markdown) 302: end