class Ronn::Document
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.
Attributes
The raw input data, read from path or stream and unmodified.
The date the document was published; center displayed in the document footer.
The index used to resolve man and file references.
The manual this document belongs to; center displayed in the header.
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.
The name of the group, organization, or individual responsible for this document; displayed in the left portion of the footer.
Path to the Ronn
document. This may be '-' or nil when the Ronn::Document
object is created with a stream.
The man page's section: a string whose first character is numeric; displayed in parenthesis along with the name.
Array of style modules to apply to the document.
Single sentence description of the thing being described by this man page; displayed in the NAME section.
Public Class Methods
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 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
Public Instance Methods
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 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
Convert the document to :roff, :html, or :html_fragment and return the result as a string.
# File lib/ronn/document.rb 220 def convert(format) 221 send "to_#{format}" 222 end
A Hpricot::Document for the manual content fragment.
# File lib/ronn/document.rb 214 def html 215 @html ||= process_html! 216 end
Preprocessed markdown input text.
# File lib/ronn/document.rb 209 def markdown 210 @markdown ||= process_markdown! 211 end
Truthful when the name was extracted from the name section of the document.
# File lib/ronn/document.rb 129 def name? 130 !@name.nil? 131 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 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
Returns the <name> part of the path, or nil when no path is available. This is used as the manual page name when the file contents do not include a name section.
# File lib/ronn/document.rb 111 def path_name 112 @basename[/^[^.]+/] if @basename 113 end
Returns the <section> part of the path, or nil when no path is available.
# File lib/ronn/document.rb 117 def path_section 118 $1 if @basename.to_s =~ /\.(\d\w*)\./ 119 end
The name used to reference this manual.
# File lib/ronn/document.rb 146 def reference_name 147 name + (section && "(#{section})").to_s 148 end
True when the section number was extracted from the name section of the document.
# File lib/ronn/document.rb 141 def section? 142 !@section.nil? 143 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 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 183 def styles=(styles) 184 @styles = (%w[man] + styles).uniq 185 end
The document's title when no name section was defined. When a name section exists, this value is nil.
# File lib/ronn/document.rb 159 def title 160 @tagline if !name? 161 end
Truthful when the document started with an h1 but did not follow the “<name>(<sect>) – <tagline>” convention. We assume this is some kind of custom title.
# File lib/ronn/document.rb 153 def title? 154 !name? && tagline 155 end
# File lib/ronn/document.rb 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 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 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 273 def to_json 274 require 'json' 275 to_h.merge('date' => date.iso8601).to_json 276 end
# File lib/ronn/document.rb 259 def to_markdown 260 markdown 261 end
Convert the document to roff and return the result as a string.
# File lib/ronn/document.rb 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 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 175 def toc 176 @toc ||= 177 html.search('h2[@id]').map { |h2| [h2.attributes['id'], h2.inner_text] } 178 end
Protected Instance Methods
Perform angle quote (<THESE>) post filtering.
# File lib/ronn/document.rb 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 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 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 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 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 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 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 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 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 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
Parse the document and extract the name, section, and tagline from its contents. This is called while the object is being initialized.
# File lib/ronn/document.rb 284 def preprocess! 285 input_html 286 nil 287 end
# File lib/ronn/document.rb 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 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
# File lib/ronn/document.rb 293 def strip_heading(html) 294 heading, html = html.split("</h1>\n", 2) 295 html || heading 296 end