lib/yard/templates/helpers/html_helper.rb in yard-0.5.8 vs lib/yard/templates/helpers/html_helper.rb in yard-0.6.0

- old
+ new

@@ -1,15 +1,18 @@ require 'cgi' module YARD module Templates::Helpers + # The helper module for HTML templates. module HtmlHelper include MarkupHelper include HtmlSyntaxHighlightHelper SimpleMarkupHtml = RDoc::Markup::ToHtml.new rescue SM::ToHtml.new + # @group Escaping Template Data + # Escapes HTML entities # # @param [String] text the text to escape # @return [String] the HTML with escaped entities def h(text) @@ -22,69 +25,87 @@ # @return [String] the escaped URL def urlencode(text) CGI.escape(text.to_s) end - # Returns the current character set. The default value can be overridden - # by setting the +LANG+ environment variable or by overriding this - # method. In Ruby 1.9 you can also modify this value by setting - # +Encoding.default_external+. - # - # @return [String] the current character set - def charset - return 'utf-8' unless RUBY19 || lang = ENV['LANG'] - if RUBY19 - lang = Encoding.default_external.name.downcase - else - lang = lang.downcase.split('.').last - end - case lang - when "ascii-8bit", "us-ascii", "ascii-7bit"; 'iso-8859-1' - else; lang - end - end + # @group Converting Markup to HTML # Turns text into HTML using +markup+ style formatting. # # @param [String] text the text to format # @param [Symbol] markup examples are +:markdown+, +:textile+, +:rdoc+. # To add a custom markup type, see {MarkupHelper} # @return [String] the HTML def htmlify(text, markup = options[:markup]) + markup_meth = "html_markup_#{markup}" + return text unless respond_to?(markup_meth) return "" unless text return text unless markup load_markup_provider(markup) - - # TODO: other libraries might be more complex - case markup - when :markdown - html = markup_class(markup).new(text).to_html - when :textile - doc = markup_class(markup).new(text) - doc.hard_breaks = false if doc.respond_to?(:hard_breaks=) - html = doc.to_html - when :rdoc - - begin - SimpleMarkupHtml.instance_variable_set("@from_path", url_for(object)) - html = MarkupHelper::SimpleMarkup.convert(text, SimpleMarkupHtml) - end - - html = fix_dash_dash(html) - html = fix_typewriter(html) + html = send(markup_meth, text) + if html.respond_to?(:encode) + html = html.force_encoding(text.encoding) # for libs that mess with encoding + html = html.encode(:invalid => :replace, :replace => '?') end - - html.force_encoding(Encoding.default_external) if RUBY19 html = resolve_links(html) html = html.gsub(/<pre>(?:\s*<code>)?(.+?)(?:<\/code>\s*)?<\/pre>/m) do str = $1 str = html_syntax_highlight(CGI.unescapeHTML(str)) unless options[:no_highlight] %Q{<pre class="code">#{str}</pre>} end html end + # Converts Markdown to HTML + # @param [String] text input Markdown text + # @return [String] output HTML + # @since 0.6.0 + def html_markup_markdown(text) + # TODO: other libraries might be more complex + markup_class(:markdown).new(text).to_html + end + + # Converts Textile to HTML + # @param [String] text the input Textile text + # @return [String] output HTML + # @since 0.6.0 + def html_markup_textile(text) + doc = markup_class(:textile).new(text) + doc.hard_breaks = false if doc.respond_to?(:hard_breaks=) + doc.to_html + end + + # Converts RDoc formatting (SimpleMarkup) to HTML + # @param [String] text the input RDoc formatted text + # @return [String] output HTML + # @since 0.6.0 + def html_markup_rdoc(text) + begin + SimpleMarkupHtml.instance_variable_set("@from_path", url_for(object)) + html = MarkupHelper::SimpleMarkup.convert(text, SimpleMarkupHtml) + end + + html = fix_dash_dash(html) + html = fix_typewriter(html) + end + + # Converts plaintext to HTML + # @param [String] text the input text + # @return [String] the output HTML + # @since 0.6.0 + def html_markup_text(text) + "<pre>" + text + "</pre>" + end + + # Converts HTML to HTML + # @param [String] text input html + # @return [String] output HTML + # @since 0.6.0 + def html_markup_html(text) + text + end + # @return [String] HTMLified text as a single line (paragraphs removed) def htmlify_line(*args) "<div class='inline'>" + htmlify(*args) + "</div>" end @@ -108,11 +129,45 @@ # # @todo Refactor into own SimpleMarkup subclass def fix_dash_dash(text) text.gsub(/&#8212;(?=\S)/, '--') end + + # @group Syntax Highlighting Source Code + + # Syntax highlights +source+ in language +type+. + # + # @note To support a specific language +type+, implement the method + # +html_syntax_highlight_TYPE+ in this class. + # + # @param [String] source the source code to highlight + # @param [Symbol] type the language type (:ruby, :plain, etc). Use + # :plain for no syntax highlighting. + # @return [String] the highlighted source + def html_syntax_highlight(source, type = nil) + return "" unless source + return h(source) if options[:no_highlight] + type ||= object.source_type || :ruby + + # handle !!!LANG prefix to send to html_syntax_highlight_LANG + if source =~ /\A(?:[ \t]*\r?\n)?[ \t]*!!!([\w.+-]+)[ \t]*\r?\n/ + type, source = $1, $' + source = $' + end + + meth = "html_syntax_highlight_#{type}" + respond_to?(meth) ? send(meth, source) : h(source) + end + + # @return [String] unhighlighted source + def html_syntax_highlight_plain(source) + h(source) + end + + # @group Linking Objects and URLs + # Resolves any text in the form of +{Name}+ to the object specified by # Name. Also supports link titles in the form +{Name title}+. # # @example Linking to an instance method # resolve_links("{MyClass#method}") # => "<a href='...'>MyClass#method</a>" @@ -121,68 +176,46 @@ # @param [String] text the text to resolve links in # @return [String] HTML with linkified references def resolve_links(text) code_tags = 0 text.gsub(/<(\/)?(pre|code|tt)|\{(\S+?)(?:\s(.*?\S))?\}(?=[\W<]|.+<\/|$)/) do |str| - closed, tag, name, title = $1, $2, $3, $4 + closed, tag, name, title, match = $1, $2, $3, $4, $& if tag code_tags += (closed ? -1 : 1) next str end next str unless code_tags == 0 + next(match) if name[0,1] == '|' if object.is_a?(String) object else link = linkify(name, title) if link == name || link == title - match = text[/(.{0,20}\{.*?#{Regexp.quote name}.*?\}.{0,20})/, 1] - log.warn "In file `#{object.file}':#{object.line}: Cannot resolve link to #{name} from text" + (match ? ":" : ".") - log.warn '...' + match.gsub(/\n/,"\n\t") + '...' if match + match = /(.+)?(\{#{Regexp.quote name}(?:\s.*?)?\})(.+)?/.match(text) + file = (@file ? @file : object.file) || '(unknown)' + line = (@file ? 1 : (object.docstring.line_range ? object.docstring.line_range.first : 1)) + (match ? $`.count("\n") : 0) + log.warn "In file `#{file}':#{line}: Cannot resolve link to #{name} from text" + (match ? ":" : ".") + log.warn((match[1] ? '...' : '') + match[2].gsub("\n","") + (match[3] ? '...' : '')) if match end - if name =~ %r{://} || name =~ /^(mailto|file):/ - link - else - "<tt>" + link + "</tt>" - end + link end end end - - def format_object_name_list(objects) - objects.sort_by {|o| o.name.to_s.downcase }.map do |o| - "<span class='name'>" + linkify(o, o.name) + "</span>" - end.join(", ") - end - # Formats a list of types from a tag. - # - # @param [Array<String>, FalseClass] typelist - # the list of types to be formatted. - # - # @param [Boolean] brackets omits the surrounding - # brackets if +brackets+ is set to +false+. - # - # @return [String] the list of types formatted - # as [Type1, Type2, ...] with the types linked - # to their respective descriptions. - # - def format_types(typelist, brackets = true) - return unless typelist.is_a?(Array) - list = typelist.map do |type| - type = type.gsub(/([<>])/) { h($1) } - type = type.gsub(/([\w:]+)/) { $1 == "lt" || $1 == "gt" ? $1 : linkify($1, $1) } - "<tt>" + type + "</tt>" - end - list.empty? ? "" : (brackets ? "(#{list.join(", ")})" : list.join(", ")) - end - + # (see BaseHelper#link_file) def link_file(filename, title = nil, anchor = nil) link_url(url_for_file(filename, anchor), title) end + + # (see BaseHelper#link_include_object) + def link_include_object(obj) + htmlify(obj.docstring) + end + # (see BaseHelper#link_object) def link_object(obj, otitle = nil, anchor = nil, relative = true) return otitle if obj.nil? obj = Registry.resolve(object, obj, true, true) if obj.is_a?(String) if !otitle && obj.root? title = "Top Level Namespace" @@ -195,26 +228,29 @@ end return title unless serializer return title if obj.is_a?(CodeObjects::Proxy) link = url_for(obj, anchor, relative) - link ? link_url(link, title, :title => "#{obj.path} (#{obj.type})") : title + link = link ? link_url(link, title, :title => "#{obj.path} (#{obj.type})") : title + "<span class='object_link'>" + link + "</span>" end + # (see BaseHelper#link_url) def link_url(url, title = nil, params = {}) title ||= url params = SymbolHash.new(false).update( :href => url, :title => h(title) ).update(params) + params[:target] ||= '_parent' if url =~ /^(\w+):\/\// "<a #{tag_attrs(params)}>#{title}</a>" end - def tag_attrs(opts = {}) - opts.sort_by {|k, v| k.to_s }.map {|k,v| "#{k}=#{v.to_s.inspect}" if v }.join(" ") - end + # @group URL Helpers + # @param [CodeObjects::Base] object the object to get an anchor for + # @return [String] the anchor for a specific object def anchor_for(object) case object when CodeObjects::MethodObject "#{object.name}-#{object.scope}_#{object.type}" when CodeObjects::ClassVariableObject @@ -226,10 +262,16 @@ else object.to_s end end + # Returns the URL for an object. + # + # @param [String, CodeObjects::Base] obj the object (or object path) to link to + # @param [String] anchor the anchor to link to + # @param [Boolean] relative use a relative or absolute link + # @return [String] the URL location of the object def url_for(obj, anchor = nil, relative = true) link = nil return link unless serializer if obj.is_a?(CodeObjects::Base) && !obj.is_a?(CodeObjects::NamespaceObject) @@ -254,10 +296,15 @@ end link + (anchor ? '#' + urlencode(anchor_for(anchor)) : '') end + # Returns the URL for a specific file + # + # @param [String] filename the filename to link to + # @param [String] anchor optional anchor + # @return [String] the URL pointing to the file def url_for_file(filename, anchor = nil) fromobj = object if CodeObjects::Base === fromobj && !fromobj.is_a?(CodeObjects::NamespaceObject) fromobj = fromobj.namespace end @@ -269,16 +316,54 @@ end link = File.relative_path(from, filename) link + '.html' + (anchor ? '#' + urlencode(anchor) : '') end + # @group Formatting Objects and Attributes + + # Formats a list of objects and links them + # @return [String] a formatted list of objects + def format_object_name_list(objects) + objects.sort_by {|o| o.name.to_s.downcase }.map do |o| + "<span class='name'>" + linkify(o, o.name) + "</span>" + end.join(", ") + end + + # Formats a list of types from a tag. + # + # @param [Array<String>, FalseClass] typelist + # the list of types to be formatted. + # + # @param [Boolean] brackets omits the surrounding + # brackets if +brackets+ is set to +false+. + # + # @return [String] the list of types formatted + # as [Type1, Type2, ...] with the types linked + # to their respective descriptions. + # + def format_types(typelist, brackets = true) + return unless typelist.is_a?(Array) + list = typelist.map do |type| + type = type.gsub(/([<>])/) { h($1) } + type = type.gsub(/([\w:]+)/) { $1 == "lt" || $1 == "gt" ? $1 : linkify($1, $1) } + "<tt>" + type + "</tt>" + end + list.empty? ? "" : (brackets ? "(#{list.join(", ")})" : list.join(", ")) + end + + # Get the return types for a method signature. + # + # @param [CodeObjects::MethodObject] meth the method object + # @param [Boolean] link whether to link the types + # @return [String] the signature types + # @since 0.5.3 def signature_types(meth, link = true) meth = convert_method_to_overload(meth) type = options[:default_return] || "" if meth.tag(:return) && meth.tag(:return).types - types = meth.tags(:return).map {|t| t.types ? t.types : [] }.flatten + types = meth.tags(:return).map {|t| t.types ? t.types : [] }.flatten.uniq first = link ? h(types.first) : format_types([types.first], false) if types.size == 2 && types.last == 'nil' type = first + '<sup>?</sup>' elsif types.size == 2 && types.last =~ /^(Array)?<#{Regexp.quote types.first}>$/ type = first + '<sup>+</sup>' @@ -294,10 +379,19 @@ end type = "(#{type}) " unless type.empty? type end + # Formats the signature of method +meth+. + # + # @param [CodeObjects::MethodObject] meth the method object to list + # the signature of + # @param [Boolean] link whether to link the method signature to the details view + # @param [Boolean] show_extras whether to show extra meta-data (visibility, attribute info) + # @param [Boolean] full_attr_name whether to show the full attribute name + # ("name=" instead of "name") + # @return [String] the formatted method signature def signature(meth, link = true, show_extras = true, full_attr_name = true) meth = convert_method_to_overload(meth) type = signature_types(meth, link) scope = meth.scope == :class ? "+" : "-" @@ -326,29 +420,45 @@ else title + extras_text end end - def html_syntax_highlight(source, type = :ruby) - return "" unless source - return h(source) if options[:no_highlight] - - # handle !!!LANG prefix to send to html_syntax_highlight_LANG - if source =~ /\A(?:[ \t]*\r?\n)?[ \t]*!!!([\w.+-]+)[ \t]*\r?\n/ - type, source = $1, $' - source = $' + # @group Getting the Character Encoding + + # Returns the current character set. The default value can be overridden + # by setting the +LANG+ environment variable or by overriding this + # method. In Ruby 1.9 you can also modify this value by setting + # +Encoding.default_external+. + # + # @return [String] the current character set + # @since 0.5.4 + def charset + return 'utf-8' unless RUBY19 || lang = ENV['LANG'] + if RUBY19 + lang = Encoding.default_external.name.downcase + else + lang = lang.downcase.split('.').last end - - meth = "html_syntax_highlight_#{type}" - respond_to?(meth) ? send(meth, source) : h(source) + case lang + when "ascii-8bit", "us-ascii", "ascii-7bit"; 'iso-8859-1' + else; lang + end end - def html_syntax_highlight_plain(source) - h(source) - end + # @endgroup private + # Converts a set of hash options into HTML attributes for a tag + # + # @param [Hash{String => String}] opts the tag options + # @return [String] the tag attributes of an HTML tag + def tag_attrs(opts = {}) + opts.sort_by {|k, v| k.to_s }.map {|k,v| "#{k}=#{v.to_s.inspect}" if v }.join(" ") + end + + # Converts a {CodeObjects::MethodObject} into an overload object + # @since 0.5.3 def convert_method_to_overload(meth) # use first overload tag if it has a return type and method itself does not if !meth.tag(:return) && meth.tags(:overload).size == 1 && meth.tag(:overload).tag(:return) return meth.tag(:overload) end