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.

Methods

Included Modules

Ronn::Utils

Attributes

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.

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.

[Source]

    # 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

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.

[Source]

    # 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

Convert the document to :roff, :html, or :html_fragment and return the result as a string.

[Source]

     # File lib/ronn/document.rb, line 220
220:     def convert(format)
221:       send "to_#{format}"
222:     end

The date the man page was published. If not set explicitly, this is the file‘s modified time or, if no file is given, the current time.

[Source]

     # File lib/ronn/document.rb, line 166
166:     def date
167:       return @date if @date
168:       return File.mtime(path) if File.exist?(path)
169:       Time.now
170:     end

A Hpricot::Document for the manual content fragment.

[Source]

     # File lib/ronn/document.rb, line 214
214:     def html
215:       @html ||= process_html!
216:     end

Preprocessed markdown input text.

[Source]

     # File lib/ronn/document.rb, line 209
209:     def markdown
210:       @markdown ||= process_markdown!
211:     end

Returns the manual page name based first on the document‘s contents and then on the path name.

[Source]

     # File lib/ronn/document.rb, line 123
123:     def name
124:       @name || path_name
125:     end

Truthful when the name was extracted from the name section of the document.

[Source]

     # File lib/ronn/document.rb, line 129
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.

[Source]

     # 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

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.

[Source]

     # File lib/ronn/document.rb, line 111
111:     def path_name
112:       @basename[/^[^.]+/] if @basename
113:     end

Returns the <section> part of the path, or nil when no path is available.

[Source]

     # File lib/ronn/document.rb, line 117
117:     def path_section
118:       $1 if @basename.to_s =~ /\.(\d\w*)\./
119:     end

The name used to reference this manual.

[Source]

     # File lib/ronn/document.rb, line 146
146:     def reference_name
147:       name + (section && "(#{section})").to_s
148:     end

Returns the manual page section based first on the document‘s contents and then on the path name.

[Source]

     # File lib/ronn/document.rb, line 135
135:     def section
136:       @section || path_section
137:     end

True when the section number was extracted from the name section of the document.

[Source]

     # File lib/ronn/document.rb, line 141
141:     def section?
142:       !@section.nil?
143:     end
section_heads()

Alias for toc

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.

[Source]

     # 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.

[Source]

     # File lib/ronn/document.rb, line 183
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.

[Source]

     # File lib/ronn/document.rb, line 159
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.

[Source]

     # File lib/ronn/document.rb, line 153
153:     def title?
154:       !name? && tagline
155:     end

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # 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

[Source]

     # 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

[Source]

     # File lib/ronn/document.rb, line 259
259:     def to_markdown
260:       markdown
261:     end

Convert the document to roff and return the result as a string.

[Source]

     # 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

[Source]

     # 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.

[Source]

     # 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

Protected Instance methods

Perform angle quote (<THESE>) post filtering.

[Source]

     # 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&gt;')
360:         new =
361:           node.to_html.
362:             gsub('&lt;var&gt;', '&lt;').
363:             gsub("&lt;/var&gt;", '>')
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.

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # 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

[Source]

     # 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.

[Source]

     # 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

[Source]

     # 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.

[Source]

     # 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

[Source]

     # 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.

[Source]

     # 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

Parse the document and extract the name, section, and tagline from its contents. This is called while the object is being initialized.

[Source]

     # File lib/ronn/document.rb, line 284
284:     def preprocess!
285:       input_html
286:       nil
287:     end

[Source]

     # 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

[Source]

     # 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

[Source]

     # File lib/ronn/document.rb, line 293
293:     def strip_heading(html)
294:       heading, html = html.split("</h1>\n", 2)
295:       html || heading
296:     end

[Validate]