require 'jsduck/meta_tag_registry' require 'jsduck/html' module JsDuck # Ruby-side implementation of class docs Renderer. # Uses PhantomJS to run Docs.Renderer JavaScript. class Renderer def initialize(opts) @opts = opts end def render(cls) @cls = cls return [ "
", render_sidebar, "
", render_meta_data(@cls[:html_meta], :top), render_private_class_notice, @cls[:doc], render_enum_class_notice, render_meta_data(@cls[:html_meta], :bottom), "
", "
", render_all_sections, "
", "
", ].flatten.compact.join end def render_private_class_notice return if !@cls[:private] return [ "

NOTE ", "This is a private utility class for internal use by the framework. ", "Don't rely on its existence.

", ] end def render_enum_class_notice return if !@cls[:enum] if @cls[:enum][:type] == "String" first = @cls[:members][:property][0] || {:name => 'foo', :default => '"foo"'} [ "

ENUM: ", "This enumeration defines a set of String values. ", "It exists primarily for documentation purposes - ", "in code use the actual string values like #{first[:default]}, ", "don't reference them through this class like #{@cls[:name]}.#{first[:name]}.

", ] else [ "

ENUM: ", "This enumeration defines a set of #{@cls[:enum][:type]} values.

", ] end end def render_meta_data(meta_data, position) return if meta_data.size == 0 MetaTagRegistry.instance.tags(position).map {|tag| meta_data[tag.key] } end def render_sidebar items = [ render_alternate_class_names, render_tree, render_dependencies(:mixins, "Mixins"), render_dependencies(:parentMixins, "Inherited mixins"), render_dependencies(:requires, "Requires"), render_dependencies(:subclasses, "Subclasses"), render_dependencies(:mixedInto, "Mixed into"), render_dependencies(:uses, "Uses"), @opts.source ? render_files : nil, ] if items.compact.length > 0 return ['
', items, '
'] else return nil end end def render_alternate_class_names return if @cls[:alternateClassNames].length == 0 return [ "

Alternate names

", @cls[:alternateClassNames].sort.map {|name| "
#{name}
" }, ] end def render_dependencies(type, title) return if !@cls[type] || @cls[type].length == 0 return [ "

#{title}

", @cls[type].sort.map {|name| "
#{name.exists? ? render_link(name) : name}
" }, ] end def render_files return if @cls[:files].length == 0 || @cls[:files][0][:filename] == "" return [ "

Files

", @cls[:files].map do |file| url = "source/" + file[:href] title = File.basename(file[:filename]) "
#{title}
" end ] end # Take care of the special case where class has parent for which we have no docs. # In that case the "extends" property exists but "superclasses" is empty. # We still create the tree, but without links in it. def render_tree return if !@cls[:extends] || @cls[:extends] == "Object" return [ "

Hierarchy

", render_class_tree(@cls[:superclasses] + [@cls[:name]]) ] end def render_class_tree(classes, i=0) return "" if classes.length <= i name = classes[i] return [ "
", classes.length-1 == i ? "#{name}" : (name.exists? ? render_link(name) : name), render_class_tree(classes, i+1), "
", ] end def render_link(cls_name, member=nil) id = member ? cls_name + "-" + member[:id] : cls_name label = member ? cls_name + "." + member[:name] : cls_name return "#{label}" end def render_all_sections sections = [ {:type => :property, :title => "Properties"}, {:type => :method, :title => "Methods"}, {:type => :event, :title => "Events"}, {:type => :css_var, :title => "CSS Variables"}, {:type => :css_mixin, :title => "CSS Mixins"}, ] render_configs_section + sections.map {|sec| render_section(sec) } end def render_configs_section configs = @cls[:members][:cfg] + @cls[:statics][:cfg] if configs.length > 0 required, optional = configs.partition {|c| c[:meta][:required] } return [ "
", required.length == 0 ? "
Defined By
" : "", "

Config options

