| 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