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. # # Ronn files may optionally follow the naming convention: # ".
.ronn". The and
are used in # generated documentation unless overridden by the information # extracted from the document's name section. class Document attr_reader :path, :data # 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. attr_accessor :name # The man page's section: a string whose first character # is numeric; displayed in parenthesis along with the name. attr_accessor :section # Single sentence description of the thing being described # by this man page; displayed in the NAME section. attr_accessor :tagline # The manual this document belongs to; center displayed in # the header. attr_accessor :manual # The name of the group, organization, or individual responsible # for this document; displayed in the left portion of the footer. attr_accessor :organization # 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) @path = path @basename = path.to_s =~ /^-?$/ ? nil : File.basename(path) @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 ".
." # 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. def basename(type=nil) type = nil if ['', 'roff'].include?(type.to_s) [path_name || @name, path_section || @section, type]. compact.join('.') 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. def path_for(type=nil) if @basename File.join(File.dirname(path), basename(type)) else basename(type) end end # Returns the 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. def path_name @basename[/^[^.]+/] if @basename end # Returns the
part of the path, or nil when # no path is available. def path_section $1 if @basename.to_s =~ /\.(\d\w*)\./ end # Returns the manual page name based first on the document's # contents and then on the path name. def name @name || path_name end # Truthful when the name was extracted from the name section # of the document. def name? !name.nil? end # Returns the manual page section based first on the document's # contents and then on the path name. def section @section || path_section end # True when the section number was extracted from the name # section of the document. def section? !section.nil? 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. def date 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(wrap_class=nil), name, section, tagline, manual, organization, date ).to_s end # Convert the document to HTML and return the result as a string. def to_html 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 , , # or