require 'jsduck/meta_tag_registry'
require 'cgi'
module JsDuck
# Ruby-side implementation of class docs Renderer.
# Uses PhantomJS to run Docs.Renderer JavaScript.
class Renderer
def render(cls)
@cls = cls
return [
"
",
render_sidebar,
"
",
render_private_class_notice,
@cls[:doc],
render_meta_data(@cls[:html_meta]),
"
",
"
",
render_member_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_meta_data(meta_data)
return if meta_data.size == 0
MetaTagRegistry.instance.tags.map {|tag| meta_data[tag.key] }
end
def render_sidebar
items = [
render_alternate_class_names,
render_tree,
render_dependencies(:allMixins, "Mixins"),
render_dependencies(:requires, "Requires"),
render_dependencies(:uses, "Uses"),
render_files,
]
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].map {|name| "#{name}
" },
]
end
def render_dependencies(type, title)
return if !@cls[type] || @cls[type].length == 0
return [
"#{title}
",
@cls[type].map {|name| "#{render_link(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])
""
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"
tree = ["Hierarchy
"]
if @cls[:superclasses].length > 0
tree + render_class_tree(@cls[:superclasses].concat([@cls[:name]]), {:first => true, :links => true})
else
tree + render_class_tree([@cls[:extends], @cls[:name]], {:first => true})
end
end
def render_class_tree(superclasses, o)
return "" if superclasses.length == 0
name = superclasses[0]
return [
"",
superclasses.length > 1 ? (o[:links] ? render_link(name) : name) : "#{name}",
render_class_tree(superclasses.slice(1, superclasses.length-1), {:links => o[:links]}),
"
",
]
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_member_sections
sections = [
{:type => :cfg, :title => "Config options"},
{:type => :property, :title => "Properties"},
{:type => :method, :title => "Methods"},
{:type => :event, :title => "Events"},
{:type => :css_var, :title => "CSS Variables"},
{:type => :css_mixin, :title => "CSS Mixins"},
]
# Skip rendering empty sections
sections.map do |sec|
members = @cls[:members][sec[:type]]
statics = @cls[:statics][sec[:type]]
if members.length > 0 || statics.length > 0
[
"",
statics.length == 0 ? "
Defined By
" : "",
"
#{sec[:title]}
",
render_subsection(members, statics.length > 0 ? "Instance #{sec[:title]}" : nil),
render_subsection(statics, "Static #{sec[:title]}"),
"
",
]
else
nil
end
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
"
",
"
",
# 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 = [m[:doc]]
if m[:default] && m[:default] != "undefined"
doc << "Defaults to: " + CGI.escapeHTML(m[:default]) + "
"
end
doc << render_meta_data(m[:html_meta])
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 << [
"",
params.map {|p| render_long_param(p) },
"
",
]
end
if item[:return]
doc << render_return(item[:return])
elsif ret
doc << render_return(ret)
end
doc
end
def render_long_param(p)
return [
"",
"#{p[:name]} : ",
p[:html_type],
p[:optional] ? " (optional)" : "",
"",
p[:doc],
p[:default] ? "
Defaults to: #{CGI.escapeHTML(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
",
"",
"- ",
"#{ret[:html_type]}",
"
",
ret[:doc],
ret[:properties] && ret[:properties].length > 0 ? render_params_and_return(ret) : "",
"
",
" ",
"
",
]
end
end
end