", render_subsection(required, "Required Config options"), render_subsection(optional, required.length > 0 ? "Optional Config options" : nil), "
", ] else return [] end end def render_section(sec) members = @cls[:members][sec[:type]] statics = @cls[:statics][sec[:type]] # Skip rendering empty sections if members.length > 0 || statics.length > 0 return [ "
", statics.length == 0 ? "
Defined By
" : "", "

#{sec[:title]}

", render_subsection(members, statics.length > 0 ? "Instance #{sec[:title]}" : nil), render_subsection(statics, "Static #{sec[:title]}"), "
", ] else return [] end end def render_subsection(members, title) return if members.length == 0 index = 0 return [ "
", title ? "
Defined By

#{title}

" : "", members.map {|m| index += 1; render_member(m, index == 1) }, "
", ] end def render_member(m, is_first) # use classname "first-child" when it's first member in its category first_child = is_first ? "first-child" : "" # shorthand to owner class owner = m[:owner] # is this method inherited from parent? inherited = (owner != @cls[:name]) return [ "
", # leftmost column: expand button "", # member name and type + link to owner class and source "
", "
", inherited ? "#{owner}" : "#{owner}", "
", @opts.source ? "view source" : "", "
", # method params signature or property type signature render_signature(m), "
", # short and long descriptions "
", "
", m[:shortDoc] ? m[:shortDoc] : m[:doc], "
", "
", render_long_doc(m), "
", "
", "
", ] end def render_signature(m) expandable = m[:shortDoc] ? "expandable" : "not-expandable" name = m[:name] before = "" if m[:tagname] == :method && m[:name] == "constructor" before = "new" name = @cls[:name] end if m[:tagname] == :cfg || m[:tagname] == :property || m[:tagname] == :css_var params = " : #{m[:html_type]}" else ps = m[:params].map {|p| render_short_param(p) }.join(", ") params = "( #{ps} )" if m[:tagname] == :method && m[:return][:type] != "undefined" params += " : " + m[:return][:html_type] end end after = "" MetaTagRegistry.instance.signatures.each do |s| after += "#{s[:long]}" if m[:meta][s[:key]] end uri = "#!/api/#{m[:owner]}-#{m[:id]}" return [ before, "#{name}", params, after ] end def render_short_param(param) p = param[:html_type] + " " + param[:name] return param[:optional] ? "["+p+"]" : p end def render_long_doc(m) doc = [] doc << render_meta_data(m[:html_meta], :top) doc << m[:doc] if m[:default] && m[:default] != "undefined" doc << "

Defaults to: " + HTML.escape(m[:default]) + "

" end doc << render_meta_data(m[:html_meta], :bottom) doc << render_params_and_return(m) if m[:overrides] overrides = m[:overrides].map {|o| render_link(o[:owner], o) }.join(", ") doc << "

Overrides: #{overrides}

" end doc end # Handles both rendering of method parameters and return value. # Plus the rendering of object properties, which could also be # functions in which case they too will be rendered with # parameters and return value. def render_params_and_return(item) doc = [] if item[:params] && item[:params].length > 0 params = item[:params] elsif item[:properties] && item[:properties].length > 0 params = item[:properties] # If the name of last property is "return" # remove it from params list and handle it separately afterwards if params.last[:name] == "return" ret = params.last params = params.slice(0, params.length-1) end end if params if item[:type] == "Function" || item[:tagname] == :method || item[:tagname] == :event doc << '

Parameters

' end doc << [ "", ] end if item[:return] doc << render_return(item[:return]) elsif ret doc << render_return(ret) end if item[:throws] doc << render_throws(item[:throws]) end doc end def render_long_param(p) return [ "
  • ", "#{p[:name]} : ", p[:html_type], p[:optional] ? " (optional)" : "", "
    ", p[:doc], p[:default] ? "

    Defaults to: #{HTML.escape(p[:default])}

    " : "", p[:properties] && p[:properties].length > 0 ? render_params_and_return(p) : "", "
    ", "
  • ", ] end def render_return(ret) return if ret[:type] == "undefined" return [ "

    Returns

    ", "", ] end def render_throws(throws) return [ "

    Throws

    ", "", ] end end end