require 'cgi'
module JsDuck
# Ruby-side implementation of class docs Renderer.
# Uses PhantomJS to run Docs.Renderer JavaScript.
class Renderer
def initialize(options={})
@options = options
end
def render(cls)
@cls = cls
return [
"
",
render_hierarchy,
"
",
render_private_class_notice,
@cls[:doc],
render_meta_data,
"
",
"
",
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
return if !@cls[:meta] || @cls[:meta].length == 0
html = [""
html
end
def render_hierarchy
has_parents = @cls[:extends] && @cls[:extends] != "Object"
has_alt_names = @cls[:alternateClassNames].length > 0
has_mixins = @cls[:superclasses].length > 0
return if !has_parents && !has_alt_names && !has_mixins
return [
'',
render_alternate_class_names,
render_tree,
render_mixins,
'
'
]
end
def render_alternate_class_names
return if @cls[:alternateClassNames].length == 0
return [
"Alternate names
",
@cls[:alternateClassNames].map {|name| "#{name}
" },
]
end
def render_mixins
return if @cls[:allMixins].length == 0
return [
"Mixins
",
@cls[:allMixins].map {|name| "#{render_link(name)}
" },
]
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)
return "#{cls_name}"
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" : ""
# use classname "expandable" when member has shortened description
expandable = m[:shortDoc] ? "expandable" : "not-expandable"
# shorthand to owner class
owner = m[:owner]
# use classname "inherited" when member is not defined in this class
inherited = owner == @cls[:name] ? "not-inherited" : "inherited"
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 = ""
if m[:protected]
after += "protected"
end
if m[:static]
after += "static"
end
if m[:deprecated]
after += "deprecated"
end
if m[:required]
after += "required"
end
if m[:template]
after += "template"
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
if m[:deprecated]
v = m[:deprecated][:version] ? "since " + m[:deprecated][:version] : ""
doc << [
"",
"
This #{m[:tagname]} has been deprecated #{v}
",
m[:deprecated][:text],
"
",
]
end
if m[:template]
doc << [
"",
"
This is a template method. A hook into the functionality of this class.",
"Feel free to override it in child classes.
",
"
",
]
end
doc << render_params_and_return(m)
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